quiche/recovery/congestion/bbr/
per_ack.rs

1// Copyright (C) 2022, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::cmp;
28
29use super::*;
30use crate::rand;
31use crate::recovery::MINIMUM_WINDOW_PACKETS;
32
33/// 1.2Mbps in bytes/sec
34const PACING_RATE_1_2MBPS: u64 = 1200 * 1000 / 8;
35
36/// 24Mbps in bytes/sec
37const PACING_RATE_24MBPS: u64 = 24 * 1000 * 1000 / 8;
38
39/// The minimal cwnd value BBR tries to target, in bytes
40#[inline]
41fn bbr_min_pipe_cwnd(r: &mut Congestion) -> usize {
42    BBR_MIN_PIPE_CWND_PKTS * r.max_datagram_size
43}
44
45// BBR Functions when ACK is received.
46//
47pub fn bbr_update_model_and_state(
48    r: &mut Congestion, packet: &Acked, bytes_in_flight: usize, now: Instant,
49) {
50    bbr_update_btlbw(r, packet, bytes_in_flight);
51    bbr_check_cycle_phase(r, now);
52    bbr_check_full_pipe(r);
53    bbr_check_drain(r, bytes_in_flight, now);
54    bbr_update_rtprop(r, now);
55    bbr_check_probe_rtt(r, bytes_in_flight, now);
56}
57
58pub fn bbr_update_control_parameters(
59    r: &mut Congestion, bytes_in_flight: usize, now: Instant,
60) {
61    pacing::bbr_set_pacing_rate(r);
62    bbr_set_send_quantum(r);
63
64    // Set outgoing packet pacing rate
65    // It is called here because send_quantum may be updated too.
66    r.set_pacing_rate(r.bbr_state.pacing_rate, now);
67
68    bbr_set_cwnd(r, bytes_in_flight);
69}
70
71// BBR Functions while processing ACKs.
72//
73
74// 4.1.1.5.  Updating the BBR.BtlBw Max Filter
75fn bbr_update_btlbw(r: &mut Congestion, packet: &Acked, _bytes_in_flight: usize) {
76    bbr_update_round(r, packet);
77
78    if r.delivery_rate().to_bytes_per_second() >= r.bbr_state.btlbw ||
79        !r.delivery_rate.sample_is_app_limited()
80    {
81        // Since minmax filter is based on time,
82        // start_time + (round_count as seconds) is used instead.
83        r.bbr_state.btlbw = r.bbr_state.btlbwfilter.running_max(
84            BTLBW_FILTER_LEN,
85            r.bbr_state.start_time + Duration::from_secs(r.bbr_state.round_count),
86            r.delivery_rate().to_bytes_per_second(),
87        );
88    }
89}
90
91// 4.1.1.3 Tracking Time for the BBR.BtlBw Max Filter
92fn bbr_update_round(r: &mut Congestion, packet: &Acked) {
93    let bbr = &mut r.bbr_state;
94
95    if packet.delivered >= bbr.next_round_delivered {
96        bbr.next_round_delivered = r.delivery_rate.delivered();
97        bbr.round_count += 1;
98        bbr.round_start = true;
99        bbr.packet_conservation = false;
100    } else {
101        bbr.round_start = false;
102    }
103}
104
105// 4.1.2.3. Updating the BBR.RTprop Min Filter
106fn bbr_update_rtprop(r: &mut Congestion, now: Instant) {
107    let bbr = &mut r.bbr_state;
108    let rs_rtt = r.delivery_rate.sample_rtt();
109
110    bbr.rtprop_expired = now > bbr.rtprop_stamp + RTPROP_FILTER_LEN;
111
112    if !rs_rtt.is_zero() && (rs_rtt <= bbr.rtprop || bbr.rtprop_expired) {
113        bbr.rtprop = rs_rtt;
114        bbr.rtprop_stamp = now;
115    }
116}
117
118// 4.2.2 Send Quantum
119fn bbr_set_send_quantum(r: &mut Congestion) {
120    let rate = r.bbr_state.pacing_rate;
121
122    r.send_quantum = match rate {
123        rate if rate < PACING_RATE_1_2MBPS => r.max_datagram_size,
124
125        rate if rate < PACING_RATE_24MBPS => 2 * r.max_datagram_size,
126
127        _ => cmp::min((rate / 1000_u64) as usize, 64 * 1024),
128    }
129}
130
131// 4.2.3.2 Target cwnd
132fn bbr_inflight(r: &mut Congestion, gain: f64) -> usize {
133    let bbr = &mut r.bbr_state;
134
135    if bbr.rtprop == Duration::MAX {
136        return r.max_datagram_size * r.initial_congestion_window_packets;
137    }
138
139    let quanta = 3 * r.send_quantum;
140    let estimated_bdp = bbr.btlbw as f64 * bbr.rtprop.as_secs_f64();
141
142    (gain * estimated_bdp) as usize + quanta
143}
144
145fn bbr_update_target_cwnd(r: &mut Congestion) {
146    r.bbr_state.target_cwnd = bbr_inflight(r, r.bbr_state.cwnd_gain);
147}
148
149// 4.2.3.4 Modulating cwnd in Loss Recovery
150pub fn bbr_save_cwnd(r: &mut Congestion) -> usize {
151    if !r.bbr_state.in_recovery && r.bbr_state.state != BBRStateMachine::ProbeRTT
152    {
153        r.congestion_window
154    } else {
155        r.congestion_window.max(r.bbr_state.prior_cwnd)
156    }
157}
158
159pub fn bbr_restore_cwnd(r: &mut Congestion) {
160    r.congestion_window = r.congestion_window.max(r.bbr_state.prior_cwnd);
161}
162
163fn bbr_modulate_cwnd_for_recovery(r: &mut Congestion, bytes_in_flight: usize) {
164    let acked_bytes = r.bbr_state.newly_acked_bytes;
165    let lost_bytes = r.bbr_state.newly_lost_bytes;
166
167    if lost_bytes > 0 {
168        // QUIC mininum cwnd is 2 x MSS.
169        r.congestion_window = r
170            .congestion_window
171            .saturating_sub(lost_bytes)
172            .max(r.max_datagram_size * MINIMUM_WINDOW_PACKETS);
173    }
174
175    if r.bbr_state.packet_conservation {
176        r.congestion_window =
177            r.congestion_window.max(bytes_in_flight + acked_bytes);
178    }
179}
180
181// 4.2.3.5 Modulating cwnd in ProbeRTT
182fn bbr_modulate_cwnd_for_probe_rtt(r: &mut Congestion) {
183    if r.bbr_state.state == BBRStateMachine::ProbeRTT {
184        r.congestion_window = r.congestion_window.min(bbr_min_pipe_cwnd(r))
185    }
186}
187
188// 4.2.3.6 Core cwnd Adjustment Mechanism
189fn bbr_set_cwnd(r: &mut Congestion, bytes_in_flight: usize) {
190    let acked_bytes = r.bbr_state.newly_acked_bytes;
191
192    bbr_update_target_cwnd(r);
193    bbr_modulate_cwnd_for_recovery(r, bytes_in_flight);
194
195    if !r.bbr_state.packet_conservation {
196        if r.bbr_state.filled_pipe {
197            r.congestion_window = cmp::min(
198                r.congestion_window + acked_bytes,
199                r.bbr_state.target_cwnd,
200            )
201        } else if r.congestion_window < r.bbr_state.target_cwnd ||
202            r.delivery_rate.delivered() <
203                r.max_datagram_size * r.initial_congestion_window_packets
204        {
205            r.congestion_window += acked_bytes;
206        }
207
208        r.congestion_window = r.congestion_window.max(bbr_min_pipe_cwnd(r))
209    }
210
211    bbr_modulate_cwnd_for_probe_rtt(r);
212}
213
214// 4.3.2.2.  Estimating When Startup has Filled the Pipe
215fn bbr_check_full_pipe(r: &mut Congestion) {
216    // No need to check for a full pipe now.
217    if r.bbr_state.filled_pipe ||
218        !r.bbr_state.round_start ||
219        r.delivery_rate.sample_is_app_limited()
220    {
221        return;
222    }
223
224    // BBR.BtlBw still growing?
225    if r.bbr_state.btlbw >=
226        (r.bbr_state.full_bw as f64 * BTLBW_GROWTH_TARGET) as u64
227    {
228        // record new baseline level
229        r.bbr_state.full_bw = r.bbr_state.btlbw;
230        r.bbr_state.full_bw_count = 0;
231        return;
232    }
233
234    // another round w/o much growth
235    r.bbr_state.full_bw_count += 1;
236
237    if r.bbr_state.full_bw_count >= 3 {
238        r.bbr_state.filled_pipe = true;
239    }
240}
241
242// 4.3.3.  Drain
243fn bbr_enter_drain(r: &mut Congestion) {
244    let bbr = &mut r.bbr_state;
245
246    bbr.state = BBRStateMachine::Drain;
247
248    // pace slowly
249    bbr.pacing_gain = 1.0 / BBR_HIGH_GAIN;
250
251    // maintain cwnd
252    bbr.cwnd_gain = BBR_HIGH_GAIN;
253}
254
255fn bbr_check_drain(r: &mut Congestion, bytes_in_flight: usize, now: Instant) {
256    if r.bbr_state.state == BBRStateMachine::Startup && r.bbr_state.filled_pipe {
257        bbr_enter_drain(r);
258    }
259
260    if r.bbr_state.state == BBRStateMachine::Drain &&
261        bbr_bytes_in_net(r, bytes_in_flight, now) <= bbr_inflight(r, 1.0)
262    {
263        // we estimate queue is drained
264        bbr_enter_probe_bw(r, now);
265    }
266}
267
268/// Estimates the number of bytes "in the network" based on pacing.
269///
270/// When pacing is implemented at lower layers (e.g. when TX_TIME and the sch_fq
271/// qdisc are used), the in-flight data can be higher than the amount data
272/// actually sent on the network.
273///
274/// This is largely based on the [`bbr_packets_in_net_at_edt()`] function from
275/// Linux' BBR implementation.
276///
277/// [`bbr_packets_in_net_at_edt()`]: https://elixir.bootlin.com/linux/v6.13.7/source/net/ipv4/tcp_bbr.c#L437
278fn bbr_bytes_in_net(r: &Congestion, in_flight: usize, now: Instant) -> usize {
279    let edt = r.pacer.next_time().max(now);
280    let interval = edt.saturating_duration_since(now);
281    let interval_delivered = interval.as_secs_f64() * r.bbr_state.btlbw as f64;
282
283    let mut in_flight_at_edt = in_flight;
284
285    if r.bbr_state.pacing_gain > 1.0 {
286        in_flight_at_edt += r.send_quantum;
287    }
288
289    in_flight_at_edt.saturating_sub(interval_delivered as usize)
290}
291
292// 4.3.4.3.  Gain Cycling Algorithm
293fn bbr_enter_probe_bw(r: &mut Congestion, now: Instant) {
294    let bbr = &mut r.bbr_state;
295
296    bbr.state = BBRStateMachine::ProbeBW;
297    bbr.pacing_gain = 1.0;
298    bbr.cwnd_gain = 2.0;
299
300    // cycle_index will be one of (1, 2, 3, 4, 5, 6, 7). Since
301    // bbr_advance_cycle_phase() is called right next and it will
302    // increase cycle_index by 1, the actual cycle_index in the
303    // beginning of ProbeBW will be one of (2, 3, 4, 5, 6, 7, 0)
304    // to avoid index 1 (pacing_gain=3/4). See 4.3.4.2 for details.
305    bbr.cycle_index = BBR_GAIN_CYCLE_LEN -
306        1 -
307        (rand::rand_u64_uniform(BBR_GAIN_CYCLE_LEN as u64 - 1) as usize);
308
309    bbr_advance_cycle_phase(r, now);
310}
311
312fn bbr_check_cycle_phase(r: &mut Congestion, now: Instant) {
313    let bbr = &mut r.bbr_state;
314
315    if bbr.state == BBRStateMachine::ProbeBW && bbr_is_next_cycle_phase(r, now) {
316        bbr_advance_cycle_phase(r, now);
317    }
318}
319
320fn bbr_advance_cycle_phase(r: &mut Congestion, now: Instant) {
321    let bbr = &mut r.bbr_state;
322
323    bbr.cycle_stamp = now;
324    bbr.cycle_index = (bbr.cycle_index + 1) % BBR_GAIN_CYCLE_LEN;
325    bbr.pacing_gain = PACING_GAIN_CYCLE[bbr.cycle_index];
326}
327
328fn bbr_is_next_cycle_phase(r: &mut Congestion, now: Instant) -> bool {
329    let bbr = &mut r.bbr_state;
330    let lost_bytes = bbr.newly_lost_bytes;
331    let pacing_gain = bbr.pacing_gain;
332
333    let is_full_length = (now - bbr.cycle_stamp) > bbr.rtprop;
334
335    let prior_in_flight = bbr.prior_bytes_in_flight;
336    let prior_in_flight = bbr_bytes_in_net(r, prior_in_flight, now);
337
338    // pacing_gain == 1.0
339    if (pacing_gain - 1.0).abs() < f64::EPSILON {
340        return is_full_length;
341    }
342
343    if pacing_gain > 1.0 {
344        return is_full_length &&
345            (lost_bytes > 0 ||
346                prior_in_flight >= bbr_inflight(r, pacing_gain));
347    }
348
349    is_full_length || prior_in_flight <= bbr_inflight(r, 1.0)
350}
351
352// 4.3.5.  ProbeRTT
353fn bbr_check_probe_rtt(r: &mut Congestion, bytes_in_flight: usize, now: Instant) {
354    if r.bbr_state.state != BBRStateMachine::ProbeRTT &&
355        r.bbr_state.rtprop_expired &&
356        !r.bbr_state.idle_restart
357    {
358        bbr_enter_probe_rtt(r);
359
360        r.bbr_state.prior_cwnd = bbr_save_cwnd(r);
361        r.bbr_state.probe_rtt_done_stamp = None;
362    }
363
364    if r.bbr_state.state == BBRStateMachine::ProbeRTT {
365        bbr_handle_probe_rtt(r, bytes_in_flight, now);
366    }
367
368    r.bbr_state.idle_restart = false;
369}
370
371fn bbr_enter_probe_rtt(r: &mut Congestion) {
372    let bbr = &mut r.bbr_state;
373
374    bbr.state = BBRStateMachine::ProbeRTT;
375    bbr.pacing_gain = 1.0;
376    bbr.cwnd_gain = 1.0;
377}
378
379fn bbr_handle_probe_rtt(
380    r: &mut Congestion, bytes_in_flight: usize, now: Instant,
381) {
382    // Ignore low rate samples during ProbeRTT.
383    r.delivery_rate.update_app_limited(true);
384
385    if let Some(probe_rtt_done_stamp) = r.bbr_state.probe_rtt_done_stamp {
386        if r.bbr_state.round_start {
387            r.bbr_state.probe_rtt_round_done = true;
388        }
389
390        if r.bbr_state.probe_rtt_round_done && now > probe_rtt_done_stamp {
391            r.bbr_state.rtprop_stamp = now;
392
393            bbr_restore_cwnd(r);
394            bbr_exit_probe_rtt(r, now);
395        }
396    } else if bytes_in_flight <= bbr_min_pipe_cwnd(r) {
397        r.bbr_state.probe_rtt_done_stamp = Some(now + PROBE_RTT_DURATION);
398        r.bbr_state.probe_rtt_round_done = false;
399        r.bbr_state.next_round_delivered = r.delivery_rate.delivered();
400    }
401}
402
403fn bbr_exit_probe_rtt(r: &mut Congestion, now: Instant) {
404    if r.bbr_state.filled_pipe {
405        bbr_enter_probe_bw(r, now);
406    } else {
407        init::bbr_enter_startup(r);
408    }
409}