golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/congestion_reno.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build go1.21 6 7 package quic 8 9 import ( 10 "context" 11 "log/slog" 12 "math" 13 "time" 14 ) 15 16 // ccReno is the NewReno-based congestion controller defined in RFC 9002. 17 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7 18 type ccReno struct { 19 maxDatagramSize int 20 21 // Maximum number of bytes allowed to be in flight. 22 congestionWindow int 23 24 // Sum of size of all packets that contain at least one ack-eliciting 25 // or PADDING frame (i.e., any non-ACK frame), and have neither been 26 // acknowledged nor declared lost. 27 bytesInFlight int 28 29 // When the congestion window is below the slow start threshold, 30 // the controller is in slow start. 31 slowStartThreshold int 32 33 // The time the current recovery period started, or zero when not 34 // in a recovery period. 35 recoveryStartTime time.Time 36 37 // Accumulated count of bytes acknowledged in congestion avoidance. 38 congestionPendingAcks int 39 40 // When entering a recovery period, we are allowed to send one packet 41 // before reducing the congestion window. sendOnePacketInRecovery is 42 // true if we haven't sent that packet yet. 43 sendOnePacketInRecovery bool 44 45 // inRecovery is set when we are in the recovery state. 46 inRecovery bool 47 48 // underutilized is set if the congestion window is underutilized 49 // due to insufficient application data, flow control limits, or 50 // anti-amplification limits. 51 underutilized bool 52 53 // ackLastLoss is the sent time of the newest lost packet processed 54 // in the current batch. 55 ackLastLoss time.Time 56 57 // Data tracking the duration of the most recently handled sequence of 58 // contiguous lost packets. If this exceeds the persistent congestion duration, 59 // persistent congestion is declared. 60 // 61 // https://www.rfc-editor.org/rfc/rfc9002#section-7.6 62 persistentCongestion [numberSpaceCount]struct { 63 start time.Time // send time of first lost packet 64 end time.Time // send time of last lost packet 65 next packetNumber // one plus the number of the last lost packet 66 } 67 } 68 69 func newReno(maxDatagramSize int) *ccReno { 70 c := &ccReno{ 71 maxDatagramSize: maxDatagramSize, 72 } 73 74 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-1 75 c.congestionWindow = min(10*maxDatagramSize, max(14720, c.minimumCongestionWindow())) 76 77 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.1-1 78 c.slowStartThreshold = math.MaxInt 79 80 for space := range c.persistentCongestion { 81 c.persistentCongestion[space].next = -1 82 } 83 return c 84 } 85 86 // canSend reports whether the congestion controller permits sending 87 // a maximum-size datagram at this time. 88 // 89 // "An endpoint MUST NOT send a packet if it would cause bytes_in_flight [...] 90 // to be larger than the congestion window [...]" 91 // https://www.rfc-editor.org/rfc/rfc9002#section-7-7 92 // 93 // For simplicity and efficiency, we don't permit sending undersized datagrams. 94 func (c *ccReno) canSend() bool { 95 if c.sendOnePacketInRecovery { 96 return true 97 } 98 return c.bytesInFlight+c.maxDatagramSize <= c.congestionWindow 99 } 100 101 // setUnderutilized indicates that the congestion window is underutilized. 102 // 103 // The congestion window is underutilized if bytes in flight is smaller than 104 // the congestion window and sending is not pacing limited; that is, the 105 // congestion controller permits sending data, but no data is sent. 106 // 107 // https://www.rfc-editor.org/rfc/rfc9002#section-7.8 108 func (c *ccReno) setUnderutilized(log *slog.Logger, v bool) { 109 if c.underutilized == v { 110 return 111 } 112 oldState := c.state() 113 c.underutilized = v 114 if logEnabled(log, QLogLevelPacket) { 115 logCongestionStateUpdated(log, oldState, c.state()) 116 } 117 } 118 119 // packetSent indicates that a packet has been sent. 120 func (c *ccReno) packetSent(now time.Time, log *slog.Logger, space numberSpace, sent *sentPacket) { 121 if !sent.inFlight { 122 return 123 } 124 c.bytesInFlight += sent.size 125 if c.sendOnePacketInRecovery { 126 c.sendOnePacketInRecovery = false 127 } 128 } 129 130 // Acked and lost packets are processed in batches 131 // resulting from either a received ACK frame or 132 // the loss detection timer expiring. 133 // 134 // A batch consists of zero or more calls to packetAcked and packetLost, 135 // followed by a single call to packetBatchEnd. 136 // 137 // Acks may be reported in any order, but lost packets must 138 // be reported in strictly increasing order. 139 140 // packetAcked indicates that a packet has been newly acknowledged. 141 func (c *ccReno) packetAcked(now time.Time, sent *sentPacket) { 142 if !sent.inFlight { 143 return 144 } 145 c.bytesInFlight -= sent.size 146 147 if c.underutilized { 148 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.8 149 return 150 } 151 if sent.time.Before(c.recoveryStartTime) { 152 // In recovery, and this packet was sent before we entered recovery. 153 // (If this packet was sent after we entered recovery, receiving an ack 154 // for it moves us out of recovery into congestion avoidance.) 155 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2 156 return 157 } 158 c.congestionPendingAcks += sent.size 159 } 160 161 // packetLost indicates that a packet has been newly marked as lost. 162 // Lost packets must be reported in increasing order. 163 func (c *ccReno) packetLost(now time.Time, space numberSpace, sent *sentPacket, rtt *rttState) { 164 // Record state to check for persistent congestion. 165 // https://www.rfc-editor.org/rfc/rfc9002#section-7.6 166 // 167 // Note that this relies on always receiving loss events in increasing order: 168 // All packets prior to the one we're examining now have either been 169 // acknowledged or declared lost. 170 isValidPersistentCongestionSample := (sent.ackEliciting && 171 !rtt.firstSampleTime.IsZero() && 172 !sent.time.Before(rtt.firstSampleTime)) 173 if isValidPersistentCongestionSample { 174 // This packet either extends an existing range of lost packets, 175 // or starts a new one. 176 if sent.num != c.persistentCongestion[space].next { 177 c.persistentCongestion[space].start = sent.time 178 } 179 c.persistentCongestion[space].end = sent.time 180 c.persistentCongestion[space].next = sent.num + 1 181 } else { 182 // This packet cannot establish persistent congestion on its own. 183 // However, if we have an existing range of lost packets, 184 // this does not break it. 185 if sent.num == c.persistentCongestion[space].next { 186 c.persistentCongestion[space].next = sent.num + 1 187 } 188 } 189 190 if !sent.inFlight { 191 return 192 } 193 c.bytesInFlight -= sent.size 194 if sent.time.After(c.ackLastLoss) { 195 c.ackLastLoss = sent.time 196 } 197 } 198 199 // packetBatchEnd is called at the end of processing a batch of acked or lost packets. 200 func (c *ccReno) packetBatchEnd(now time.Time, log *slog.Logger, space numberSpace, rtt *rttState, maxAckDelay time.Duration) { 201 if logEnabled(log, QLogLevelPacket) { 202 oldState := c.state() 203 defer func() { logCongestionStateUpdated(log, oldState, c.state()) }() 204 } 205 if !c.ackLastLoss.IsZero() && !c.ackLastLoss.Before(c.recoveryStartTime) { 206 // Enter the recovery state. 207 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2 208 c.recoveryStartTime = now 209 c.slowStartThreshold = c.congestionWindow / 2 210 c.congestionWindow = max(c.slowStartThreshold, c.minimumCongestionWindow()) 211 c.sendOnePacketInRecovery = true 212 // Clear congestionPendingAcks to avoid increasing the congestion 213 // window based on acks in a frame that sends us into recovery. 214 c.congestionPendingAcks = 0 215 c.inRecovery = true 216 } else if c.congestionPendingAcks > 0 { 217 // We are in slow start or congestion avoidance. 218 c.inRecovery = false 219 if c.congestionWindow < c.slowStartThreshold { 220 // When the congestion window is less than the slow start threshold, 221 // we are in slow start and increase the window by the number of 222 // bytes acknowledged. 223 d := min(c.slowStartThreshold-c.congestionWindow, c.congestionPendingAcks) 224 c.congestionWindow += d 225 c.congestionPendingAcks -= d 226 } 227 // When the congestion window is at or above the slow start threshold, 228 // we are in congestion avoidance. 229 // 230 // RFC 9002 does not specify an algorithm here. The following is 231 // the recommended algorithm from RFC 5681, in which we increment 232 // the window by the maximum datagram size every time the number 233 // of bytes acknowledged reaches cwnd. 234 for c.congestionPendingAcks > c.congestionWindow { 235 c.congestionPendingAcks -= c.congestionWindow 236 c.congestionWindow += c.maxDatagramSize 237 } 238 } 239 if !c.ackLastLoss.IsZero() { 240 // Check for persistent congestion. 241 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6 242 // 243 // "A sender [...] MAY use state for just the packet number space that 244 // was acknowledged." 245 // https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2-5 246 // 247 // For simplicity, we consider each number space independently. 248 const persistentCongestionThreshold = 3 249 d := (rtt.smoothedRTT + max(4*rtt.rttvar, timerGranularity) + maxAckDelay) * 250 persistentCongestionThreshold 251 start := c.persistentCongestion[space].start 252 end := c.persistentCongestion[space].end 253 if end.Sub(start) >= d { 254 c.congestionWindow = c.minimumCongestionWindow() 255 c.recoveryStartTime = time.Time{} 256 rtt.establishPersistentCongestion() 257 } 258 } 259 c.ackLastLoss = time.Time{} 260 } 261 262 // packetDiscarded indicates that the keys for a packet's space have been discarded. 263 func (c *ccReno) packetDiscarded(sent *sentPacket) { 264 // https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-3 265 if sent.inFlight { 266 c.bytesInFlight -= sent.size 267 } 268 } 269 270 func (c *ccReno) minimumCongestionWindow() int { 271 // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-4 272 return 2 * c.maxDatagramSize 273 } 274 275 func logCongestionStateUpdated(log *slog.Logger, oldState, newState congestionState) { 276 if oldState == newState { 277 return 278 } 279 log.LogAttrs(context.Background(), QLogLevelPacket, 280 "recovery:congestion_state_updated", 281 slog.String("old", oldState.String()), 282 slog.String("new", newState.String()), 283 ) 284 } 285 286 type congestionState string 287 288 func (s congestionState) String() string { return string(s) } 289 290 const ( 291 congestionSlowStart = congestionState("slow_start") 292 congestionCongestionAvoidance = congestionState("congestion_avoidance") 293 congestionApplicationLimited = congestionState("application_limited") 294 congestionRecovery = congestionState("recovery") 295 ) 296 297 func (c *ccReno) state() congestionState { 298 switch { 299 case c.inRecovery: 300 return congestionRecovery 301 case c.underutilized: 302 return congestionApplicationLimited 303 case c.congestionWindow < c.slowStartThreshold: 304 return congestionSlowStart 305 default: 306 return congestionCongestionAvoidance 307 } 308 }