Skip to content

Commit 4c5c440

Browse files
committed
Check that SYN+ACK packets are retransmitted in SYN_RCVD state.
1 parent 2fcf8ac commit 4c5c440

File tree

8 files changed

+148
-22
lines changed

8 files changed

+148
-22
lines changed

regress/sys/netinet/tcpstate/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $OpenBSD: Makefile,v 1.3 2025/08/01 11:10:00 bluhm Exp $
1+
# $OpenBSD: Makefile,v 1.4 2025/08/01 21:58:48 bluhm Exp $
22

33
# Copyright (c) 2025 Alexander Bluhm <[email protected]>
44
#
@@ -111,7 +111,7 @@ run-$t: $t.py addr.py
111111
${SUDO} ${PYTHON}$t.py
112112
.endfor
113113

114-
.for t in CLOSING ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 LAST_ACK SYN_SENT
114+
.for t in CLOSING ESTABLISHED FIN_WAIT_1 FIN_WAIT_2 LAST_ACK SYN_RCVD SYN_SENT
115115
REGRESS_TARGETS += run-netstat-${t:L:S/_//g}
116116
netstat-${t:L:S/_//g}.log:
117117
${MAKE} -C ${.CURDIR} run-tcp_${t:L:S/_//g:C/[12]//}

regress/sys/netinet/tcpstate/README

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
These regress tests use a local and remote machine. On remote
22
machine inetd is providing services. Local machine runs scapy to
33
navigate the remote TCP stack through the TCP state diagram.
4+
For active connections netcat is started on remote machine.
5+
6+
tcp_closing FIN packets are retransmitted in CLOSING state.
7+
tcp_established data packets are retransmitted in ESTABLISHED state.
8+
tcp_finwait FIN retransmitted in FIN_WAIT_1 and goto FIN_WAIT_2 state.
9+
tcp_lastack FIN packets are retransmitted in LAST_ACK state.
10+
tcp_syncache SYN+ACK packets are retransmitted by SYN cache.
11+
tcp_synrcvd SYN+ACK retransmitted in SYN_RCVD after SYN_SENT state.
12+
tcp_synsent SYN packets are retransmitted in SYN_SENT state.

regress/sys/netinet/tcpstate/tcp_closing.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ def run(self):
8989
(recv_fin.getlayer(TCP).flags))
9090
exit(1)
9191
if recv_ack.seq != recv_fin.seq or recv_ack.ack != 3:
92-
print("ERROR: expecting seq %d ack %d, " \
93-
"got seq %d ack %d in recv ack" % \
92+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
93+
"in recv ack" % \
9494
(recv_fin.seq, 3, recv_ack.seq, recv_ack.ack))
9595
exit(1)
9696

@@ -115,8 +115,8 @@ def run(self):
115115
if rxmit_fin is None:
116116
print("ERROR: No FIN retransmitted from daytime server.")
117117
if rxmit_fin.seq != data.seq+tcplen or rxmit_fin.ack != 3:
118-
print("ERROR: expecting seq %d ack %d, " \
119-
"got seq %d ack %d in rxmit FIN" % \
118+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
119+
"in rxmit FIN" % \
120120
(data.seq+tcplen, 3, rxmit_fin.seq, rxmit_fin.ack))
121121
exit(1)
122122

regress/sys/netinet/tcpstate/tcp_established.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ def run(self):
103103
if rxmit_echo is None:
104104
print("ERROR: No echo retransmitted from echo server.")
105105
if rxmit_echo.seq != synack.seq+1+paylen-1 or rxmit_echo.ack != 2+paylen:
106-
print("ERROR: expecting seq %d ack %d, " \
107-
"got seq %d ack %d in rxmit echo" % \
106+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
107+
"in rxmit echo" % \
108108
(synack.seq+1+paylen-1, 2+paylen, rxmit_echo.seq, rxmit_echo.ack))
109109
exit(1)
110110

regress/sys/netinet/tcpstate/tcp_finwait.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def run(self):
7272
(recv_fin.getlayer(TCP).flags))
7373
exit(1)
7474
if recv_fin.seq != data.seq+tcplen or recv_fin.ack != 2:
75-
print("ERROR: expecting seq %d ack %d, " \
76-
"got seq %d ack %d in recv fin" % \
75+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
76+
"in recv fin" % \
7777
(data.seq+tcplen, 2, recv_fin.seq, recv_fin.ack))
7878
exit(1)
7979

@@ -121,8 +121,8 @@ def run(self):
121121
if rxmit_fin is None:
122122
print("ERROR: No FIN retransmitted from daytime server.")
123123
if rxmit_fin.seq != data.seq+tcplen or rxmit_fin.ack != 2:
124-
print("ERROR: expecting seq %d ack %d, " \
125-
"got seq %d ack %d in rxmit FIN" % \
124+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
125+
"in rxmit FIN" % \
126126
(data.seq+tcplen, 2, rxmit_fin.seq, rxmit_fin.ack))
127127
exit(1)
128128

regress/sys/netinet/tcpstate/tcp_lastack.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def run(self):
7272
print("ERROR: No FIN received from discard server.")
7373
exit(1)
7474
if recv_fin.seq != synack.seq+1 or recv_fin.ack != 3:
75-
print("ERROR: expecting seq %d ack %d, " \
76-
"got seq %d ack %d in recv fin" % \
75+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
76+
"in recv fin" % \
7777
(synack.seq+1, 3, recv_fin.seq, recv_fin.ack))
7878
exit(1)
7979

@@ -95,8 +95,8 @@ def run(self):
9595
if rxmit_fin is None:
9696
print("ERROR: No FIN retransmitted from discard server.")
9797
if rxmit_fin.seq != synack.seq+1 or rxmit_fin.ack != 3:
98-
print("ERROR: expecting seq %d ack %d, " \
99-
"got seq %d ack %d in rxmit FIN" % \
98+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
99+
"in rxmit FIN" % \
100100
(synack.seq+1, 3, rxmit_fin.seq, rxmit_fin.ack))
101101
exit(1)
102102

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/local/bin/python3
2+
# transfer peer from SYN_SENT to SYN_RCVD state an check retransmit of SYN+ACK
3+
# from LISTEN state SYN_RCVD is cannot be reached as SYN cache handles it
4+
5+
import os
6+
import threading
7+
from addr import *
8+
from scapy.all import *
9+
10+
class Sniff1(threading.Thread):
11+
filter = None
12+
captured = None
13+
packet = None
14+
count = None
15+
timeout = None
16+
def __init__(self, count=1, timeout=3):
17+
self.count = count
18+
self.timeout = timeout
19+
# clear packets buffered by scapy bpf
20+
sniff(iface=LOCAL_IF, timeout=1)
21+
super(Sniff1, self).__init__()
22+
def run(self):
23+
self.captured = sniff(iface=LOCAL_IF, filter=self.filter,
24+
count=self.count, timeout=self.timeout)
25+
if self.captured:
26+
self.packet = self.captured[0]
27+
28+
ip=IP(src=FAKE_NET_ADDR, dst=REMOTE_ADDR)
29+
tport=os.getpid() & 0xffff
30+
31+
print("Start sniffer for SYN packet from peer");
32+
sniffer = Sniff1(timeout=10)
33+
sniffer.filter = \
34+
"ip and src %s and dst %s and tcp port %u " \
35+
"and tcp[tcpflags] = tcp-syn" % \
36+
(ip.dst, ip.src, tport)
37+
sniffer.start()
38+
time.sleep(1)
39+
40+
print("Connect netcat")
41+
nc=os.popen("ssh %s nc -4Nn -s %s %s %u" % (REMOTE_SSH, ip.dst, ip.src, tport),
42+
mode='w')
43+
44+
print("Wait for SYN.")
45+
sniffer.join(timeout=10)
46+
syn=sniffer.packet
47+
if syn is None:
48+
print("ERROR: No SYN received from netcat client.")
49+
exit(1)
50+
51+
print("Start sniffer for SYN+ACK packet from peer");
52+
sniffer = Sniff1(count=2, timeout=10)
53+
sniffer.filter = \
54+
"ip and src %s and dst %s and tcp port %u " \
55+
"and tcp[tcpflags] = tcp-syn|tcp-ack" % \
56+
(ip.dst, ip.src, tport)
57+
sniffer.start()
58+
time.sleep(1)
59+
60+
print("Send SYN packet, receive SYN+ACK.")
61+
synsyn=TCP(sport=syn.dport, dport=syn.sport, flags='S',
62+
seq=1, ack=syn.seq+1, window=(2**16)-1)
63+
send(ip/synsyn)
64+
65+
print("Wait for SYN+ACK and its retransmit.")
66+
sniffer.join(timeout=10)
67+
synack=sniffer.packet
68+
if synack is None:
69+
print("ERROR: No SYN+ACK from netcat client received.")
70+
exit(1)
71+
if synack.seq != syn.seq or synack.ack != 2:
72+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
73+
"in SYN+ACK" % \
74+
(syn.seq, 2, synack.seq, synack.ack))
75+
exit(1)
76+
77+
print("Check peer is in SYN_RCVD state.")
78+
with os.popen("ssh "+REMOTE_SSH+" netstat -vnp tcp") as netstat:
79+
with open("netstat-synrcvd.log", 'w') as log:
80+
for line in netstat:
81+
if "%s.%d" % (FAKE_NET_ADDR, tport) in line:
82+
print(line)
83+
log.write(line)
84+
85+
print("Send ACK packet to finish handshake.")
86+
ack=TCP(sport=synack.dport, dport=synack.sport, flags='A',
87+
seq=2, ack=synack.seq+1, window=(2**16)-1)
88+
send(ip/ack)
89+
90+
print("Check peer is in ESTABLISHED state.")
91+
with os.popen("ssh "+REMOTE_SSH+" netstat -vnp tcp") as netstat:
92+
with open("netstat-synrcvd.log", 'a') as log:
93+
for line in netstat:
94+
if "%s.%d" % (FAKE_NET_ADDR, tport) in line:
95+
print(line)
96+
log.write(line)
97+
98+
print("Send reset to cleanup the connection")
99+
new_rst=TCP(sport=synack.dport, dport=synack.sport, flags='RA',
100+
seq=ack.seq, ack=ack.ack)
101+
send(ip/new_rst)
102+
103+
print("Check retransmit of SYN+ACK");
104+
rxmit_synack = sniffer.captured[1]
105+
if rxmit_synack is None:
106+
print("ERROR: No SYN+ACK retransmitted from netcat client.")
107+
if rxmit_synack.ack != 2:
108+
print("ERROR: expecting ack %d, got ack %d in rxmit SYN+ACK" % \
109+
(2, rxmit_synack.ack))
110+
exit(1)
111+
if rxmit_synack.seq != syn.seq or rxmit_synack.ack != 2:
112+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
113+
"in SYN+ACK" % \
114+
(syn.seq, 2, rxmit_synack.seq, rxmit_synack.ack))
115+
exit(1)
116+
117+
exit(0);

