summaryrefslogtreecommitdiffstats
path: root/src/net/tcp.c
diff options
context:
space:
mode:
authorMichael Brown2009-06-23 17:10:34 +0200
committerMichael Brown2009-06-23 17:10:34 +0200
commit58f60df66c074eec1756173ba0354c1b6a95f0e6 (patch)
treee1c753f1fd58606bf5e8064830bfdc3482543be2 /src/net/tcp.c
parent[tcp] Attempt to catch all possible error cases with debug messages (diff)
downloadipxe-58f60df66c074eec1756173ba0354c1b6a95f0e6.tar.gz
ipxe-58f60df66c074eec1756173ba0354c1b6a95f0e6.tar.xz
ipxe-58f60df66c074eec1756173ba0354c1b6a95f0e6.zip
[tcp] Avoid rewinding sequence numbers on receiving old duplicate ACKs
Commit 558c1a4 ("[tcp] Improve robustness in the presence of duplicated received packets") introduced a regression in that an old duplicate ACK received while in the ESTABLISHED state would pass through normal ACK processing, including updating tcp->snd_seq. Fix by ensuring that ACK processing ignores all duplicate ACKs.
Diffstat (limited to 'src/net/tcp.c')
-rw-r--r--src/net/tcp.c58
1 files changed, 31 insertions, 27 deletions
diff --git a/src/net/tcp.c b/src/net/tcp.c
index 5a64c83f..e86cdf8a 100644
--- a/src/net/tcp.c
+++ b/src/net/tcp.c
@@ -726,40 +726,44 @@ static int tcp_rx_ack ( struct tcp_connection *tcp, uint32_t ack,
size_t len;
unsigned int acked_flags;
- /* 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.
- */
+ /* Check for out-of-range or old duplicate ACKs */
+ 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 ) );
- /* Send RST if an out-of-range ACK is received on a
- * not-yet-established connection.
- */
- if ( ! TCP_HAS_BEEN_ESTABLISHED ( tcp->tcp_state ) )
+
+ if ( TCP_HAS_BEEN_ESTABLISHED ( tcp->tcp_state ) ) {
+ /* Just ignore what might be old duplicate ACKs */
+ return 0;
+ } else {
+ /* Send RST if an out-of-range ACK is received
+ * on a not-yet-established connection, as per
+ * RFC 793.
+ */
return -EINVAL;
+ }
}
+ /* Ignore ACKs that don't actually acknowledge any new data.
+ * (In particular, do not stop the retransmission timer; this
+ * avoids creating a sorceror's apprentice syndrome when a
+ * duplicate ACK is received and we still have data in our
+ * transmit queue.)
+ */
+ if ( ack_len == 0 )
+ return 0;
+
+ /* Stop the retransmission timer */
+ stop_timer ( &tcp->timer );
+
+ /* 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--;
+
/* Update SEQ and sent counters, and window size */
tcp->snd_seq = ack;
tcp->snd_sent = 0;