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:
@@ -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
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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." % \
|
||||
|
||||
@@ -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." % \
|
||||
|
||||
@@ -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." % \
|
||||
|
||||
106
regress/sys/netinet/tcpstate/tcp_keepalive.py
Executable file
106
regress/sys/netinet/tcpstate/tcp_keepalive.py
Executable 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)
|
||||
@@ -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." % \
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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." % \
|
||||
|
||||
Reference in New Issue
Block a user