summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/transport/tcp/snd.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/tcpip/transport/tcp/snd.go')
-rw-r--r--pkg/tcpip/transport/tcp/snd.go51
1 files changed, 49 insertions, 2 deletions
diff --git a/pkg/tcpip/transport/tcp/snd.go b/pkg/tcpip/transport/tcp/snd.go
index 5c9e76fa1..8312ae077 100644
--- a/pkg/tcpip/transport/tcp/snd.go
+++ b/pkg/tcpip/transport/tcp/snd.go
@@ -199,10 +199,12 @@ func newSender(ep *endpoint, iss, irs seqnum.Value, sndWnd seqnum.Size, mss uint
s.sndWndScale = uint8(sndWndScale)
}
- s.updateMaxPayloadSize(int(ep.route.MTU()), 0)
-
+ // Initialize SACK Scoreboard.
+ s.ep.scoreboard = NewSACKScoreboard(mss, iss)
s.resendTimer.init(&s.resendWaker)
+ s.updateMaxPayloadSize(int(ep.route.MTU()), 0)
+
return s
}
@@ -380,6 +382,21 @@ func (s *sender) retransmitTimerExpired() bool {
// We'll keep on transmitting (or retransmitting) as we get acks for
// the data we transmit.
s.outstanding = 0
+
+ // Expunge all SACK information as per https://tools.ietf.org/html/rfc6675#section-5.1
+ //
+ // In order to avoid memory deadlocks, the TCP receiver is allowed to
+ // discard data that has already been selectively acknowledged. As a
+ // result, [RFC2018] suggests that a TCP sender SHOULD expunge the SACK
+ // information gathered from a receiver upon a retransmission timeout
+ // (RTO) "since the timeout might indicate that the data receiver has
+ // reneged." Additionally, a TCP sender MUST "ignore prior SACK
+ // information in determining which data to retransmit."
+ //
+ // NOTE: We take the stricter interpretation and just expunge all
+ // information as we lack more rigorous checks to validate if the SACK
+ // information is usable after an RTO.
+ s.ep.scoreboard.Reset()
s.writeNext = s.writeList.Front()
s.sendData()
@@ -550,6 +567,10 @@ func (s *sender) leaveFastRecovery() {
// Deflate cwnd. It had been artificially inflated when new dups arrived.
s.sndCwnd = s.sndSsthresh
+
+ // As recovery is now complete, delete all SACK information for acked
+ // data.
+ s.ep.scoreboard.Delete(s.sndUna)
s.cc.PostRecovery()
}
@@ -644,6 +665,29 @@ func (s *sender) handleRcvdSegment(seg *segment) {
if s.ep.sendTSOk && seg.parsedOptions.TS {
s.ep.updateRecentTimestamp(seg.parsedOptions.TSVal, s.maxSentAck, seg.sequenceNumber)
}
+
+ // Insert SACKBlock information into our scoreboard.
+ if s.ep.sackPermitted {
+ for _, sb := range seg.parsedOptions.SACKBlocks {
+ // Only insert the SACK block if the following holds
+ // true:
+ // * SACK block acks data after the ack number in the
+ // current segment.
+ // * SACK block represents a sequence
+ // between sndUna and sndNxt (i.e. data that is
+ // currently unacked and in-flight).
+ // * SACK block that has not been SACKed already.
+ //
+ // NOTE: This check specifically excludes DSACK blocks
+ // which have start/end before sndUna and are used to
+ // indicate spurious retransmissions.
+ if seg.ackNumber.LessThan(sb.Start) && s.sndUna.LessThan(sb.Start) && sb.End.LessThanEq(s.sndNxt) && !s.ep.scoreboard.IsSACKED(sb) {
+ s.ep.scoreboard.Insert(sb)
+ seg.hasNewSACKInfo = true
+ }
+ }
+ }
+
// Count the duplicates and do the fast retransmit if needed.
rtx := s.checkDuplicateAck(seg)
@@ -702,6 +746,9 @@ func (s *sender) handleRcvdSegment(seg *segment) {
// Update the send buffer usage and notify potential waiters.
s.ep.updateSndBufferUsage(int(acked))
+ // Clear SACK information for all acked data.
+ s.ep.scoreboard.Delete(s.sndUna)
+
// If we are not in fast recovery then update the congestion
// window based on the number of acknowledged packets.
if !s.fr.active {