kern/93236: [PATCH] Received out of window SYN in ESTABLISHED
connection isn't ACKed as required by RFC793
Grant Edwards
grante at visi.com
Sun Feb 12 09:10:05 PST 2006
>Number: 93236
>Category: kern
>Synopsis: [PATCH] Received out of window SYN in ESTABLISHED connection isn't ACKed as required by RFC793
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Sun Feb 12 17:10:03 GMT 2006
>Closed-Date:
>Last-Modified:
>Originator: Grant Edwards
>Release: None
>Organization:
>Environment:
I'm using just the TCP/IP stack.
>Description:
Problem Summary
When the FreeBSD stack receives an out-of-window SYN for an
"ESTABLISHED" connection, the SYN is ignored rather that
ACKed as required by RFC793. FWIW, Linux and NetBSD both
follow the RFC.
Details
-------
After switching from the NetBSD TCP/IP stack to the FreeBSD
TCP/IP stack we've run into a problem which is causing
customers to complain.
Here's the scenario:
1) Host opens TCP connection to device under test (DUT) which
is running either FreeBSD or NetBSD TCP/IP stack.
2) Somebody pushes the reset button on the host.
3) The host reboots and attempts to open a TCP connection to
the DUT _using_the_same_source_port_ (and the same
destination port).
4a) When the NetBSD stack received a SYN for an already-open
connection, it sent an ACK with the sequence number from
the old connection. The host sees this and sent a RST
(which drops the connection), then the HOST attempted to
re-open the connection (which succeeds). All is good.
4b) When the FreeBSD stack receives a SYN for an already-open
connection, it just ignores it. So, after a timeout, the
host sends it again. Again it's ignored. This goes on
until the Host times out the connection operation and the
system fails.
RFC793 page 34 describes this scenario exactly and requires the
behavior of the Linux and NetBSD stacks. According to the
RFC's sample implimentation on pages 58 though 84, when you
receive a SYN in the ESTABLISHED state you either
1) Send a RST if the sequence number is inside the current
receive window window (p71).
2) Send an ACK if the sequence number is outside the current
receive window (p69,71).
Ignoring the SYN isn't allowed.
Here's the relevent code from the current version of tcp_input.c:
At one point it is determined (correctly) that the sequence
number in the SYN is outside the receive window. This means an
ACK is required, so the ACKNOW flag is set at line 1645:
1629 /*
1630 * Following if statement from Stevens, vol. 2, p. 960.
1631 */
1632 if (todrop > tlen
1633 || (todrop == tlen && (thflags & TH_FIN) == 0)) {
1634 /*
1635 * Any valid FIN must be to the left of the window.
1636 * At this point the FIN must be a duplicate or out
1637 * of sequence; drop it.
1638 */
1639 thflags &= ~TH_FIN;
1640
1641 /*
1642 * Send an ACK to resynchronize and drop any data.
1643 * But keep on processing for RST or ACK.
1644 */
1645 tp->t_flags |= TF_ACKNOW;
1646 todrop = tlen;
1647 tcpstat.tcps_rcvduppack++;
1648 tcpstat.tcps_rcvdupbyte += todrop;
1649 } else {
1650 tcpstat.tcps_rcvpartduppack++;
1651 tcpstat.tcps_rcvpartdupbyte += todrop;
1652 }
1653 drop_hdrlen += todrop; /* drop from the top afterwards */
1654 th->th_seq += todrop;
1655 tlen -= todrop;
1656 if (th->th_urp > todrop)
1657 th->th_urp -= todrop;
1658 else {
1659 thflags &= ~TH_URG;
1660 th->th_urp = 0;
1661 }
1662 }
Now we rattle on down through the tcp_input fuction for a while
and end up jumping to "drop" at line 1769:
1759 /*
1760 * If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN
1761 * flag is on (half-synchronized state), then queue data for
1762 * later processing; else drop segment and return.
1763 */
1764 if ((thflags & TH_ACK) == 0) {
1765 if (tp->t_state == TCPS_SYN_RECEIVED ||
1766 (tp->t_flags & TF_NEEDSYN))
1767 goto step6;
1768 else
1769 goto drop;
1770 }
The code at "drop:" cleans up a little and returns without ever
calling tcp_output(tp) to actually send the ACK that's required
by the RFC and was requested by the setting of the TF_ACKNOW bit.
2549 drop:
2550 /*
2551 * Drop space held by incoming segment and return.
2552 */
2553 #ifdef TCPDEBUG
2554 if (tp == 0 || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG))
2555 tcp_trace(TA_DROP, ostate, tp, (void *)tcp_saveipgen,
2556 &tcp_savetcp, 0);
2557 #endif
2558 if (tp)
2559 INP_UNLOCK(inp);
2560 if (headlocked)
2561 INP_INFO_WUNLOCK(&tcbinfo);
2562 m_freem(m);
2563 return;
So, the ACK that's required by the TCP RFC is never sent (the
SYN packet is just ignored): the host just sits there and
sends SYNs.
>How-To-Repeat:
1) Open TCP connection from host A to FreeBSD network stack on host B.
2) Press reset button on host A
3) After host A has rebooted, attempt to open TCP connection from host A
to host B using same source/dest ports as step 1 above.
4) SYNs sent to attempt to "re-open" the connection will be ignored.
>Fix:
This bug was fixed in the NetBSD stack at tcp_input.c version 1.78 with the
CVS log entry below:
"Ensure that out of window SYNs receive an ACK in responce,
rather than being dropped. This fixes a bug reported by
Jason Thorpe."
Here's the NetBSD tcp_input.c diff that corresponds to that fix:
===================================================================
RCS file: /ftp/cvs/cvsroot/src/sys/netinet/tcp_input.c,v
retrieving revision 1.77
retrieving revision 1.78
diff -u -p -r1.77 -r1.78
--- src/sys/netinet/tcp_input.c 1999/02/05 22:37:24 1.77
+++ src/sys/netinet/tcp_input.c 1999/04/09 22:01:07 1.78
@@ -1,4 +1,4 @@
-/* $NetBSD: tcp_input.c,v 1.77 1999/02/05 22:37:24 matt Exp $ */
+/* $NetBSD: tcp_input.c,v 1.78 1999/04/09 22:01:07 kml Exp $ */
/*-
* Copyright (c) 1997, 1998, 1999 The NetBSD Foundation, Inc.
@@ -1104,8 +1104,12 @@ after_listen:
/*
* If the ACK bit is off we drop the segment and return.
*/
- if ((tiflags & TH_ACK) == 0)
- goto drop;
+ if ((tiflags & TH_ACK) == 0) {
+ if (tp->t_flags & TF_ACKNOW)
+ goto dropafterack;
+ else
+ goto drop;
+ }
/*
* Ack processing.
===================================================================
I've got a patch for the current FreeBSD tcp_input.c, but I don't see any way to attach it as a file, so I'll paste it in here:
--- tcp_input.c-orig 2006-02-12 10:24:35.988707395 -0600
+++ tcp_input.c 2006-02-12 10:25:28.877515703 -0600
@@ -1758,20 +1758,22 @@
/*
* If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN
* flag is on (half-synchronized state), then queue data for
* later processing; else drop segment and return.
*/
if ((thflags & TH_ACK) == 0) {
if (tp->t_state == TCPS_SYN_RECEIVED ||
(tp->t_flags & TF_NEEDSYN))
goto step6;
+ else if (tp->t_flags & TF_ACKNOW)
+ goto dropafterack;
else
goto drop;
}
/*
* Ack processing.
*/
switch (tp->t_state) {
/*
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list