summaryrefslogtreecommitdiffstats
path: root/src/net/tcp.c
diff options
context:
space:
mode:
authorMichael Brown2009-06-23 02:29:43 +0200
committerMichael Brown2009-06-23 10:40:26 +0200
commit558c1a45fe30ffe3d6c67b2321e0fc973f13e9b7 (patch)
tree16153ed9464185891dabb5eaee235b318ff672bc /src/net/tcp.c
parent[settings] Fix setting_cmp() to handle nameless settings (diff)
downloadipxe-558c1a45fe30ffe3d6c67b2321e0fc973f13e9b7.tar.gz
ipxe-558c1a45fe30ffe3d6c67b2321e0fc973f13e9b7.tar.xz
ipxe-558c1a45fe30ffe3d6c67b2321e0fc973f13e9b7.zip
[tcp] Improve robustness in the presence of duplicated received packets
gPXE responds to duplicated ACKs with an immediate retransmission, which can lead to a sorceror's apprentice syndrome. It also responds to out-of-range (or old duplicate) ACKs with a RST, which can cause valid connections to be dropped. Fix the sorceror's apprentice syndrome by leaving the retransmission timer running (and so inhibiting the immediate retransmission) when we receive a potential duplicate ACK. This seems to match the behaviour of Linux observed via wireshark traces. Fix the RST issue by sending RST only on out-of-range ACKs that occur before the connection is fully established, as per RFC 793. These problems were exposed during development of the 802.11 wireless link layer; the 802.11 protocol has a failure mode that can easily cause duplicated packets. The fixes were tested in a controlled way by faking large numbers of duplicated packets in the rtl8139 driver. Originally-fixed-by: Joshua Oreman <oremanj@rwcr.net>
Diffstat (limited to 'src/net/tcp.c')
-rw-r--r--src/net/tcp.c41
1 files changed, 28 insertions, 13 deletions
diff --git a/src/net/tcp.c b/src/net/tcp.c
index 1de2abf0..29e233fa 100644
--- a/src/net/tcp.c
+++ b/src/net/tcp.c
@@ -704,30 +704,45 @@ static int tcp_rx_ack ( struct tcp_connection *tcp, uint32_t ack,
size_t len;
unsigned int acked_flags;
- /* Ignore duplicate or out-of-range ACK */
- if ( ack_len > tcp->snd_sent ) {
- DBGC ( tcp, "TCP %p received ACK for [%08x,%08zx), "
- "sent only [%08x,%08x)\n", tcp, tcp->snd_seq,
- ( tcp->snd_seq + ack_len ), tcp->snd_seq,
- ( tcp->snd_seq + tcp->snd_sent ) );
- return -EINVAL;
- }
-
- /* Acknowledge any flags being sent */
+ /* Determine acknowledged flags and data length */
len = ack_len;
acked_flags = ( TCP_FLAGS_SENDING ( tcp->tcp_state ) &
( TCP_SYN | TCP_FIN ) );
if ( acked_flags )
len--;
+ /* Stop retransmission timer if necessary */
+ if ( ack_len == 0 ) {
+ /* Duplicate ACK (or just a packet that isn't
+ * intending to ACK any new data). If the
+ * retransmission timer is running, leave it running
+ * so that we don't immediately retransmit and cause a
+ * sorceror's apprentice syndrome.
+ */
+ } else if ( ack_len <= tcp->snd_sent ) {
+ /* ACK of new data. Stop the retransmission timer. */
+ stop_timer ( &tcp->timer );
+ } else {
+ /* Out-of-range (or old duplicate) ACK. Leave the
+ * timer running, as for the ack_len==0 case, to
+ * handle old duplicate ACKs.
+ */
+ DBGC ( tcp, "TCP %p received ACK for [%08x,%08zx), "
+ "sent only [%08x,%08x)\n", tcp, tcp->snd_seq,
+ ( tcp->snd_seq + ack_len ), tcp->snd_seq,
+ ( tcp->snd_seq + tcp->snd_sent ) );
+ /* Send RST if an out-of-range ACK is received on a
+ * not-yet-established connection.
+ */
+ if ( ! TCP_HAS_BEEN_ESTABLISHED ( tcp->tcp_state ) )
+ return -EINVAL;
+ }
+
/* Update SEQ and sent counters, and window size */
tcp->snd_seq = ack;
tcp->snd_sent = 0;
tcp->snd_win = win;
- /* Stop the retransmission timer */
- stop_timer ( &tcp->timer );
-
/* Remove any acknowledged data from transmit queue */
tcp_process_queue ( tcp, len, NULL, 1 );