quiche/recovery/congestion/bbr2/mod.rs
1// Redistribution and use in source and binary forms, with or without
2// modification, are permitted provided that the following conditions are
3// met:
4//
5// * Redistributions of source code must retain the above copyright notice,
6// this list of conditions and the following disclaimer.
7//
8// * Redistributions in binary form must reproduce the above copyright
9// notice, this list of conditions and the following disclaimer in the
10// documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
13// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
14// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
16// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
17// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
18// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
20// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24//! BBR v2 Congestion Control
25//!
26//! This implementation is based on the following draft:
27//! <https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-02>
28
29use super::*;
30use crate::minmax::Minmax;
31
32use std::time::Duration;
33use std::time::Instant;
34
35use super::CongestionControlOps;
36
37pub(crate) static BBR2: CongestionControlOps = CongestionControlOps {
38 on_init,
39 on_packet_sent,
40 on_packets_acked,
41 congestion_event,
42 checkpoint,
43 rollback,
44 has_custom_pacing,
45 #[cfg(feature = "qlog")]
46 state_str,
47 debug_fmt,
48};
49
50/// The static discount factor of 1% used to scale BBR.bw to produce
51/// BBR.pacing_rate.
52const PACING_MARGIN_PERCENT: f64 = 0.01;
53
54/// A constant specifying the minimum gain value
55/// for calculating the pacing rate that will allow the sending rate to
56/// double each round (4*ln(2) ~=2.77 ) BBRStartupPacingGain; used in
57/// Startup mode for BBR.pacing_gain.
58const STARTUP_PACING_GAIN: f64 = 2.77;
59
60/// A constant specifying the pacing gain value for Probe Down mode.
61const PROBE_DOWN_PACING_GAIN: f64 = 3_f64 / 4_f64;
62
63/// A constant specifying the pacing gain value for Probe Up mode.
64const PROBE_UP_PACING_GAIN: f64 = 5_f64 / 4_f64;
65
66/// A constant specifying the pacing gain value for Probe Refill, Probe RTT,
67/// Cruise mode.
68const PACING_GAIN: f64 = 1.0;
69
70/// A constant specifying the minimum gain value for the cwnd in the Startup
71/// phase
72const STARTUP_CWND_GAIN: f64 = 2.77;
73
74/// A constant specifying the minimum gain value for
75/// calculating the cwnd that will allow the sending rate to double each
76/// round (2.0); used in Probe and Drain mode for BBR.cwnd_gain.
77const CWND_GAIN: f64 = 2.0;
78
79/// The maximum tolerated per-round-trip packet loss rate
80/// when probing for bandwidth (the default is 2%).
81const LOSS_THRESH: f64 = 0.02;
82
83/// Exit startup if the number of loss marking events is >=FULL_LOSS_COUNT
84const FULL_LOSS_COUNT: u32 = 8;
85
86/// The default multiplicative decrease to make upon each round
87/// trip during which the connection detects packet loss (the value is
88/// 0.7).
89const BETA: f64 = 0.7;
90
91/// The multiplicative factor to apply to BBR.inflight_hi
92/// when attempting to leave free headroom in the path (e.g. free space
93/// in the bottleneck buffer or free time slots in the bottleneck link)
94/// that can be used by cross traffic (the value is 0.85).
95const HEADROOM: f64 = 0.85;
96
97/// The minimal cwnd value BBR targets, to allow
98/// pipelining with TCP endpoints that follow an "ACK every other packet"
99/// delayed-ACK policy: 4 * SMSS.
100const MIN_PIPE_CWND_PKTS: usize = 4;
101
102// To do: Tune window for expiry of Max BW measurement
103// The filter window length for BBR.MaxBwFilter = 2 (representing up to 2
104// ProbeBW cycles, the current cycle and the previous full cycle).
105// const MAX_BW_FILTER_LEN: Duration = Duration::from_secs(2);
106
107// To do: Tune window for expiry of ACK aggregation measurement
108// The window length of the BBR.ExtraACKedFilter max filter window: 10 (in
109// units of packet-timed round trips).
110// const EXTRA_ACKED_FILTER_LEN: Duration = Duration::from_secs(10);
111
112/// A constant specifying the length of the BBR.min_rtt min filter window,
113/// MinRTTFilterLen is 10 secs.
114const MIN_RTT_FILTER_LEN: u32 = 1;
115
116/// A constant specifying the gain value for calculating the cwnd during
117/// ProbeRTT: 0.5 (meaning that ProbeRTT attempts to reduce in-flight data to
118/// 50% of the estimated BDP).
119const PROBE_RTT_CWND_GAIN: f64 = 0.5;
120
121/// A constant specifying the minimum duration for which ProbeRTT state holds
122/// inflight to BBRMinPipeCwnd or fewer packets: 200 ms.
123const PROBE_RTT_DURATION: Duration = Duration::from_millis(200);
124
125/// ProbeRTTInterval: A constant specifying the minimum time interval between
126/// ProbeRTT states. To do: investigate probe duration. Set arbitrarily high for
127/// now.
128const PROBE_RTT_INTERVAL: Duration = Duration::from_secs(86400);
129
130/// Threshold for checking a full bandwidth growth during Startup.
131const MAX_BW_GROWTH_THRESHOLD: f64 = 1.25;
132
133/// Threshold for determining maximum bandwidth of network during Startup.
134const MAX_BW_COUNT: usize = 3;
135
136/// BBR2 Internal State Machine.
137#[derive(Debug, PartialEq, Eq, Copy, Clone)]
138enum BBR2StateMachine {
139 Startup,
140 Drain,
141 ProbeBWDOWN,
142 ProbeBWCRUISE,
143 ProbeBWREFILL,
144 ProbeBWUP,
145 ProbeRTT,
146}
147
148impl From<BBR2StateMachine> for &'static str {
149 fn from(state: BBR2StateMachine) -> &'static str {
150 match state {
151 BBR2StateMachine::Startup => "bbr_startup",
152 BBR2StateMachine::Drain => "bbr_drain",
153 BBR2StateMachine::ProbeBWDOWN => "bbr_probe_bw_down",
154 BBR2StateMachine::ProbeBWCRUISE => "bbr_probe_bw_cruise",
155 BBR2StateMachine::ProbeBWREFILL => "bbr_probe_bw_refill",
156 BBR2StateMachine::ProbeBWUP => "bbr_probe_bw_up",
157 BBR2StateMachine::ProbeRTT => "bbr_probe_rtt",
158 }
159 }
160}
161
162/// BBR2 Ack Phases.
163#[derive(Debug, PartialEq, Eq)]
164enum BBR2AckPhase {
165 Init,
166 ProbeFeedback,
167 ProbeStarting,
168 ProbeStopping,
169 Refilling,
170}
171
172/// BBR2 Specific State Variables.
173pub struct State {
174 // 2.3. Per-ACK Rate Sample State
175 // It's stored in rate sample but we keep in BBR state here.
176
177 // The volume of data that was estimated to be in
178 // flight at the time of the transmission of the packet that has just
179 // been ACKed.
180 tx_in_flight: usize,
181
182 // The volume of data that was declared lost between the
183 // transmission and acknowledgement of the packet that has just been
184 // ACKed.
185 lost: usize,
186
187 // The volume of data cumulatively or selectively acknowledged upon the ACK
188 // that was just received. (This quantity is referred to as "DeliveredData"
189 // in [RFC6937].)
190 newly_acked_bytes: usize,
191
192 // The volume of data newly marked lost upon the ACK that was just received.
193 newly_lost_bytes: usize,
194
195 // 2.4. Output Control Parameters
196 // The current pacing rate for a BBR2 flow, which controls inter-packet
197 // spacing.
198 pacing_rate: u64,
199
200 // Save initial pacing rate so we can update when more reliable bytes
201 // delivered and RTT samples are available
202 init_pacing_rate: u64,
203
204 // 2.5. Pacing State and Parameters
205 // The dynamic gain factor used to scale BBR.bw to
206 // produce BBR.pacing_rate.
207 pacing_gain: f64,
208
209 // 2.6. cwnd State and Parameters
210 // The dynamic gain factor used to scale the estimated BDP to produce a
211 // congestion window (cwnd).
212 cwnd_gain: f64,
213
214 // A boolean indicating whether BBR is currently using packet conservation
215 // dynamics to bound cwnd.
216 packet_conservation: bool,
217
218 // 2.7. General Algorithm State
219 // The current state of a BBR2 flow in the BBR2 state machine.
220 state: BBR2StateMachine,
221
222 // Count of packet-timed round trips elapsed so far.
223 round_count: u64,
224
225 // A boolean that BBR2 sets to true once per packet-timed round trip,
226 // on ACKs that advance BBR2.round_count.
227 round_start: bool,
228
229 // packet.delivered value denoting the end of a packet-timed round trip.
230 next_round_delivered: usize,
231
232 // A boolean that is true if and only if a connection is restarting after
233 // being idle.
234 idle_restart: bool,
235
236 // 2.9.1. Data Rate Network Path Model Parameters
237 // The windowed maximum recent bandwidth sample - obtained using the BBR
238 // delivery rate sampling algorithm
239 // [draft-cheng-iccrg-delivery-rate-estimation] - measured during the current
240 // or previous bandwidth probing cycle (or during Startup, if the flow is
241 // still in that state). (Part of the long-term model.)
242 max_bw: u64,
243
244 // The long-term maximum sending bandwidth that the algorithm estimates will
245 // produce acceptable queue pressure, based on signals in the current or
246 // previous bandwidth probing cycle, as measured by loss. (Part of the
247 // long-term model.)
248 bw_hi: u64,
249
250 // The short-term maximum sending bandwidth that the algorithm estimates is
251 // safe for matching the current network path delivery rate, based on any
252 // loss signals in the current bandwidth probing cycle. This is generally
253 // lower than max_bw or bw_hi (thus the name). (Part of the short-term
254 // model.)
255 bw_lo: u64,
256
257 // The maximum sending bandwidth that the algorithm estimates is appropriate
258 // for matching the current network path delivery rate, given all available
259 // signals in the model, at any time scale. It is the min() of max_bw,
260 // bw_hi, and bw_lo.
261 bw: u64,
262
263 // 2.9.2. Data Volume Network Path Model Parameters
264 // The windowed minimum round-trip time sample measured over the last
265 // MinRTTFilterLen = 10 seconds. This attempts to estimate the two-way
266 // propagation delay of the network path when all connections sharing a
267 // bottleneck are using BBR, but also allows BBR to estimate the value
268 // required for a bdp estimate that allows full throughput if there are
269 // legacy loss-based Reno or CUBIC flows sharing the bottleneck.
270 min_rtt: Duration,
271
272 // The estimate of the network path's BDP (Bandwidth-Delay Product), computed
273 // as: BBR.bdp = BBR.bw * BBR.min_rtt.
274 bdp: usize,
275
276 // A volume of data that is the estimate of the recent degree of aggregation
277 // in the network path.
278 extra_acked: usize,
279
280 // The estimate of the minimum volume of data necessary to achieve full
281 // throughput when using sender (TSO/GSO) and receiver (LRO, GRO) host
282 // offload mechanisms.
283 offload_budget: usize,
284
285 // The estimate of the volume of in-flight data required to fully utilize the
286 // bottleneck bandwidth available to the flow, based on the BDP estimate
287 // (BBR.bdp), the aggregation estimate (BBR.extra_acked), the offload budget
288 // (BBR.offload_budget), and BBRMinPipeCwnd.
289 max_inflight: usize,
290
291 // Analogous to BBR.bw_hi, the long-term maximum volume of in-flight data
292 // that the algorithm estimates will produce acceptable queue pressure, based
293 // on signals in the current or previous bandwidth probing cycle, as measured
294 // by loss. That is, if a flow is probing for bandwidth, and observes that
295 // sending a particular volume of in-flight data causes a loss rate higher
296 // than the loss rate objective, it sets inflight_hi to that volume of data.
297 // (Part of the long-term model.)
298 inflight_hi: usize,
299
300 // Analogous to BBR.bw_lo, the short-term maximum volume of in-flight data
301 // that the algorithm estimates is safe for matching the current network path
302 // delivery process, based on any loss signals in the current bandwidth
303 // probing cycle. This is generally lower than max_inflight or inflight_hi
304 // (thus the name). (Part of the short-term model.)
305 inflight_lo: usize,
306
307 // 2.10. State for Responding to Congestion
308 // a 1-round-trip max of delivered bandwidth (rs.delivery_rate).
309 bw_latest: u64,
310
311 // a 1-round-trip max of delivered volume of data (rs.delivered).
312 inflight_latest: usize,
313
314 // 2.11. Estimating BBR.max_bw
315 // The filter for tracking the maximum recent rs.delivery_rate sample, for
316 // estimating BBR.max_bw.
317 max_bw_filter: Minmax<u64>,
318
319 // The virtual time used by the BBR.max_bw filter window. Note that
320 // BBR.cycle_count only needs to be tracked with a single bit, since the
321 // BBR.MaxBwFilter only needs to track samples from two time slots: the
322 // previous ProbeBW cycle and the current ProbeBW cycle.
323 cycle_count: u64,
324
325 // 2.12. Estimating BBR.extra_acked
326 // the start of the time interval for estimating the excess amount of data
327 // acknowledged due to aggregation effects.
328 extra_acked_interval_start: Instant,
329
330 // the volume of data marked as delivered since
331 // BBR.extra_acked_interval_start.
332 extra_acked_delivered: usize,
333
334 // BBR.ExtraACKedFilter: the max filter tracking the recent maximum degree of
335 // aggregation in the path.
336 extra_acked_filter: Minmax<usize>,
337
338 // 2.13. Startup Parameters and State
339 // A boolean that records whether BBR estimates that it has ever fully
340 // utilized its available bandwidth ("filled the pipe").
341 filled_pipe: bool,
342
343 // A recent baseline BBR.max_bw to estimate if BBR has "filled the pipe" in
344 // Startup.
345 full_bw: u64,
346
347 // The number of non-app-limited round trips without large increases in
348 // BBR.full_bw.
349 full_bw_count: usize,
350
351 // 2.14.1. Parameters for Estimating BBR.min_rtt
352 // The wall clock time at which the current BBR.min_rtt sample was obtained.
353 min_rtt_stamp: Instant,
354
355 // 2.14.2. Parameters for Scheduling ProbeRTT
356 // The minimum RTT sample recorded in the last ProbeRTTInterval.
357 probe_rtt_min_delay: Duration,
358
359 // The wall clock time at which the current BBR.probe_rtt_min_delay sample
360 // was obtained.
361 probe_rtt_min_stamp: Instant,
362
363 // A boolean recording whether the BBR.probe_rtt_min_delay has expired and is
364 // due for a refresh with an application idle period or a transition into
365 // ProbeRTT state.
366 probe_rtt_expired: bool,
367
368 // Others
369 // A state indicating we are in the recovery.
370 in_recovery: bool,
371
372 // Start time of the connection.
373 start_time: Instant,
374
375 // Saved cwnd before loss recovery.
376 prior_cwnd: usize,
377
378 // Whether we have a bandwidth probe samples.
379 bw_probe_samples: bool,
380
381 // Others
382 probe_up_cnt: usize,
383
384 prior_bytes_in_flight: usize,
385
386 probe_rtt_done_stamp: Option<Instant>,
387
388 probe_rtt_round_done: bool,
389
390 bw_probe_wait: Duration,
391
392 rounds_since_probe: usize,
393
394 cycle_stamp: Instant,
395
396 ack_phase: BBR2AckPhase,
397
398 bw_probe_up_rounds: usize,
399
400 bw_probe_up_acks: usize,
401
402 loss_round_start: bool,
403
404 loss_round_delivered: usize,
405
406 loss_in_round: bool,
407
408 loss_events_in_round: usize,
409}
410
411impl State {
412 pub fn new() -> Self {
413 let now = Instant::now();
414
415 State {
416 tx_in_flight: 0,
417
418 lost: 0,
419
420 newly_acked_bytes: 0,
421
422 newly_lost_bytes: 0,
423
424 pacing_rate: 0,
425
426 init_pacing_rate: 0,
427
428 pacing_gain: 0.0,
429
430 cwnd_gain: 0.0,
431
432 packet_conservation: false,
433
434 state: BBR2StateMachine::Startup,
435
436 round_count: 0,
437
438 round_start: false,
439
440 next_round_delivered: 0,
441
442 idle_restart: false,
443
444 max_bw: 0,
445
446 bw_hi: u64::MAX,
447
448 bw_lo: u64::MAX,
449
450 bw: 0,
451
452 min_rtt: Duration::MAX,
453
454 bdp: 0,
455
456 extra_acked: 0,
457
458 offload_budget: 0,
459
460 max_inflight: 0,
461
462 inflight_hi: usize::MAX,
463
464 inflight_lo: usize::MAX,
465
466 bw_latest: 0,
467
468 inflight_latest: 0,
469
470 max_bw_filter: Minmax::new(0),
471
472 cycle_count: 0,
473
474 extra_acked_interval_start: now,
475
476 extra_acked_delivered: 0,
477
478 extra_acked_filter: Minmax::new(0),
479
480 filled_pipe: false,
481
482 full_bw: 0,
483
484 full_bw_count: 0,
485
486 min_rtt_stamp: now,
487
488 probe_rtt_min_delay: Duration::MAX,
489
490 probe_rtt_min_stamp: now,
491
492 probe_rtt_expired: false,
493
494 in_recovery: false,
495
496 start_time: now,
497
498 prior_cwnd: 0,
499
500 bw_probe_samples: false,
501
502 probe_up_cnt: 0,
503
504 prior_bytes_in_flight: 0,
505
506 probe_rtt_done_stamp: None,
507
508 probe_rtt_round_done: false,
509
510 bw_probe_wait: Duration::ZERO,
511
512 rounds_since_probe: 0,
513
514 cycle_stamp: now,
515
516 ack_phase: BBR2AckPhase::Init,
517
518 bw_probe_up_rounds: 0,
519
520 bw_probe_up_acks: 0,
521
522 loss_round_start: false,
523
524 loss_round_delivered: 0,
525
526 loss_in_round: false,
527
528 loss_events_in_round: 0,
529 }
530 }
531}
532
533// When entering the recovery episode.
534fn bbr2_enter_recovery(r: &mut Congestion, in_flight: usize, now: Instant) {
535 r.bbr2_state.prior_cwnd = per_ack::bbr2_save_cwnd(r);
536
537 r.congestion_window =
538 in_flight + r.bbr2_state.newly_acked_bytes.max(r.max_datagram_size);
539 r.congestion_recovery_start_time = Some(now);
540
541 r.bbr2_state.packet_conservation = true;
542 r.bbr2_state.in_recovery = true;
543
544 // Start round now.
545 r.bbr2_state.next_round_delivered = r.delivery_rate.delivered();
546}
547
548// When exiting the recovery episode.
549fn bbr2_exit_recovery(r: &mut Congestion) {
550 r.congestion_recovery_start_time = None;
551
552 r.bbr2_state.packet_conservation = false;
553 r.bbr2_state.in_recovery = false;
554
555 per_ack::bbr2_restore_cwnd(r);
556}
557
558// Congestion Control Hooks.
559//
560fn on_init(r: &mut Congestion) {
561 init::bbr2_init(r);
562}
563
564fn on_packet_sent(
565 r: &mut Congestion, _sent_bytes: usize, bytes_in_flight: usize, now: Instant,
566) {
567 per_transmit::bbr2_on_transmit(r, bytes_in_flight, now);
568}
569
570fn on_packets_acked(
571 r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,
572 now: Instant, _rtt_stats: &RttStats,
573) {
574 r.bbr2_state.newly_acked_bytes = 0;
575
576 let time_sent = packets.last().map(|pkt| pkt.time_sent);
577
578 r.bbr2_state.prior_bytes_in_flight = bytes_in_flight;
579 let mut bytes_in_flight = bytes_in_flight;
580
581 for p in packets.drain(..) {
582 per_ack::bbr2_update_model_and_state(r, &p, bytes_in_flight, now);
583
584 r.bbr2_state.prior_bytes_in_flight = bytes_in_flight;
585 bytes_in_flight -= p.size;
586
587 r.bbr2_state.newly_acked_bytes += p.size;
588 }
589
590 if let Some(ts) = time_sent {
591 if !r.in_congestion_recovery(ts) {
592 // Upon exiting loss recovery.
593 bbr2_exit_recovery(r);
594 }
595 }
596
597 per_ack::bbr2_update_control_parameters(r, bytes_in_flight, now);
598
599 r.bbr2_state.newly_lost_bytes = 0;
600}
601
602fn congestion_event(
603 r: &mut Congestion, bytes_in_flight: usize, lost_bytes: usize,
604 largest_lost_pkt: &Sent, now: Instant,
605) {
606 r.bbr2_state.newly_lost_bytes = lost_bytes;
607
608 per_loss::bbr2_update_on_loss(r, largest_lost_pkt, lost_bytes, now);
609
610 // Upon entering Fast Recovery.
611 if !r.in_congestion_recovery(largest_lost_pkt.time_sent) {
612 // Upon entering Fast Recovery.
613 bbr2_enter_recovery(r, bytes_in_flight - lost_bytes, now);
614 }
615}
616
617fn checkpoint(_r: &mut Congestion) {}
618
619fn rollback(_r: &mut Congestion) -> bool {
620 false
621}
622
623fn has_custom_pacing() -> bool {
624 true
625}
626
627// rate -> kbit/sec. if inf, return -1
628fn rate_kbps(rate: u64) -> isize {
629 if rate == u64::MAX {
630 -1
631 } else {
632 (rate * 8 / 1000) as isize
633 }
634}
635
636#[cfg(feature = "qlog")]
637fn state_str(r: &Congestion, _now: Instant) -> &'static str {
638 r.bbr2_state.state.into()
639}
640
641fn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {
642 let bbr = &r.bbr2_state;
643
644 write!(f, "bbr2={{ ")?;
645 write!(
646 f,
647 "state={:?} in_recovery={} ack_phase={:?} filled_pipe={} full_bw_count={} loss_events_in_round={} ",
648 bbr.state, bbr.in_recovery, bbr.ack_phase, bbr.filled_pipe, bbr.full_bw_count, bbr.loss_events_in_round
649 )?;
650 write!(
651 f,
652 "send_quantum={} extra_acked={} min_rtt={:?} round_start={} ",
653 r.send_quantum, bbr.extra_acked, bbr.min_rtt, bbr.round_start
654 )?;
655 write!(
656 f,
657 "max_bw={}kbps bw_lo={}kbps bw={}kbps bw_hi={}kbps full_bw={}kbps ",
658 rate_kbps(bbr.max_bw),
659 rate_kbps(bbr.bw_lo),
660 rate_kbps(bbr.bw),
661 rate_kbps(bbr.bw_hi),
662 rate_kbps(bbr.full_bw)
663 )?;
664 write!(
665 f,
666 "inflight_lo={} inflight_hi={} max_inflight={} ",
667 bbr.inflight_lo, bbr.inflight_hi, bbr.max_inflight
668 )?;
669 write!(
670 f,
671 "probe_up_cnt={} bw_probe_samples={} ",
672 bbr.probe_up_cnt, bbr.bw_probe_samples
673 )?;
674 write!(f, "}}")
675}
676
677// TODO: write more tests
678#[cfg(test)]
679mod tests {
680 use super::*;
681
682 use smallvec::smallvec;
683
684 use crate::packet;
685 use crate::ranges;
686 use crate::recovery::congestion::recovery::LegacyRecovery;
687 use crate::recovery::HandshakeStatus;
688 use crate::recovery::RecoveryOps;
689 use crate::CongestionControlAlgorithm;
690 use crate::OnAckReceivedOutcome;
691
692 #[test]
693 fn bbr_init() {
694 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
695 cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
696
697 let r = LegacyRecovery::new(&cfg);
698
699 // on_init() is called in Connection::new(), so it need to be
700 // called manually here.
701
702 assert_eq!(
703 r.cwnd(),
704 r.max_datagram_size * r.congestion.initial_congestion_window_packets
705 );
706 assert_eq!(r.bytes_in_flight(), 0);
707
708 assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Startup);
709 }
710
711 #[test]
712 fn bbr2_startup() {
713 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
714 cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
715
716 let mut r = LegacyRecovery::new(&cfg);
717 let now = Instant::now();
718 let mss = r.max_datagram_size;
719
720 // Send 5 packets.
721 for pn in 0..5 {
722 let pkt = Sent {
723 pkt_num: pn,
724 frames: smallvec![],
725 time_sent: now,
726 time_acked: None,
727 time_lost: None,
728 size: mss,
729 ack_eliciting: true,
730 in_flight: true,
731 delivered: 0,
732 delivered_time: now,
733 first_sent_time: now,
734 is_app_limited: false,
735 tx_in_flight: 0,
736 lost: 0,
737 has_data: false,
738 is_pmtud_probe: false,
739 };
740
741 r.on_packet_sent(
742 pkt,
743 packet::Epoch::Application,
744 HandshakeStatus::default(),
745 now,
746 "",
747 );
748 }
749
750 let rtt = Duration::from_millis(50);
751 let now = now + rtt;
752 let cwnd_prev = r.cwnd();
753
754 let mut acked = ranges::RangeSet::default();
755 acked.insert(0..5);
756
757 assert_eq!(
758 r.on_ack_received(
759 &acked,
760 25,
761 packet::Epoch::Application,
762 HandshakeStatus::default(),
763 now,
764 None,
765 "",
766 )
767 .unwrap(),
768 OnAckReceivedOutcome {
769 lost_packets: 0,
770 lost_bytes: 0,
771 acked_bytes: mss * 5,
772 spurious_losses: 0,
773 }
774 );
775
776 assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Startup);
777 assert_eq!(r.cwnd(), cwnd_prev + mss * 5);
778 assert_eq!(r.bytes_in_flight(), 0);
779 assert_eq!(
780 r.delivery_rate().to_bytes_per_second(),
781 ((mss * 5) as f64 / rtt.as_secs_f64()) as u64
782 );
783 assert_eq!(
784 r.congestion.bbr2_state.full_bw,
785 r.delivery_rate().to_bytes_per_second()
786 );
787 }
788
789 #[test]
790 fn bbr2_congestion_event() {
791 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
792 cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
793
794 let mut r = LegacyRecovery::new(&cfg);
795 let now = Instant::now();
796 let mss = r.max_datagram_size;
797
798 // Send 5 packets.
799 for pn in 0..5 {
800 let pkt = Sent {
801 pkt_num: pn,
802 frames: smallvec![],
803 time_sent: now,
804 time_acked: None,
805 time_lost: None,
806 size: mss,
807 ack_eliciting: true,
808 in_flight: true,
809 delivered: 0,
810 delivered_time: now,
811 first_sent_time: now,
812 is_app_limited: false,
813 tx_in_flight: 0,
814 lost: 0,
815 has_data: false,
816 is_pmtud_probe: false,
817 };
818
819 r.on_packet_sent(
820 pkt,
821 packet::Epoch::Application,
822 HandshakeStatus::default(),
823 now,
824 "",
825 );
826 }
827
828 let rtt = Duration::from_millis(50);
829 let now = now + rtt;
830
831 // Make a packet loss to trigger a congestion event.
832 let mut acked = ranges::RangeSet::default();
833 acked.insert(4..5);
834
835 // 2 acked, 2 x MSS lost.
836 assert_eq!(
837 r.on_ack_received(
838 &acked,
839 25,
840 packet::Epoch::Application,
841 HandshakeStatus::default(),
842 now,
843 None,
844 "",
845 )
846 .unwrap(),
847 OnAckReceivedOutcome {
848 lost_packets: 2,
849 lost_bytes: 2 * mss,
850 acked_bytes: mss,
851 spurious_losses: 0,
852 }
853 );
854
855 assert!(r.congestion.bbr2_state.in_recovery);
856
857 // Still in flight: 2, 3.
858 assert_eq!(r.bytes_in_flight(), mss * 2);
859
860 assert_eq!(r.congestion.bbr2_state.newly_acked_bytes, mss);
861
862 assert_eq!(r.cwnd(), mss * 3);
863 }
864
865 #[test]
866 fn bbr2_probe_bw() {
867 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
868 cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
869
870 let mut r = LegacyRecovery::new(&cfg);
871 let now = Instant::now();
872 let mss = r.max_datagram_size;
873
874 let mut pn = 0;
875
876 // Stop right before filled_pipe=true.
877 for _ in 0..3 {
878 let pkt = Sent {
879 pkt_num: pn,
880 frames: smallvec![],
881 time_sent: now,
882 time_acked: None,
883 time_lost: None,
884 size: mss,
885 ack_eliciting: true,
886 in_flight: true,
887 delivered: r.congestion.delivery_rate.delivered(),
888 delivered_time: now,
889 first_sent_time: now,
890 is_app_limited: false,
891 tx_in_flight: 0,
892 lost: 0,
893 has_data: false,
894 is_pmtud_probe: false,
895 };
896
897 r.on_packet_sent(
898 pkt,
899 packet::Epoch::Application,
900 HandshakeStatus::default(),
901 now,
902 "",
903 );
904
905 pn += 1;
906
907 let rtt = Duration::from_millis(50);
908
909 let now = now + rtt;
910
911 let mut acked = ranges::RangeSet::default();
912 acked.insert(0..pn);
913
914 assert_eq!(
915 r.on_ack_received(
916 &acked,
917 25,
918 packet::Epoch::Application,
919 HandshakeStatus::default(),
920 now,
921 None,
922 "",
923 )
924 .unwrap(),
925 OnAckReceivedOutcome {
926 lost_packets: 0,
927 lost_bytes: 0,
928 acked_bytes: mss,
929 spurious_losses: 0,
930 }
931 );
932 }
933
934 // Stop at right before filled_pipe=true.
935 for _ in 0..5 {
936 let pkt = Sent {
937 pkt_num: pn,
938 frames: smallvec![],
939 time_sent: now,
940 time_acked: None,
941 time_lost: None,
942 size: mss,
943 ack_eliciting: true,
944 in_flight: true,
945 delivered: r.congestion.delivery_rate.delivered(),
946 delivered_time: now,
947 first_sent_time: now,
948 is_app_limited: false,
949 tx_in_flight: 0,
950 lost: 0,
951 has_data: false,
952 is_pmtud_probe: false,
953 };
954
955 r.on_packet_sent(
956 pkt,
957 packet::Epoch::Application,
958 HandshakeStatus::default(),
959 now,
960 "",
961 );
962
963 pn += 1;
964 }
965
966 let rtt = Duration::from_millis(50);
967 let now = now + rtt;
968
969 let mut acked = ranges::RangeSet::default();
970
971 // We sent 5 packets, but ack only one, so stay
972 // in Drain state.
973 acked.insert(0..pn - 4);
974
975 assert_eq!(
976 r.on_ack_received(
977 &acked,
978 25,
979 packet::Epoch::Application,
980 HandshakeStatus::default(),
981 now,
982 None,
983 "",
984 )
985 .unwrap(),
986 OnAckReceivedOutcome {
987 lost_packets: 0,
988 lost_bytes: 0,
989 acked_bytes: mss,
990 spurious_losses: 0,
991 }
992 );
993
994 assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::Drain);
995 assert!(r.congestion.bbr2_state.filled_pipe);
996 assert!(r.congestion.bbr2_state.pacing_gain < 1.0);
997 }
998
999 #[test]
1000 fn bbr2_probe_rtt() {
1001 let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
1002 cfg.set_cc_algorithm(CongestionControlAlgorithm::BBR2);
1003
1004 let mut r = LegacyRecovery::new(&cfg);
1005 let now = Instant::now();
1006 let mss = r.max_datagram_size;
1007
1008 let mut pn = 0;
1009
1010 // At 4th roundtrip, filled_pipe=true and switch to Drain,
1011 // but move to ProbeBW immediately because bytes_in_flight is
1012 // smaller than BBRInFlight(1).
1013 for _ in 0..4 {
1014 let pkt = Sent {
1015 pkt_num: pn,
1016 frames: smallvec![],
1017 time_sent: now,
1018 time_acked: None,
1019 time_lost: None,
1020 size: mss,
1021 ack_eliciting: true,
1022 in_flight: true,
1023 delivered: r.congestion.delivery_rate.delivered(),
1024 delivered_time: now,
1025 first_sent_time: now,
1026 is_app_limited: false,
1027 tx_in_flight: 0,
1028 lost: 0,
1029 has_data: false,
1030 is_pmtud_probe: false,
1031 };
1032
1033 r.on_packet_sent(
1034 pkt,
1035 packet::Epoch::Application,
1036 HandshakeStatus::default(),
1037 now,
1038 "",
1039 );
1040
1041 pn += 1;
1042
1043 let rtt = Duration::from_millis(50);
1044 let now = now + rtt;
1045
1046 let mut acked = ranges::RangeSet::default();
1047 acked.insert(0..pn);
1048
1049 assert_eq!(
1050 r.on_ack_received(
1051 &acked,
1052 25,
1053 packet::Epoch::Application,
1054 HandshakeStatus::default(),
1055 now,
1056 None,
1057 "",
1058 )
1059 .unwrap(),
1060 OnAckReceivedOutcome {
1061 lost_packets: 0,
1062 lost_bytes: 0,
1063 acked_bytes: mss,
1064 spurious_losses: 0,
1065 }
1066 );
1067 }
1068
1069 // Now we are in ProbeBW state.
1070 assert_eq!(
1071 r.congestion.bbr2_state.state,
1072 BBR2StateMachine::ProbeBWCRUISE
1073 );
1074
1075 // After RTPROP_FILTER_LEN (10s), switch to ProbeRTT.
1076 let now = now + PROBE_RTT_INTERVAL;
1077
1078 let pkt = Sent {
1079 pkt_num: pn,
1080 frames: smallvec![],
1081 time_sent: now,
1082 time_acked: None,
1083 time_lost: None,
1084 size: mss,
1085 ack_eliciting: true,
1086 in_flight: true,
1087 delivered: r.congestion.delivery_rate.delivered(),
1088 delivered_time: now,
1089 first_sent_time: now,
1090 is_app_limited: false,
1091 tx_in_flight: 0,
1092 lost: 0,
1093 has_data: false,
1094 is_pmtud_probe: false,
1095 };
1096
1097 r.on_packet_sent(
1098 pkt,
1099 packet::Epoch::Application,
1100 HandshakeStatus::default(),
1101 now,
1102 "",
1103 );
1104
1105 pn += 1;
1106
1107 // Don't update rtprop by giving larger rtt than before.
1108 // If rtprop is updated, rtprop expiry check is reset.
1109 let rtt = Duration::from_millis(100);
1110 let now = now + rtt;
1111
1112 let mut acked = ranges::RangeSet::default();
1113 acked.insert(0..pn);
1114
1115 assert_eq!(
1116 r.on_ack_received(
1117 &acked,
1118 25,
1119 packet::Epoch::Application,
1120 HandshakeStatus::default(),
1121 now,
1122 None,
1123 "",
1124 )
1125 .unwrap(),
1126 OnAckReceivedOutcome {
1127 lost_packets: 0,
1128 lost_bytes: 0,
1129 acked_bytes: mss,
1130 spurious_losses: 0,
1131 }
1132 );
1133
1134 assert_eq!(r.congestion.bbr2_state.state, BBR2StateMachine::ProbeRTT);
1135 assert_eq!(r.congestion.bbr2_state.pacing_gain, 1.0);
1136 }
1137}
1138
1139mod init;
1140mod pacing;
1141mod per_ack;
1142mod per_loss;
1143mod per_transmit;