From 5f7749078e1cf1c7ea9fa091a1c1f1d94ec37ce2 Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Sun, 2 Aug 2015 18:29:29 +0200 Subject: [PATCH] tcp: Fix retransmit logic to avoid lots of spurious retransmits. The retransmit timer was only stopped when all in flight data was acknowledged and never updated on individual acknowledgements. This caused a lot of erroneous retransmits whenever the buffer was filled fast enough so that the acknowledgements never caught up, i.e. whenever uploading or streaming data. Move setting of the initial retransmit timer inside the send loop so it is closer to the actual time the segment is sent out and simplify the logic a bit. Limit the minimal retransmit timeout to 200 msecs to avoid spurious retransmit in the face of delayed acknowledgements. This is lower than the 1 second minimum the RFCs suggest. Other stacks use various other sub-second timeouts, the 200 msecs follows what Linux does. Also add the exponential back off of the retransmit timeout when retransmits are triggered. This is bounded by a 60 seconds maximum according to RFC6298. --- .../network/protocols/tcp/TCPEndpoint.cpp | 69 ++++++++++++------- .../kernel/network/protocols/tcp/tcp.h | 5 ++ 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp b/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp index 8204458499..4fab61628e 100644 --- a/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/TCPEndpoint.cpp @@ -1953,7 +1953,8 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow) } uint32 length = min_c(fSendQueue.Available(fSendNext), sendWindow); - tcp_sequence previousSendNext = fSendNext; + bool shouldStartRetransmitTimer = fSendNext == fSendUnacknowledged; + bool retransmit = fSendNext < fSendMax; do { uint32 segmentMaxSize = fSendMaxSegmentSize @@ -1968,7 +1969,7 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow) } // Determine if we should really send this segment - if (!force && !_ShouldSendSegment(segment, segmentLength, + if (!force && !retransmit && !_ShouldSendSegment(segment, segmentLength, segmentMaxSize, flightSize)) { if (fSendQueue.Available() && !gStackModule->is_timer_active(&fPersistTimer) @@ -2047,24 +2048,25 @@ TCPEndpoint::_SendQueued(bool force, uint32 sendWindow) return status; } + if (shouldStartRetransmitTimer && size > 0) { + TRACE("starting initial retransmit timer of: %" B_PRIdBIGTIME, + fRetransmitTimeout); + gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout); + shouldStartRetransmitTimer = false; + } + if (segment.flags & TCP_FLAG_ACKNOWLEDGE) fLastAcknowledgeSent = segment.acknowledge; length -= segmentLength; segment.flags &= ~(TCP_FLAG_SYNCHRONIZE | TCP_FLAG_RESET | TCP_FLAG_FINISH); + + if (retransmit) + break; + } while (length > 0); - // if we sent data from the beggining of the send queue, - // start the retransmition timer - if (previousSendNext == fSendUnacknowledged - && fSendNext > previousSendNext) { - TRACE(" SendQueue(): set retransmit timer with rto %" B_PRIdBIGTIME, - fRetransmitTimeout); - - gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout); - } - return B_OK; } @@ -2120,24 +2122,34 @@ TCPEndpoint::_PrepareSendPath(const sockaddr* peer) void TCPEndpoint::_Acknowledged(tcp_segment_header& segment) { - size_t previouslyUsed = fSendQueue.Used(); + TRACE("_Acknowledged(): ack %" B_PRIu32 "; uack %" B_PRIu32 "; next %" + B_PRIu32 "; max %" B_PRIu32, segment.acknowledge, + fSendUnacknowledged.Number(), fSendNext.Number(), fSendMax.Number()); - fSendQueue.RemoveUntil(segment.acknowledge); - fSendUnacknowledged = segment.acknowledge; + ASSERT(fSendUnacknowledged <= segment.acknowledge); - if (fSendNext < fSendUnacknowledged) - fSendNext = fSendUnacknowledged; - - if (fSendUnacknowledged == fSendMax) - gStackModule->cancel_timer(&fRetransmitTimer); - - if (fSendQueue.Used() < previouslyUsed) { - // this ACK acknowledged data + if (fSendUnacknowledged < segment.acknowledge) { + fSendQueue.RemoveUntil(segment.acknowledge); + fSendUnacknowledged = segment.acknowledge; + if (fSendNext < fSendUnacknowledged) + fSendNext = fSendUnacknowledged; if (segment.options & TCP_HAS_TIMESTAMPS) _UpdateRoundTripTime(tcp_diff_timestamp(segment.timestamp_reply)); else { - // TODO: Fallback to RFC 793 type estimation + // TODO: Fallback to RFC 793 type estimation; This just resets + // any potential exponential back off that happened due to + // retransmits. + fRetransmitTimeout = TCP_INITIAL_RTT; + } + + if (fSendUnacknowledged == fSendMax) { + TRACE("all acknowledged, cancelling retransmission timer"); + gStackModule->cancel_timer(&fRetransmitTimer); + } else { + TRACE("data acknowledged, resetting retransmission timer to: %" + B_PRIdBIGTIME, fRetransmitTimeout); + gStackModule->set_timer(&fRetransmitTimer, fRetransmitTimeout); } if (is_writable(fState)) { @@ -2171,8 +2183,15 @@ void TCPEndpoint::_Retransmit() { TRACE("Retransmit()"); + _ResetSlowStart(); fSendNext = fSendUnacknowledged; + + // Do exponential back off of the retransmit timeout + fRetransmitTimeout *= 2; + if (fRetransmitTimeout > TCP_MAX_RETRANSMIT_TIMEOUT) + fRetransmitTimeout = TCP_MAX_RETRANSMIT_TIMEOUT; + _SendQueued(); } @@ -2192,6 +2211,8 @@ TCPEndpoint::_UpdateRoundTripTime(int32 roundTripTime) fRetransmitTimeout = ((fRoundTripTime / 4 + fRoundTripDeviation) / 2) * kTimestampFactor; + if (fRetransmitTimeout < TCP_MIN_RETRANSMIT_TIMEOUT) + fRetransmitTimeout = TCP_MIN_RETRANSMIT_TIMEOUT; TRACE(" RTO is now %" B_PRIdBIGTIME " (after rtt %" B_PRId32 "ms)", fRetransmitTimeout, roundTripTime); diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.h b/src/add-ons/kernel/network/protocols/tcp/tcp.h index 64e13acca0..07d4423022 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.h +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.h @@ -186,6 +186,11 @@ operator==(tcp_sequence a, tcp_sequence b) #define TCP_MAX_WINDOW 65535 #define TCP_MAX_SEGMENT_LIFETIME 60000000 // 60 secs +// Minimum retransmit timeout (consider delayed ack) +#define TCP_MIN_RETRANSMIT_TIMEOUT 200000 // 200 msecs +// Maximum retransmit timeout (per RFC6298) +#define TCP_MAX_RETRANSMIT_TIMEOUT 60000000 // 60 secs + struct tcp_sack { uint32 left_edge; uint32 right_edge;