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 }