gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/transport/tcp/cubic_test.go (about)

     1  // Copyright 2024 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  	"testing"
    19  	"time"
    20  
    21  	"gvisor.dev/gvisor/pkg/tcpip"
    22  	"gvisor.dev/gvisor/pkg/tcpip/faketime"
    23  	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
    24  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    25  )
    26  
    27  // TestHyStartAckTrainOK tests that HyStart triggers early exit from slow start
    28  // if ACKs come in the same round for longer than RTT/2.
    29  func TestHyStartAckTrainOK(t *testing.T) {
    30  	fClock := faketime.NewManualClock()
    31  	stackOpts := stack.Options{
    32  		TransportProtocols: []stack.TransportProtocolFactory{NewProtocol},
    33  		Clock:              fClock,
    34  	}
    35  	s := stack.New(stackOpts)
    36  	ep := &Endpoint{
    37  		stack: s,
    38  		cc:    tcpip.CongestionControlOption("cubic"),
    39  	}
    40  	iss := seqnum.Value(0)
    41  	snd := &sender{
    42  		ep: ep,
    43  		TCPSenderState: stack.TCPSenderState{
    44  			SndUna:   iss + 1,
    45  			SndNxt:   iss + 1,
    46  			Ssthresh: InitialSsthresh,
    47  		},
    48  	}
    49  	uut := newCubicCC(snd)
    50  	snd.cc = uut
    51  
    52  	if uut.LastRTT != effectivelyInfinity {
    53  		t.Fatal()
    54  	}
    55  	if uut.CurrRTT != effectivelyInfinity {
    56  		t.Fatal()
    57  	}
    58  
    59  	d0 := 4 * time.Millisecond
    60  	uut.updateHyStart(d0)
    61  	if uut.CurrRTT != d0 {
    62  		t.Fatal()
    63  	}
    64  	if snd.Ssthresh != InitialSsthresh {
    65  		t.Fatal("HyStart should not be triggered")
    66  	}
    67  
    68  	// Move SndNext and SndUna to advance to a new round.
    69  	snd.SndNxt = snd.SndNxt.Add(2000)
    70  	snd.SndUna = snd.SndUna.Add(1000)
    71  	fClock.Advance(d0)
    72  	r1ExpectedStart := fClock.NowMonotonic()
    73  
    74  	d1 := 5 * time.Millisecond
    75  	uut.updateHyStart(d1)
    76  	if uut.LastRTT != d0 {
    77  		t.Fatal()
    78  	}
    79  	if uut.CurrRTT != d1 {
    80  		t.Fatal()
    81  	}
    82  	if uut.RoundStart != r1ExpectedStart {
    83  		t.Fatal()
    84  	}
    85  
    86  	// Still in round after RTT/2 (2ms) triggers HyStart.  Note that HyStart
    87  	// will ignore ACKs spaced more than 2ms apart, so we send one per ms 3
    88  	// times.
    89  	for range 2 {
    90  		fClock.Advance(time.Millisecond)
    91  		uut.updateHyStart(d1)
    92  		if snd.Ssthresh != InitialSsthresh {
    93  			t.Fatal("HyStart should not be triggered")
    94  		}
    95  		if uut.LastAck != fClock.NowMonotonic() {
    96  			t.Fatal()
    97  		}
    98  	}
    99  
   100  	// 3 ms---triggers HyStart setting Ssthresh
   101  	fClock.Advance(time.Millisecond)
   102  	uut.updateHyStart(d1)
   103  	if snd.Ssthresh == InitialSsthresh {
   104  		t.Fatal("HyStart SHOULD be triggered")
   105  	}
   106  }
   107  
   108  // TestHyStartAckTrainTooSpread tests that ACKs that are more than 2ms apart
   109  // are ignored for purposes of triggering HyStart via the ACK train mechanism.
   110  func TestHyStartAckTrainTooSpread(t *testing.T) {
   111  	fClock := faketime.NewManualClock()
   112  	stackOpts := stack.Options{
   113  		TransportProtocols: []stack.TransportProtocolFactory{NewProtocol},
   114  		Clock:              fClock,
   115  	}
   116  	s := stack.New(stackOpts)
   117  	ep := &Endpoint{
   118  		stack: s,
   119  		cc:    tcpip.CongestionControlOption("cubic"),
   120  	}
   121  	iss := seqnum.Value(0)
   122  	snd := &sender{
   123  		ep: ep,
   124  		TCPSenderState: stack.TCPSenderState{
   125  			SndUna:   iss + 1,
   126  			SndNxt:   iss + 1,
   127  			Ssthresh: InitialSsthresh,
   128  		},
   129  	}
   130  	uut := newCubicCC(snd)
   131  	snd.cc = uut
   132  
   133  	if uut.LastRTT != effectivelyInfinity {
   134  		t.Fatal()
   135  	}
   136  	if uut.CurrRTT != effectivelyInfinity {
   137  		t.Fatal()
   138  	}
   139  	d0 := 4 * time.Millisecond
   140  	uut.updateHyStart(d0)
   141  	if uut.CurrRTT != d0 {
   142  		t.Fatal()
   143  	}
   144  	if snd.Ssthresh != InitialSsthresh {
   145  		t.Fatal("HyStart should not be triggered")
   146  	}
   147  
   148  	// Move SndNext and SndUna to advance to a new round.
   149  	snd.SndNxt = snd.SndNxt.Add(2000)
   150  	snd.SndUna = snd.SndUna.Add(1000)
   151  	fClock.Advance(d0)
   152  	r1ExpectedStart := fClock.NowMonotonic()
   153  
   154  	d1 := 5 * time.Millisecond
   155  	uut.updateHyStart(d1)
   156  	if uut.LastRTT != d0 {
   157  		t.Fatal()
   158  	}
   159  	if uut.CurrRTT != d1 {
   160  		t.Fatal()
   161  	}
   162  	if uut.RoundStart != r1ExpectedStart {
   163  		t.Fatal()
   164  	}
   165  
   166  	// HyStart will ignore ACKs spaced more than 2ms apart
   167  	fClock.Advance(3 * time.Millisecond)
   168  	uut.updateHyStart(d1)
   169  	if snd.Ssthresh != InitialSsthresh {
   170  		t.Fatal("HyStart should not be triggered")
   171  	}
   172  	if uut.LastAck != r1ExpectedStart {
   173  		t.Fatal("Should ignore ACK 3ms later")
   174  	}
   175  }
   176  
   177  // TestHyStartDelayOK tests that HyStart triggers early exit from slow start
   178  // if RTT exceeds previous round by at least minRTTThresh.
   179  func TestHyStartDelayOK(t *testing.T) {
   180  	fClock := faketime.NewManualClock()
   181  	stackOpts := stack.Options{
   182  		TransportProtocols: []stack.TransportProtocolFactory{NewProtocol},
   183  		Clock:              fClock,
   184  	}
   185  	s := stack.New(stackOpts)
   186  	ep := &Endpoint{
   187  		stack: s,
   188  		cc:    tcpip.CongestionControlOption("cubic"),
   189  	}
   190  	iss := seqnum.Value(0)
   191  	snd := &sender{
   192  		ep: ep,
   193  		TCPSenderState: stack.TCPSenderState{
   194  			SndUna:   iss + 1,
   195  			SndNxt:   iss + 1,
   196  			Ssthresh: InitialSsthresh,
   197  		},
   198  	}
   199  	uut := newCubicCC(snd)
   200  	snd.cc = uut
   201  
   202  	d0 := 4 * time.Millisecond
   203  	uut.updateHyStart(d0)
   204  
   205  	// Move SndNext and SndUna to advance to a new round.
   206  	snd.SndNxt = snd.SndNxt.Add(2000)
   207  	snd.SndUna = snd.SndUna.Add(1000)
   208  	fClock.Advance(d0)
   209  
   210  	d1 := d0 + minRTTThresh
   211  
   212  	// Delay detection requires at least nRTTSample measurements.
   213  	for i := uint(1); i < nRTTSample; i++ {
   214  		uut.updateHyStart(d1)
   215  		if uut.SampleCount != i {
   216  			t.Fatal()
   217  		}
   218  	}
   219  	if snd.Ssthresh != InitialSsthresh {
   220  		t.Fatal("triggered with fewer than nRTTSample measurements")
   221  	}
   222  	uut.updateHyStart(d1)
   223  	if snd.Ssthresh == InitialSsthresh {
   224  		t.Fatal("didn't trigger SS exit")
   225  	}
   226  }
   227  
   228  // TestHyStartDelay_BelowThresh tests that HyStart doesn't trigger early exit
   229  // from slow start if at least one RTT measurement is below threshold.
   230  func TestHyStartDelay_BelowThresh(t *testing.T) {
   231  	fClock := faketime.NewManualClock()
   232  	stackOpts := stack.Options{
   233  		TransportProtocols: []stack.TransportProtocolFactory{NewProtocol},
   234  		Clock:              fClock,
   235  	}
   236  	s := stack.New(stackOpts)
   237  	ep := &Endpoint{
   238  		stack: s,
   239  		cc:    tcpip.CongestionControlOption("cubic"),
   240  	}
   241  	iss := seqnum.Value(0)
   242  	snd := &sender{
   243  		ep: ep,
   244  		TCPSenderState: stack.TCPSenderState{
   245  			SndUna:   iss + 1,
   246  			SndNxt:   iss + 1,
   247  			Ssthresh: InitialSsthresh,
   248  		},
   249  	}
   250  	uut := newCubicCC(snd)
   251  	snd.cc = uut
   252  
   253  	d0 := 4 * time.Millisecond
   254  	uut.updateHyStart(d0)
   255  
   256  	// Move SndNext and SndUna to advance to a new round.
   257  	snd.SndNxt = snd.SndNxt.Add(2000)
   258  	snd.SndUna = snd.SndUna.Add(1000)
   259  	fClock.Advance(d0)
   260  
   261  	d1 := d0 + minRTTThresh
   262  
   263  	// Delay detection requires at least nRTTSample measurements.
   264  	for i := uint(1); i < nRTTSample; i++ {
   265  		uut.updateHyStart(d1)
   266  		if uut.SampleCount != i {
   267  			t.Fatal()
   268  		}
   269  	}
   270  	if snd.Ssthresh != InitialSsthresh {
   271  		t.Fatal("triggered with fewer than nRTTSample measurements")
   272  	}
   273  	uut.updateHyStart(d1 - time.Millisecond)
   274  	if snd.Ssthresh != InitialSsthresh {
   275  		t.Fatal("triggered with a measurement under threshold")
   276  	}
   277  }