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  }