TCP timer bug in 2.0.x

Matthew Ghio (ghio@temp0086.myriad.ml.org)
Sat, 31 May 1997 00:14:28 -0700


There's an annoying bug in the TCP driver up thru 2.0.30 which can cause
ftp sessions to hang. The general symptom is an ftp download that starts
out fast, and gets slower, and slower, and slower until it's basically
stuck. This only affects outgoing sends, so you might not notice
yourself, but if you have a web/ftp server, your users likely will.

The problem is in the retransmit timer backoff. The timer is set based
on the roundtrip time when a packet is transmitted/acked. When a sent
packet is not acked, it will retransmit, doubling the time between each
retransmit, up to a maximum of 120 seconds.

The bug is that when a retransmitted packet gets acked, the timer doesn't
get reset. So if the timer was at 120 seconds, then we'll have to wait a
whole 120 seconds before sending the next packet, no matter how quickly
the packet was acked.

Unfortulately, it's even worse than that. The retransmit flag will never
get cleared as long as there's data in the send queue. This means that if
the ftpd keeps the pipe full, the timer will never get reset, and your ftp
session will be slowed to a rate of one packet every two minutes. Chances
are, you aren't willing to wait that long.

(If you want to test this, start a file transfer to somewhere then type
route add somewhere dummy, wait for a few packets to get lost, then
correct the routing, and notice how painfully slow your ftp transfer goes
afterwards.)

I'm not quite sure what is the best way to fix this. I patched my kernel
so that whenever an ack comes in, it resets the timer to 200ms. This
does solve the problem, but probably causes a lot of unnecessary
retransmits. It would be better to calculate a timeout based on roundtrip
time, but that won't work if you have to retransmit the packet.

--- linux/net/ipv4/tcp_input.c.orig Mon Apr 21 14:53:17 1997
+++ linux/net/ipv4/tcp_input.c Mon Apr 21 14:54:15 1997
@@ -1654,6 +1654,9 @@

if (sk->send_head != NULL && (flag&2) && sk->retransmits)
{
+ sk->rto = HZ/5; /* 200 ms */
+ tcp_reset_xmit_timer(sk, TIME_WRITE, sk->rto);
+
tcp_do_retransmit(sk, 1);
}