1
0
mirror of https://github.com/openbsd/src.git synced 2026-05-01 17:46:35 +00:00

Test that TCP keepalive packets are sent. Set very short intervals

and use scapy to analyse ACK and RST from TCP stack.
This commit is contained in:
bluhm
2025-09-17 22:50:08 +00:00
parent 6d22a4a711
commit 611e730811
11 changed files with 155 additions and 19 deletions

View File

@@ -1,4 +1,4 @@
# $OpenBSD: Makefile,v 1.5 2025/09/09 18:49:54 bluhm Exp $
# $OpenBSD: Makefile,v 1.6 2025/09/17 22:50:08 bluhm Exp $
# Copyright (c) 2025 Alexander Bluhm <bluhm@openbsd.org>
#
@@ -106,6 +106,14 @@ PYTHON = PYTHONPATH=${.OBJDIR} python3 -u ${.CURDIR}/
REGRESS_TARGETS =
TCP_SCRIPTS !!= cd ${.CURDIR} && ls -1 tcp*.py
# keepalive is special, it needs sysctl to see effect within a few seconds
run-tcp_keepalive: tcp_keepalive.py addr.py
ssh ${REMOTE_SSH} ${SUDO} sysctl \
net.inet.tcp.keepidle=3 net.inet.tcp.keepintvl=1
${SUDO} ${PYTHON}tcp_keepalive.py
ssh ${REMOTE_SSH} ${SUDO} sysctl \
net.inet.tcp.keepidle=7200 net.inet.tcp.keepintvl=75
.for t in ${TCP_SCRIPTS:R}
REGRESS_TARGETS += run-$t
run-$t: $t.py addr.py
@@ -121,13 +129,18 @@ run-netstat-${t:L:S/_//g}: netstat-${t:L:S/_//g}.log
.endfor
.if ! empty(PF_ANCHOR:Mregress)
REGRESS_CLEANUP += cleanup
cleanup:
REGRESS_CLEANUP += cleanup-pf
cleanup-pf:
${SUDO} pfctl -a regress -Fr
ssh ${REMOTE_SSH} ${SUDO} pfctl -a regress -Fa
rm -f stamp-pfctl
.endif
REGRESS_CLEANUP += cleanup-sysctl
cleanup-sysctl:
ssh ${REMOTE_SSH} ${SUDO} sysctl net.inet.tcp.keepinittime=75 \
net.inet.tcp.keepidle=7200 net.inet.tcp.keepintvl=75
CLEANFILES += addr.py *.pyc *.log stamp-*
.PHONY: check-setup check-setup-local check-setup-remote

View File

@@ -4,15 +4,22 @@
use strict;
use warnings;
use Errno qw(EINPROGRESS);
use Socket qw(:DEFAULT SOCK_NONBLOCK inet_pton);
use Socket qw(:DEFAULT SOCK_NONBLOCK inet_pton SOL_SOCKET SO_KEEPALIVE);
@ARGV == 3
or die "usage: client.pl bind-addr connect-addr connect-port\n";
my ($bindaddr, $connectaddr, $connectport) = @ARGV;
@ARGV == 3 || @ARGV == 4
or die "usage: client.pl bind-addr connect-addr connect-port\n".
" [shutdown|keepalive]\n";
my ($bindaddr, $connectaddr, $connectport, $action) = @ARGV;
$action ||= "";
socket(my $s, PF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0)
or die "socket: $!";
if ($action =~ /keepalive/) {
setsockopt($s, SOL_SOCKET, SO_KEEPALIVE, 1)
or die "setsockopt: $!";
}
my $bindip = inet_pton(AF_INET, $bindaddr)
or die "inet_pton bind-addr $bindaddr";
bind($s, sockaddr_in(0, $bindip))
@@ -26,8 +33,10 @@ connect($s, sockaddr_in($connectport, $connectip)) || $!{EINPROGRESS}
getc();
shutdown($s, SHUT_WR)
or die "shutdown: $!";
if ($action =~ /shutdown/) {
shutdown($s, SHUT_WR)
or die "shutdown: $!";
}
my $timeout = 10;
my $rin = '';

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into CLOSING state an check retransmit of FIN
# transfer peer into CLOSING state and check retransmit of FIN
import os
import threading
@@ -114,6 +114,7 @@ print("Check retransmit of FIN.");
rxmit_fin = sniffer.captured[3]
if rxmit_fin is None:
print("ERROR: No FIN retransmitted from daytime server.")
exit(1)
if rxmit_fin.seq != data.seq+tcplen or rxmit_fin.ack != 3:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in rxmit FIN." % \

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into ESTABLISHED state an check retransmit of data
# transfer peer into ESTABLISHED state and check retransmit of data
import os
import threading
@@ -102,6 +102,7 @@ print("Check retransmit of echo.");
rxmit_echo = sniffer.captured[2]
if rxmit_echo is None:
print("ERROR: No echo retransmitted from echo server.")
exit(1)
if rxmit_echo.seq != synack.seq+1+paylen-1 or rxmit_echo.ack != 2+paylen:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in rxmit echo." % \

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into FIN_WAIT_2 state an check retransmit of FIN
# transfer peer into FIN_WAIT_2 state and check retransmit of FIN
import os
import threading
@@ -120,6 +120,7 @@ print("Check retransmit of FIN.");
rxmit_fin = sniffer.captured[2]
if rxmit_fin is None:
print("ERROR: No FIN retransmitted from daytime server.")
exit(1)
if rxmit_fin.seq != data.seq+tcplen or rxmit_fin.ack != 2:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in rxmit FIN." % \

View File

@@ -0,0 +1,106 @@
#!/usr/local/bin/python3
# transfer peer into ESTABLISHED state and check RST after keepalive
import os
import threading
from addr import *
from scapy.all import *
class Sniff1(threading.Thread):
filter = None
captured = None
packet = None
count = None
timeout = None
def __init__(self, count=1, timeout=3):
self.count = count
self.timeout = timeout
# clear packets buffered by scapy bpf
sniff(iface=LOCAL_IF, timeout=1)
super(Sniff1, self).__init__()
def run(self):
self.captured = sniff(iface=LOCAL_IF, filter=self.filter,
count=self.count, timeout=self.timeout)
if self.captured:
self.packet = self.captured[0]
ip=IP(src=FAKE_NET_ADDR, dst=REMOTE_ADDR)
tport=os.getpid() & 0xffff
print("Start sniffer for SYN packet from peer.");
sniffer = Sniff1(timeout=10)
sniffer.filter = \
"ip and src %s and dst %s and tcp port %u " \
"and tcp[tcpflags] = tcp-syn" % \
(ip.dst, ip.src, tport)
sniffer.start()
time.sleep(1)
print("Connect from remote client.")
os.popen("ssh %s perl %s/client.pl %s %s %u keepalive" % \
(REMOTE_SSH, CURDIR, ip.dst, ip.src, tport), mode='w')
print("Wait for SYN.")
sniffer.join(timeout=5)
syn=sniffer.packet
if syn is None:
print("ERROR: No SYN received from remote client.")
exit(1)
synack=TCP(sport=syn.dport, dport=syn.sport, flags='SA',
seq=1, ack=syn.seq+1, window=(2**16)-1)
ack=sr1(ip/synack, timeout=5)
if ack is None:
print("ERROR: No ACK from remote client received.")
exit(1)
if ack.seq != syn.seq+1 or ack.ack != 2:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d in ACK." % \
(syn.seq+1, 2, ack.seq, ack.ack))
exit(1)
print("Start sniffer for keepalive ACK or RST packet from peer.");
sniffer = Sniff1(count=9, timeout=15)
sniffer.filter = \
"ip and src %s and dst %s and tcp port %u and " \
"( tcp[tcpflags] = tcp-ack|tcp-rst or tcp[tcpflags] = tcp-ack ) " % \
(ip.dst, ip.src, tport)
sniffer.start()
time.sleep(1)
print("Wait for keepalive.")
sniffer.join(timeout=15)
keep_ack=sniffer.packet
if keep_ack is None:
print("ERROR: No keepalive received from remote client.")
exit(1)
print("Send reset to cleanup the connection.")
new_rst=TCP(sport=ack.dport, dport=ack.sport, flags='RA',
seq=ack.ack, ack=ack.seq)
send(ip/new_rst)
print("Check keepalive ACK.");
if str(keep_ack[TCP].flags) != 'A':
print("ERROR: First keepalive is not ACK.")
exit(1)
if keep_ack.seq != ack.seq-1 or keep_ack.ack != 2:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in keepalive ACK." % \
(ack.seq-1, 2, keep_ack.seq, keep_ack.ack))
exit(1)
print("Check keepalive RST.");
keep_rst=sniffer.captured[8]
if keep_rst is None:
print("ERROR: No keepalive RST received from remote client.")
exit(1)
if str(keep_rst[TCP].flags) != 'RA':
print("ERROR: Last keepalive is not RST.")
exit(1)
if keep_rst.seq != ack.seq or keep_rst.ack != 2:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in keepalive RST." % \
(ack.seq, 2, keep_rst.seq, keep_rst.ack))
exit(1)
exit(0)

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into LAST_ACK state an check retransmit of FIN
# transfer peer into LAST_ACK state and check retransmit of FIN
import os
import threading
@@ -94,6 +94,7 @@ print("Check retransmit of FIN.");
rxmit_fin = sniffer.captured[1]
if rxmit_fin is None:
print("ERROR: No FIN retransmitted from discard server.")
exit(1)
if rxmit_fin.seq != synack.seq+1 or rxmit_fin.ack != 3:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in rxmit FIN." % \

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into SYN cache an check retransmit of SYN+ACK
# transfer peer into SYN cache and check retransmit of SYN+ACK
import os
import threading
@@ -73,6 +73,7 @@ print("Check retransmit of SYN+ACK.");
rxmit_synack = sniffer.captured[1]
if rxmit_synack is None:
print("ERROR: No SYN+ACK retransmitted from discard server.")
exit(1)
if rxmit_synack.ack != 2:
print("ERROR: expecting ack %d, got ack %d in rxmit SYN+ACK." % \
(2, rxmit_synack.ack))

View File

@@ -1,6 +1,6 @@
#!/usr/local/bin/python3
# transfer peer from SYN_SENT via SYN_RCVD to FIN_WAIT_1 state
# an check retransmit of FIN
# and check retransmit of FIN
import os
import threading
@@ -38,7 +38,7 @@ sniffer.start()
time.sleep(1)
print("Connect from remote client.")
client=os.popen("ssh %s perl %s/client.pl %s %s %u" % \
client=os.popen("ssh %s perl %s/client.pl %s %s %u shutdown" % \
(REMOTE_SSH, CURDIR, ip.dst, ip.src, tport), mode='w')
print("Wait for SYN.")
@@ -78,7 +78,7 @@ sniffer.filter = \
sniffer.start()
time.sleep(1)
print("Close remote client.")
print("Close remote client to trigger shutdown.")
os.close(client.fileno())
print("Wait for FIN and its retransmit.")
@@ -109,6 +109,7 @@ print("Check retransmit of FIN.");
rxmit_fin = sniffer.captured[1]
if rxmit_fin is None:
print("ERROR: No FIN retransmitted from remote client.")
exit(1)
if rxmit_fin.seq != syn.seq or rxmit_fin.ack != 2:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d in FIN." % \
(syn.seq, 2, rxmit_fin.seq, rxmit_fin.ack))

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer from SYN_SENT to SYN_RCVD state an check retransmit of SYN+ACK
# transfer peer from SYN_SENT to SYN_RCVD state and check retransmit of SYN+ACK
# from LISTEN state SYN_RCVD is cannot be reached as SYN cache handles it
import os
@@ -104,6 +104,7 @@ print("Check retransmit of SYN+ACK.");
rxmit_synack = sniffer.captured[1]
if rxmit_synack is None:
print("ERROR: No SYN+ACK retransmitted from remote client.")
exit(1)
if rxmit_synack.ack != 2:
print("ERROR: expecting ack %d, got ack %d in rxmit SYN+ACK." % \
(2, rxmit_synack.ack))

View File

@@ -1,5 +1,5 @@
#!/usr/local/bin/python3
# transfer peer into SYN_SENT state an check retransmit of SYN
# transfer peer into SYN_SENT state and check retransmit of SYN
import os
import threading
@@ -75,6 +75,7 @@ print("Check retransmit of SYN.");
rxmit_syn = sniffer.captured[1]
if rxmit_syn is None:
print("ERROR: No SYN retransmitted from netstat client.")
exit(1)
if rxmit_syn.seq != syn.seq or rxmit_syn.ack != syn.ack:
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
"in rxmit SYN." % \