PATCH for 2.1.54/55: "tcp_retrans_try_collapse" can collapse a FIN out of existence

Kevin Buhr (buhr@stat.wisc.edu)
19 Sep 1997 18:50:30 -0500


Linus, Dave, and Eric:

In the "obscure bugs" department, I think I've finally figured out
what was causing a growing number of "CLOSING" sockets to hang around
indefinitely on me.

If the Linux side closes the connection, sends a FIN packet, but has
to retransmit unacknowledged earlier data, "tcp_retrans_try_collapse"
(in "linux/net/ipv4/tcp_output.c") may try to collapse the FIN packet
into the previous packet. The existing code in 2.1.54/55 (or, for
that matter, the 2.1.56 pre-patch) doesn't handle this correctly, so
the FIN packet is forever lost, and the socket remains stuck in the
CLOSING state, since it never retransmits the FIN and never gets a
final ACK.

The enclosed patch, against 2.1.54 (or 2.1.55), appears to fix this
problem.

I was able to simulate this pathological scenario with some cable
pulling (verifying, with the help of some extra debugging output, that
I actually had size2==0, buff->end_seq - buff->seq == 1, th1->fin==0,
and th2->fin==1), and, with this patch in place, "tcpdump" revealed
what appeared to be correct behaviour.

Kevin <buhr@stat.wisc.edu>

* * *

--- linux/net/ipv4/tcp_output.c.orig Fri Sep 19 18:28:16 1997
+++ linux/net/ipv4/tcp_output.c Fri Sep 19 18:27:06 1997
@@ -605,11 +605,14 @@
memcpy(skb_put(skb, size2), ((char *) th2) + (th2->doff << 2), size2);

/* Update sizes on original skb, both TCP and IP. */
- skb->end_seq += size2;
+ skb->end_seq += buff->end_seq - buff->seq;
if (th2->urg) {
th1->urg = 1;
th1->urg_ptr = th2->urg_ptr + size1;
}
+ if (th2->fin) {
+ th1->fin = 1;
+ }

/* ... and off you go. */
kfree_skb(buff, FREE_WRITE);