github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/tcpip/transport/tcp/sack_recovery.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tcp 16 17 import "github.com/sagernet/gvisor/pkg/tcpip/seqnum" 18 19 // sackRecovery stores the variables related to TCP SACK loss recovery 20 // algorithm. 21 // 22 // +stateify savable 23 type sackRecovery struct { 24 s *sender 25 } 26 27 func newSACKRecovery(s *sender) *sackRecovery { 28 return &sackRecovery{s: s} 29 } 30 31 // handleSACKRecovery implements the loss recovery phase as described in RFC6675 32 // section 5, step C. 33 // +checklocks:sr.s.ep.mu 34 func (sr *sackRecovery) handleSACKRecovery(limit int, end seqnum.Value) (dataSent bool) { 35 snd := sr.s 36 snd.SetPipe() 37 38 if smss := int(snd.ep.scoreboard.SMSS()); limit > smss { 39 // Cap segment size limit to s.smss as SACK recovery requires 40 // that all retransmissions or new segments send during recovery 41 // be of <= SMSS. 42 limit = smss 43 } 44 45 nextSegHint := snd.writeList.Front() 46 for snd.Outstanding < snd.SndCwnd { 47 var nextSeg *segment 48 var rescueRtx bool 49 nextSeg, nextSegHint, rescueRtx = snd.NextSeg(nextSegHint) 50 if nextSeg == nil { 51 return dataSent 52 } 53 if !snd.isAssignedSequenceNumber(nextSeg) || snd.SndNxt.LessThanEq(nextSeg.sequenceNumber) { 54 // New data being sent. 55 56 // Step C.3 described below is handled by 57 // maybeSendSegment which increments sndNxt when 58 // a segment is transmitted. 59 // 60 // Step C.3 "If any of the data octets sent in 61 // (C.1) are above HighData, HighData must be 62 // updated to reflect the transmission of 63 // previously unsent data." 64 // 65 // We pass s.smss as the limit as the Step 2) requires that 66 // new data sent should be of size s.smss or less. 67 if sent := snd.maybeSendSegment(nextSeg, limit, end); !sent { 68 return dataSent 69 } 70 dataSent = true 71 snd.Outstanding++ 72 snd.updateWriteNext(nextSeg.Next()) 73 continue 74 } 75 76 // Now handle the retransmission case where we matched either step 1,3 or 4 77 // of the NextSeg algorithm. 78 // RFC 6675, Step C.4. 79 // 80 // "The estimate of the amount of data outstanding in the network 81 // must be updated by incrementing pipe by the number of octets 82 // transmitted in (C.1)." 83 snd.Outstanding++ 84 dataSent = true 85 snd.sendSegment(nextSeg) 86 87 segEnd := nextSeg.sequenceNumber.Add(nextSeg.logicalLen()) 88 if rescueRtx { 89 // We do the last part of rule (4) of NextSeg here to update 90 // RescueRxt as until this point we don't know if we are going 91 // to use the rescue transmission. 92 snd.FastRecovery.RescueRxt = snd.FastRecovery.Last 93 } else { 94 // RFC 6675, Step C.2 95 // 96 // "If any of the data octets sent in (C.1) are below 97 // HighData, HighRxt MUST be set to the highest sequence 98 // number of the retransmitted segment unless NextSeg () 99 // rule (4) was invoked for this retransmission." 100 snd.FastRecovery.HighRxt = segEnd - 1 101 } 102 } 103 return dataSent 104 } 105 106 // +checklocks:sr.s.ep.mu 107 func (sr *sackRecovery) DoRecovery(rcvdSeg *segment, fastRetransmit bool) { 108 snd := sr.s 109 if fastRetransmit { 110 snd.resendSegment() 111 } 112 113 // We are in fast recovery mode. Ignore the ack if it's out of range. 114 if ack := rcvdSeg.ackNumber; !ack.InRange(snd.SndUna, snd.SndNxt+1) { 115 return 116 } 117 118 // RFC 6675 recovery algorithm step C 1-5. 119 end := snd.SndUna.Add(snd.SndWnd) 120 dataSent := sr.handleSACKRecovery(snd.MaxPayloadSize, end) 121 snd.postXmit(dataSent, true /* shouldScheduleProbe */) 122 }