github.com/lightlus/netstack@v1.2.0/tcpip/transport/tcp/cubic.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 "math" 19 "time" 20 ) 21 22 // cubicState stores the variables related to TCP CUBIC congestion 23 // control algorithm state. 24 // 25 // See: https://tools.ietf.org/html/rfc8312. 26 // +stateify savable 27 type cubicState struct { 28 // wLastMax is the previous wMax value. 29 wLastMax float64 30 31 // wMax is the value of the congestion window at the 32 // time of last congestion event. 33 wMax float64 34 35 // t denotes the time when the current congestion avoidance 36 // was entered. 37 t time.Time 38 39 // numCongestionEvents tracks the number of congestion events since last 40 // RTO. 41 numCongestionEvents int 42 43 // c is the cubic constant as specified in RFC8312. It's fixed at 0.4 as 44 // per RFC. 45 c float64 46 47 // k is the time period that the above function takes to increase the 48 // current window size to W_max if there are no further congestion 49 // events and is calculated using the following equation: 50 // 51 // K = cubic_root(W_max*(1-beta_cubic)/C) (Eq. 2) 52 k float64 53 54 // beta is the CUBIC multiplication decrease factor. that is, when a 55 // congestion event is detected, CUBIC reduces its cwnd to 56 // W_cubic(0)=W_max*beta_cubic. 57 beta float64 58 59 // wC is window computed by CUBIC at time t. It's calculated using the 60 // formula: 61 // 62 // W_cubic(t) = C*(t-K)^3 + W_max (Eq. 1) 63 wC float64 64 65 // wEst is the window computed by CUBIC at time t+RTT i.e 66 // W_cubic(t+RTT). 67 wEst float64 68 69 s *sender 70 } 71 72 // newCubicCC returns a partially initialized cubic state with the constants 73 // beta and c set and t set to current time. 74 func newCubicCC(s *sender) *cubicState { 75 return &cubicState{ 76 t: time.Now(), 77 beta: 0.7, 78 c: 0.4, 79 s: s, 80 } 81 } 82 83 // enterCongestionAvoidance is used to initialize cubic in cases where we exit 84 // SlowStart without a real congestion event taking place. This can happen when 85 // a connection goes back to slow start due to a retransmit and we exceed the 86 // previously lowered ssThresh without experiencing packet loss. 87 // 88 // Refer: https://tools.ietf.org/html/rfc8312#section-4.8 89 func (c *cubicState) enterCongestionAvoidance() { 90 // See: https://tools.ietf.org/html/rfc8312#section-4.7 & 91 // https://tools.ietf.org/html/rfc8312#section-4.8 92 if c.numCongestionEvents == 0 { 93 c.k = 0 94 c.t = time.Now() 95 c.wLastMax = c.wMax 96 c.wMax = float64(c.s.sndCwnd) 97 } 98 } 99 100 // updateSlowStart will update the congestion window as per the slow-start 101 // algorithm used by NewReno. If after adjusting the congestion window we cross 102 // the ssThresh then it will return the number of packets that must be consumed 103 // in congestion avoidance mode. 104 func (c *cubicState) updateSlowStart(packetsAcked int) int { 105 // Don't let the congestion window cross into the congestion 106 // avoidance range. 107 newcwnd := c.s.sndCwnd + packetsAcked 108 enterCA := false 109 if newcwnd >= c.s.sndSsthresh { 110 newcwnd = c.s.sndSsthresh 111 c.s.sndCAAckCount = 0 112 enterCA = true 113 } 114 115 packetsAcked -= newcwnd - c.s.sndCwnd 116 c.s.sndCwnd = newcwnd 117 if enterCA { 118 c.enterCongestionAvoidance() 119 } 120 return packetsAcked 121 } 122 123 // Update updates cubic's internal state variables. It must be called on every 124 // ACK received. 125 // Refer: https://tools.ietf.org/html/rfc8312#section-4 126 func (c *cubicState) Update(packetsAcked int) { 127 if c.s.sndCwnd < c.s.sndSsthresh { 128 packetsAcked = c.updateSlowStart(packetsAcked) 129 if packetsAcked == 0 { 130 return 131 } 132 } else { 133 c.s.rtt.Lock() 134 srtt := c.s.rtt.srtt 135 c.s.rtt.Unlock() 136 c.s.sndCwnd = c.getCwnd(packetsAcked, c.s.sndCwnd, srtt) 137 } 138 } 139 140 // cubicCwnd computes the CUBIC congestion window after t seconds from last 141 // congestion event. 142 func (c *cubicState) cubicCwnd(t float64) float64 { 143 return c.c*math.Pow(t, 3.0) + c.wMax 144 } 145 146 // getCwnd returns the current congestion window as computed by CUBIC. 147 // Refer: https://tools.ietf.org/html/rfc8312#section-4 148 func (c *cubicState) getCwnd(packetsAcked, sndCwnd int, srtt time.Duration) int { 149 elapsed := time.Since(c.t).Seconds() 150 151 // Compute the window as per Cubic after 'elapsed' time 152 // since last congestion event. 153 c.wC = c.cubicCwnd(elapsed - c.k) 154 155 // Compute the TCP friendly estimate of the congestion window. 156 c.wEst = c.wMax*c.beta + (3.0*((1.0-c.beta)/(1.0+c.beta)))*(elapsed/srtt.Seconds()) 157 158 // Make sure in the TCP friendly region CUBIC performs at least 159 // as well as Reno. 160 if c.wC < c.wEst && float64(sndCwnd) < c.wEst { 161 // TCP Friendly region of cubic. 162 return int(c.wEst) 163 } 164 165 // In Concave/Convex region of CUBIC, calculate what CUBIC window 166 // will be after 1 RTT and use that to grow congestion window 167 // for every ack. 168 tEst := (time.Since(c.t) + srtt).Seconds() 169 wtRtt := c.cubicCwnd(tEst - c.k) 170 // As per 4.3 for each received ACK cwnd must be incremented 171 // by (w_cubic(t+RTT) - cwnd/cwnd. 172 cwnd := float64(sndCwnd) 173 for i := 0; i < packetsAcked; i++ { 174 // Concave/Convex regions of cubic have the same formulas. 175 // See: https://tools.ietf.org/html/rfc8312#section-4.3 176 cwnd += (wtRtt - cwnd) / cwnd 177 } 178 return int(cwnd) 179 } 180 181 // HandleNDupAcks implements congestionControl.HandleNDupAcks. 182 func (c *cubicState) HandleNDupAcks() { 183 // See: https://tools.ietf.org/html/rfc8312#section-4.5 184 c.numCongestionEvents++ 185 c.t = time.Now() 186 c.wLastMax = c.wMax 187 c.wMax = float64(c.s.sndCwnd) 188 189 c.fastConvergence() 190 c.reduceSlowStartThreshold() 191 } 192 193 // HandleRTOExpired implements congestionContrl.HandleRTOExpired. 194 func (c *cubicState) HandleRTOExpired() { 195 // See: https://tools.ietf.org/html/rfc8312#section-4.6 196 c.t = time.Now() 197 c.numCongestionEvents = 0 198 c.wLastMax = c.wMax 199 c.wMax = float64(c.s.sndCwnd) 200 201 c.fastConvergence() 202 203 // We lost a packet, so reduce ssthresh. 204 c.reduceSlowStartThreshold() 205 206 // Reduce the congestion window to 1, i.e., enter slow-start. Per 207 // RFC 5681, page 7, we must use 1 regardless of the value of the 208 // initial congestion window. 209 c.s.sndCwnd = 1 210 } 211 212 // fastConvergence implements the logic for Fast Convergence algorithm as 213 // described in https://tools.ietf.org/html/rfc8312#section-4.6. 214 func (c *cubicState) fastConvergence() { 215 if c.wMax < c.wLastMax { 216 c.wLastMax = c.wMax 217 c.wMax = c.wMax * (1.0 + c.beta) / 2.0 218 } else { 219 c.wLastMax = c.wMax 220 } 221 // Recompute k as wMax may have changed. 222 c.k = math.Cbrt(c.wMax * (1 - c.beta) / c.c) 223 } 224 225 // PostRecovery implemements congestionControl.PostRecovery. 226 func (c *cubicState) PostRecovery() { 227 c.t = time.Now() 228 } 229 230 // reduceSlowStartThreshold returns new SsThresh as described in 231 // https://tools.ietf.org/html/rfc8312#section-4.7. 232 func (c *cubicState) reduceSlowStartThreshold() { 233 c.s.sndSsthresh = int(math.Max(float64(c.s.sndCwnd)*c.beta, 2.0)) 234 }