github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/tcpip/transport/tcp/sack_scoreboard.go (about) 1 // Copyright 2018 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 ( 18 "fmt" 19 "strings" 20 21 "github.com/google/btree" 22 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/header" 23 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/seqnum" 24 ) 25 26 const ( 27 // maxSACKBlocks is the maximum number of distinct SACKBlocks the 28 // scoreboard will track. Once there are 100 distinct blocks, new 29 // insertions will fail. 30 maxSACKBlocks = 100 31 32 // defaultBtreeDegree is set to 2 as btree.New(2) results in a 2-3-4 33 // tree. 34 defaultBtreeDegree = 2 35 ) 36 37 // SACKScoreboard stores a set of disjoint SACK ranges. 38 // 39 // +stateify savable 40 type SACKScoreboard struct { 41 // smss is defined in RFC5681 as following: 42 // 43 // The SMSS is the size of the largest segment that the sender can 44 // transmit. This value can be based on the maximum transmission unit 45 // of the network, the path MTU discovery [RFC1191, RFC4821] algorithm, 46 // RMSS (see next item), or other factors. The size does not include 47 // the TCP/IP headers and options. 48 smss uint16 49 maxSACKED seqnum.Value 50 sacked seqnum.Size `state:"nosave"` 51 ranges *btree.BTree `state:"nosave"` 52 } 53 54 // NewSACKScoreboard returns a new SACK Scoreboard. 55 func NewSACKScoreboard(smss uint16, iss seqnum.Value) *SACKScoreboard { 56 return &SACKScoreboard{ 57 smss: smss, 58 ranges: btree.New(defaultBtreeDegree), 59 maxSACKED: iss, 60 } 61 } 62 63 // Reset erases all known range information from the SACK scoreboard. 64 func (s *SACKScoreboard) Reset() { 65 s.ranges = btree.New(defaultBtreeDegree) 66 s.sacked = 0 67 } 68 69 // Insert inserts/merges the provided SACKBlock into the scoreboard. 70 func (s *SACKScoreboard) Insert(r header.SACKBlock) { 71 if s.ranges.Len() >= maxSACKBlocks { 72 return 73 } 74 75 // Check if we can merge the new range with a range before or after it. 76 var toDelete []btree.Item 77 if s.maxSACKED.LessThan(r.End - 1) { 78 s.maxSACKED = r.End - 1 79 } 80 s.ranges.AscendGreaterOrEqual(r, func(i btree.Item) bool { 81 if i == r { 82 return true 83 } 84 sacked := i.(header.SACKBlock) 85 // There is a hole between these two SACK blocks, so we can't 86 // merge anymore. 87 if r.End.LessThan(sacked.Start) { 88 return false 89 } 90 // There is some overlap at this point, merge the blocks and 91 // delete the other one. 92 // 93 // ----sS--------sE 94 // r.S---------------rE 95 // -------sE 96 if sacked.End.LessThan(r.End) { 97 // sacked is contained in the newly inserted range. 98 // Delete this block. 99 toDelete = append(toDelete, i) 100 return true 101 } 102 // sacked covers a range past end of the newly inserted 103 // block. 104 r.End = sacked.End 105 toDelete = append(toDelete, i) 106 return true 107 }) 108 109 s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool { 110 if i == r { 111 return true 112 } 113 sacked := i.(header.SACKBlock) 114 // sA------sE 115 // rA----rE 116 if sacked.End.LessThan(r.Start) { 117 return false 118 } 119 // The previous range extends into the current block. Merge it 120 // into the newly inserted range and delete the other one. 121 // 122 // <-rA---rE----<---rE---> 123 // sA--------------sE 124 r.Start = sacked.Start 125 // Extend r to cover sacked if sacked extends past r. 126 if r.End.LessThan(sacked.End) { 127 r.End = sacked.End 128 } 129 toDelete = append(toDelete, i) 130 return true 131 }) 132 for _, i := range toDelete { 133 if sb := s.ranges.Delete(i); sb != nil { 134 sb := i.(header.SACKBlock) 135 s.sacked -= sb.Start.Size(sb.End) 136 } 137 } 138 139 replaced := s.ranges.ReplaceOrInsert(r) 140 if replaced == nil { 141 s.sacked += r.Start.Size(r.End) 142 } 143 } 144 145 // IsSACKED returns true if the a given range of sequence numbers denoted by r 146 // are already covered by SACK information in the scoreboard. 147 func (s *SACKScoreboard) IsSACKED(r header.SACKBlock) bool { 148 if s.Empty() { 149 return false 150 } 151 152 found := false 153 s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool { 154 sacked := i.(header.SACKBlock) 155 if sacked.End.LessThan(r.Start) { 156 return false 157 } 158 if sacked.Contains(r) { 159 found = true 160 return false 161 } 162 return true 163 }) 164 return found 165 } 166 167 // String returns human-readable state of the scoreboard structure. 168 func (s *SACKScoreboard) String() string { 169 var str strings.Builder 170 str.WriteString("SACKScoreboard: {") 171 s.ranges.Ascend(func(i btree.Item) bool { 172 str.WriteString(fmt.Sprintf("%v,", i)) 173 return true 174 }) 175 str.WriteString("}\n") 176 return str.String() 177 } 178 179 // Delete removes all SACK information prior to seq. 180 func (s *SACKScoreboard) Delete(seq seqnum.Value) { 181 if s.Empty() { 182 return 183 } 184 toDelete := []btree.Item{} 185 toInsert := []btree.Item{} 186 r := header.SACKBlock{seq, seq.Add(1)} 187 s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool { 188 if i == r { 189 return true 190 } 191 sb := i.(header.SACKBlock) 192 toDelete = append(toDelete, i) 193 if sb.End.LessThanEq(seq) { 194 s.sacked -= sb.Start.Size(sb.End) 195 } else { 196 newSB := header.SACKBlock{seq, sb.End} 197 toInsert = append(toInsert, newSB) 198 s.sacked -= sb.Start.Size(seq) 199 } 200 return true 201 }) 202 for _, sb := range toDelete { 203 s.ranges.Delete(sb) 204 } 205 for _, sb := range toInsert { 206 s.ranges.ReplaceOrInsert(sb) 207 } 208 } 209 210 // Copy provides a copy of the SACK scoreboard. 211 func (s *SACKScoreboard) Copy() (sackBlocks []header.SACKBlock, maxSACKED seqnum.Value) { 212 s.ranges.Ascend(func(i btree.Item) bool { 213 sackBlocks = append(sackBlocks, i.(header.SACKBlock)) 214 return true 215 }) 216 return sackBlocks, s.maxSACKED 217 } 218 219 // IsRangeLost implements the IsLost(SeqNum) operation defined in RFC 6675 220 // section 4 but operates on a range of sequence numbers and returns true if 221 // there are at least nDupAckThreshold SACK blocks greater than the range being 222 // checked or if at least (nDupAckThreshold-1)*s.smss bytes have been SACKED 223 // with sequence numbers greater than the block being checked. 224 func (s *SACKScoreboard) IsRangeLost(r header.SACKBlock) bool { 225 if s.Empty() { 226 return false 227 } 228 nDupSACK := 0 229 nDupSACKBytes := seqnum.Size(0) 230 isLost := false 231 232 // We need to check if the immediate lower (if any) sacked 233 // range contains or partially overlaps with r. 234 searchMore := true 235 s.ranges.DescendLessOrEqual(r, func(i btree.Item) bool { 236 sacked := i.(header.SACKBlock) 237 if sacked.Contains(r) { 238 searchMore = false 239 return false 240 } 241 if sacked.End.LessThanEq(r.Start) { 242 // all sequence numbers covered by sacked are below 243 // r so we continue searching. 244 return false 245 } 246 // There is a partial overlap. In this case we r.Start is 247 // between sacked.Start & sacked.End and r.End extends beyond 248 // sacked.End. 249 // Move r.Start to sacked.End and continuing searching blocks 250 // above r.Start. 251 r.Start = sacked.End 252 return false 253 }) 254 255 if !searchMore { 256 return isLost 257 } 258 259 s.ranges.AscendGreaterOrEqual(r, func(i btree.Item) bool { 260 sacked := i.(header.SACKBlock) 261 if sacked.Contains(r) { 262 return false 263 } 264 nDupSACKBytes += sacked.Start.Size(sacked.End) 265 nDupSACK++ 266 if nDupSACK >= nDupAckThreshold || nDupSACKBytes >= seqnum.Size((nDupAckThreshold-1)*s.smss) { 267 isLost = true 268 return false 269 } 270 return true 271 }) 272 return isLost 273 } 274 275 // IsLost implements the IsLost(SeqNum) operation defined in RFC3517 section 276 // 4. 277 // 278 // This routine returns whether the given sequence number is considered to be 279 // lost. The routine returns true when either nDupAckThreshold discontiguous 280 // SACKed sequences have arrived above 'SeqNum' or (nDupAckThreshold * SMSS) 281 // bytes with sequence numbers greater than 'SeqNum' have been SACKed. 282 // Otherwise, the routine returns false. 283 func (s *SACKScoreboard) IsLost(seq seqnum.Value) bool { 284 return s.IsRangeLost(header.SACKBlock{seq, seq.Add(1)}) 285 } 286 287 // Empty returns true if the SACK scoreboard has no entries, false otherwise. 288 func (s *SACKScoreboard) Empty() bool { 289 return s.ranges.Len() == 0 290 } 291 292 // Sacked returns the current number of bytes held in the SACK scoreboard. 293 func (s *SACKScoreboard) Sacked() seqnum.Size { 294 return s.sacked 295 } 296 297 // MaxSACKED returns the highest sequence number ever inserted in the SACK 298 // scoreboard. 299 func (s *SACKScoreboard) MaxSACKED() seqnum.Value { 300 return s.maxSACKED 301 } 302 303 // SMSS returns the sender's MSS as held by the SACK scoreboard. 304 func (s *SACKScoreboard) SMSS() uint16 { 305 return s.smss 306 }