regress/sys/netinet/tcpstate/tcp_synsent.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def run(self):
3737
time.sleep(1)
3838

3939
print("Connect netcat")
40-
os.popen("ssh %s nc -4n -s %s %s %u" % (REMOTE_SSH, ip.dst, ip.src, tport))
40+
nc=os.popen("ssh %s nc -4Nn -s %s %s %u" % (REMOTE_SSH, ip.dst, ip.src, tport),
41+
mode='w')
4142

4243
print("Wait for SYN and its retransmit.")
4344
sniffer.join(timeout=10)
@@ -58,11 +59,10 @@ def run(self):
5859
seq=1, ack=syn.seq+1, window=(2**16)-1)
5960
ack=sr1(ip/synack, timeout=5)
6061
if ack is None:
61-
print("ERROR: No 3-way handshake ACK from netcat client received.")
62+
print("ERROR: No ACK from netcat client received.")
6263
exit(1)
6364
if ack.seq != syn.seq+1 or ack.ack != 2:
64-
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
65-
"in 3-way handshake ACK" % \
65+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d in ACK" % \
6666
(syn.seq+1, 2, ack.seq, ack.ack))
6767
exit(1)
6868

@@ -76,8 +76,8 @@ def run(self):
7676
if rxmit_syn is None:
7777
print("ERROR: No SYN retransmitted from netstat client.")
7878
if rxmit_syn.seq != syn.seq or rxmit_syn.ack != syn.ack:
79-
print("ERROR: expecting seq %d ack %d, " \
80-
"got seq %d ack %d in rxmit SYN" % \
79+
print("ERROR: expecting seq %d ack %d, got seq %d ack %d " \
80+
"in rxmit SYN" % \
8181
(syn.seq, syn.ack, rxmit_syn.seq, rxmit_syn.ack))
8282
exit(1)
8383

0 commit comments

Comments
 (0)