noq_proto/congestion/bbr3/mod.rs
1mod max_filter;
2
3use crate::RttEstimator;
4use crate::congestion::bbr3::max_filter::MaxFilter;
5use crate::congestion::{Controller, ControllerFactory, ControllerMetrics};
6use crate::{Duration, Instant};
7use rand::{RngExt, SeedableRng};
8use rand_pcg::Pcg32;
9use std::any::Any;
10use std::cmp::{max, min};
11use std::collections::VecDeque;
12use std::sync::Arc;
13
14/// equivalent to BBR.MaxBwFilterLen <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.10>
15const MAX_BW_FILTER_LEN: usize = 2;
16
17/// equivalent to BBR.ExtraAckedFilterLen <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.11>
18const EXTRA_ACKED_FILTER_LEN: usize = 10;
19
20/// safety mechanism to flag packets as stale within our tracking VecDeque. rounds refer to <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1>.
21/// The value of 10 rounds is picked because normally after max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity) <https://datatracker.ietf.org/doc/html/rfc9002#section-6.1.2>
22/// the packet should have been declared lost already, this is just to guarantee that the VecDeque doesn't grow indefinitely.
23const ROUND_COUNT_WINDOW: u64 = 10;
24
25/// the minimum for the maximum datagram size <https://datatracker.ietf.org/doc/html/rfc9000#section-14>
26const MIN_MAX_DATAGRAM_SIZE: u16 = 1200;
27
28/// the maximum for the maximum datagram size <https://datatracker.ietf.org/doc/html/rfc9000#section-18.2>
29const MAX_DATAGRAM_SIZE: u64 = 65527;
30
31/// 1.2Mbps in bytes/sec used to determine send_quantum
32/// this is the pacing rate used where we don't authorize a burst bigger than a full packet
33/// inspired by a previous version of BBR2 used in cloudflare's quiche
34const PACING_RATE_1_2MBPS: f64 = 1200.0 * 1000.0;
35
36/// 24Mbps in bytes/sec
37/// this is the pacing rate used where we don't authorize a burst bigger than two full packets
38/// inspired by a previous version of BBR2 used in cloudflare's quiche
39const PACING_RATE_24MBPS: f64 = 24000.0 * 1000.0;
40
41/// 64 Kb in bytes
42/// this is the maximum size we want for a quantum in `set_send_quantum`
43/// inspired by a previous version of BBR2 used in cloudflare's quiche
44const HIGH_PACE_MAX_QUANTUM: u64 = 64 * 1000;
45
46/// equivalent to BBR.StartupPacingGain: A constant specifying the minimum gain value for calculating the pacing rate that will allow
47/// the sending rate to double each round (4 * ln(2) ~= 2.77)
48/// BBRStartupPacingGain; used in Startup mode for BBR.pacing_gain. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
49const STARTUP_PACING_GAIN: f64 = 2.773;
50
51/// default pacing gain is 1, when cruising, probing for RTT or refilling <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
52const DEFAULT_PACING_GAIN: f64 = 1.0;
53
54/// pacing gain when probing bandwidth down <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
55const PROBE_BW_DOWN_PACING_GAIN: f64 = 0.9;
56
57/// pacing gain when probing bandwidth up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
58const PROBE_BW_UP_PACING_GAIN: f64 = 1.25;
59
60/// equivalent to BBR.PacingMarginPercent: The static discount factor of 1% used to scale BBR.bw to produce C.pacing_rate.
61const PACING_MARGIN_PERCENT: f64 = 1.0;
62
63/// equivalent to BBR.DefaultCwndGain: A constant specifying the minimum gain value that allows the sending rate to double each round (2) BBRStartupCwndGain.
64/// Used by default in most phases for BBR.cwnd_gain.
65const DEFAULT_CWND_GAIN: f64 = 2.0;
66
67/// equivalent to BBR.DrainPacingGain: A constant specifying the pacing gain value used in Drain mode,
68/// to attempt to drain the estimated queue at the bottleneck link in one round-trip or less.
69/// As noted in BBRDrainPacingGain, any value at or below 1 / BBRStartupCwndGain = 1 / 2 = 0.5 will theoretically achieve this.
70/// BBR uses the value 0.5, which has been shown to offer good performance when compared with other alternatives.
71/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.4>
72/// <https://github.com/google/bbr/blob/master/Documentation/startup/gain/analysis/bbr_drain_gain.pdf>
73const DRAIN_PACING_GAIN: f64 = 1.0 / DEFAULT_CWND_GAIN;
74
75/// cwnd gain used when probing up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
76const PROBE_BW_UP_CWND_GAIN: f64 = 2.25;
77
78/// cwnd gain used when probing RTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
79const PROBE_RTT_CWND_GAIN: f64 = 0.5;
80
81/// equivalent to BBR.ProbeRTTDuration: A constant specifying the minimum duration for which ProbeRTT state holds C.inflight to BBR.MinPipeCwnd or fewer packets: 200 ms.
82const PROBE_RTT_DURATION_MS: u64 = 200;
83
84/// equivalent to BBR.ProbeRTTInterval: A constant specifying the minimum time interval between ProbeRTT states: 5 secs.
85const PROBE_RTT_INTERVAL_SEC: u64 = 5;
86
87/// equivalent to BBR.LossThresh: A constant specifying the maximum tolerated per-round-trip packet loss rate when probing for bandwidth (the default is 2%).
88const LOSS_THRESH: f64 = 0.02;
89
90/// equivalent to BBR.Beta: A constant specifying the default multiplicative decrease to make upon each round trip during which the connection detects packet loss (the value is 0.7).
91const BETA: f64 = 0.7;
92
93/// equivalent to BBR.Headroom: A constant specifying the multiplicative factor to apply to BBR.inflight_longterm when calculating
94/// a volume of free headroom to try to leave unused in the path
95/// (e.g. free space in the bottleneck buffer or free time slots in the bottleneck link) that can be used by cross traffic (the value is 0.15).
96const HEADROOM: f64 = 0.15;
97
98/// equivalent to BBR.MinRTTFilterLen: A constant specifying the length of the BBR.min_rtt min filter window, BBR.MinRTTFilterLen is 10 secs.
99const MIN_RTT_FILTER_LEN: u64 = 10;
100
101/// multiplier used to check growth when validating if the full bandwidth has been reached
102/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
103const FULL_BW_GROWTH: f64 = 1.25;
104
105/// maximum number of rounds needed before we consider that the pipe is full <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
106const MAX_FULL_BW_COUNT: u64 = 3;
107
108/// when setting `bw_probe_up_rounds` when raising our inflight long term slope we don't go above this
109/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
110const MAX_LONG_TERM_PROBE_UP_ROUNDS: u32 = 30;
111
112/// max number of rounds used when deciding to coexist with Reno / CUBIC <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.1>
113const MAX_RENO_ROUNDS: u64 = 63;
114
115/// minimum amount of time to wait before probing again <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-5>
116const MIN_PROBE_WAIT_MS: u64 = 2000;
117
118/// when waiting before probing again we add up to one second of added wait time
119/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-5>
120const MAX_ADDED_PROBE_WAIT_MS: u64 = 1000;
121
122/// Substates when probing bandwidth
123/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3>
124#[derive(Debug, Clone, Copy, Eq, PartialEq)]
125enum ProbeBwSubstate {
126 /// Deceleration: sends slower than delivery rate to reduce queue
127 /// equivalent to ProbeBW_DOWN <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.1>
128 Down,
129
130 /// Cruising: sends at delivery rate to maintain high utilization
131 /// equivalent to ProbeBW_CRUISE <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.2>
132 Cruise,
133
134 /// Refill: sends at BBR.bw for one RTT to fill pipe before probing up
135 /// equivalent to ProbeBW_REFILL <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.3>
136 Refill,
137
138 /// Acceleration: sends faster than delivery rate to probe for more bandwidth
139 /// equivalent to ProbeBW_UP <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.4>
140 Up,
141}
142
143/// State Machine description from BBR3
144/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3>
145#[derive(Debug, Clone, Copy, Eq, PartialEq)]
146enum BbrState {
147 /// Initial state: rapidly probes for bandwidth using high pacing_gain
148 /// equivalent to Startup <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1>
149 Startup,
150
151 /// Drains queue created during Startup by using low pacing_gain (< 1.0)
152 /// equivalent to Drain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2>
153 Drain,
154
155 /// Steady-state phase that cycles through bandwidth probing tactics
156 /// equivalent to ProbeBW states <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3>
157 ProbeBw(ProbeBwSubstate),
158
159 /// Temporarily reduces inflight to measure true min_rtt
160 /// equivalent to ProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4>
161 ProbeRtt,
162}
163
164/// Ack phases used during ProbeBW states
165/// equivalent to BBR.ack_phase states <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6>
166#[derive(Debug, Clone, Copy, Eq, PartialEq)]
167enum AckPhase {
168 /// equivalent to ACKS_PROBE_STARTING
169 ProbeStarting,
170 /// equivalent to ACKS_PROBE_STOPPING
171 ProbeStopping,
172 /// equivalent to ACKS_REFILLING
173 Refilling,
174 /// equivalent to ACKS_PROBE_FEEDBACK
175 ProbeFeedback,
176}
177
178/// Description of a packet for the purposes of analysis through BBR3
179/// all volumes of data use bytes, all rates of data use bytes/sec
180/// equivalent to P <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.1.2>
181#[derive(Debug, Clone, Copy)]
182struct BbrPacket {
183 /// equivalent to P.delivered: C.delivered when the packet was sent from transport connection C.
184 delivered: u64,
185 /// equivalent to P.delivered_time: C.delivered_time when the packet was sent.
186 delivered_time: Instant,
187 /// equivalent to P.first_send_time: C.first_send_time when the packet was sent.
188 first_send_time: Instant,
189 /// equivalent to P.send_time: The pacing departure time selected when the packet was scheduled to be sent.
190 send_time: Instant,
191 /// equivalent to P.is_app_limited: true if C.app_limited was non-zero when the packet was sent, else false.
192 is_app_limited: bool,
193 /// equivalent to P.tx_in_flight: C.inflight immediately after the transmission of packet P.
194 tx_in_flight: u64,
195 /// packet number from the connection
196 packet_number: u64,
197 /// packet size in bytes
198 size: u16,
199 /// equivalent to P.lost: C.lost when the packet was sent
200 lost: u64,
201 /// used to flag acknowledgement within our VecDeque, a packet can be flagged lost after having been flagged acknowledged
202 /// hence the necessity of this flag being set before we remove it from packets.
203 acknowledged: bool,
204 /// once a packet has been acknowledged on a given round it is marked for removal on the next round.
205 stale: bool,
206 /// used to mark packets stale if they're far from the current round <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1>
207 round_count: u64,
208}
209
210/// Description of a per-ack rate sample state that will allow us to determine a short term evolution of the connection
211/// equivalent to RS <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.2>
212#[derive(Debug, Clone, Copy)]
213struct BbrRateSample {
214 /// equivalent to RS.delivery_rate: The delivery rate (aka bandwidth) sample obtained from the packet that has just been ACKed.
215 delivery_rate: f64,
216 /// equivalent to RS.is_app_limited: The P.is_app_limited from the most recent packet
217 /// delivered; indicates whether the rate sample is application-limited.
218 is_app_limited: bool,
219 /// equivalent to RS.interval: The length of the sampling interval.
220 interval: Duration,
221 /// equivalent to RS.delivered: The volume of data delivered between the transmission of the packet that has just been ACKed and the current time.
222 delivered: u64,
223 /// equivalent to RS.prior_delivered: The P.delivered count from the most recent packet delivered.
224 prior_delivered: u64,
225 /// equivalent to RS.prior_time: The P.delivered_time from the most recent packet delivered.
226 prior_time: Instant,
227 /// equivalent to RS.send_elapsed: Send time interval calculated from the most recent
228 /// packet delivered (see the "Send Rate" section above).
229 send_elapsed: Duration,
230 /// equivalent to RS.ack_elapsed: ACK time interval calculated from the most recent
231 /// packet delivered (see the "ACK Rate" section above).
232 ack_elapsed: Duration,
233 /// equivalent to RS.rtt: The RTT sample calculated based on the most recently-sent packet of the packets that have just been ACKed.
234 rtt: Duration,
235 /// equivalent to RS.tx_in_flight: C.inflight at the time of the transmission of the packet that has just been ACKed
236 /// (the most recently sent packet among packets ACKed by the ACK that was just received).
237 tx_in_flight: u64,
238 /// equivalent to RS.newly_acked: The volume of data in bytes cumulatively or selectively acknowledged upon the ACK that was just received.
239 newly_acked: u64,
240 /// equivalent to RS.newly_lost: The volume of data in bytes newly marked lost upon the ACK that was just received.
241 newly_lost: u64,
242 /// equivalent to RS.lost: The volume of data in bytes that was declared lost between the transmission
243 /// and acknowledgment of the packet that has just been ACKed (the most recently sent packet among packets ACKed by the ACK that was just received).
244 lost: u64,
245 /// equivalent to RS.last_end_seq
246 last_end_seq: u64,
247 /// represents the last packet that was used in the generation of this rate sample
248 last_packet: BbrPacket,
249}
250
251/// Experimental! Use at your own risk.
252///
253/// Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks.
254/// Based on <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html>
255/// equivalent to a combination of BBR and C states
256/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.4>
257/// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.1>
258#[derive(Debug, Clone)]
259pub struct Bbr3 {
260 /// equivalent to C.SMSS The Sender Maximum Send Size in bytes. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.1>
261 /// <https://www.rfc-editor.org/rfc/rfc9000#name-datagram-size>
262 smss: u64,
263 /// equivalent to C.InitialCwnd: The initial congestion window set by the transport protocol implementation for the connection at initialization time.
264 initial_cwnd: u64,
265 /// equivalent to C.delivered: The total amount of data delivered so far over the lifetime of the transport connection C.
266 /// This MUST NOT include pure ACK packets. It SHOULD include spurious retransmissions that have been acknowledged as delivered.
267 delivered: u64,
268 /// equivalent to C.inflight: The connection's best estimate of the number of bytes outstanding in the network.
269 /// This includes the number of bytes that have been sent and have not been acknowledged or marked as lost since their last transmission
270 /// (e.g. "pipe" from RFC6675 or "bytes_in_flight" from RFC9002). This MUST NOT include pure ACK packets.
271 inflight: u64,
272 /// equivalent to C.is_cwnd_limited: True if the connection has fully utilized C.cwnd at any point in the last packet-timed round trip.
273 is_cwnd_limited: bool,
274 /// equivalent to BBR.cycle_count: The virtual time used by the BBR.max_bw filter window.
275 /// since the BBR.max_bw_filter only needs to track samples from two time slots: the previous ProbeBW cycle and the current ProbeBW cycle.
276 cycle_count: u64,
277 /// equivalent to C.cwnd: The transport sender's congestion window. When transmitting data, the sending connection ensures that C.inflight does not exceed C.cwnd.
278 cwnd: u64,
279 /// equivalent to C.pacing_rate: The current pacing rate for a BBR flow, which controls inter-packet spacing.
280 pacing_rate: f64,
281 /// equivalent to C.send_quantum: The maximum size of a data aggregate scheduled and transmitted together as a unit, e.g., to amortize per-packet transmission overheads.
282 send_quantum: u64,
283 /// equivalent to BBR.pacing_gain: The dynamic gain factor used to scale BBR.bw to produce C.pacing_rate.
284 pacing_gain: f64,
285 /// default pacing gain is 1, when cruising, probing for RTT or refilling <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
286 default_pacing_gain: f64,
287 /// pacing gain when probing bandwidth down <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
288 probe_bw_down_pacing_gain: f64,
289 /// pacing gain when probing bandwidth up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
290 probe_bw_up_pacing_gain: f64,
291 /// equivalent to BBR.StartupPacingGain: A constant specifying the minimum gain value for calculating the pacing rate that will allow
292 /// the sending rate to double each round (4 * ln(2) ~= 2.77)
293 /// BBRStartupPacingGain; used in Startup mode for BBR.pacing_gain. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
294 startup_pacing_gain: f64,
295 /// equivalent to BBR.DrainPacingGain: A constant specifying the pacing gain value used in Drain mode,
296 /// to attempt to drain the estimated queue at the bottleneck link in one round-trip or less.
297 /// As noted in BBRDrainPacingGain, any value at or below 1 / BBRStartupCwndGain = 1 / 2 = 0.5 will theoretically achieve this.
298 /// BBR uses the value 0.5, which has been shown to offer good performance when compared with other alternatives.
299 /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
300 drain_pacing_gain: f64,
301 /// equivalent to BBR.PacingMarginPercent: The static discount factor of 1% used to scale BBR.bw to produce C.pacing_rate.
302 pacing_margin_percent: f64,
303 /// equivalent to BBR.cwnd_gain: The dynamic gain factor used to scale the estimated BDP to produce a congestion window (C.cwnd).
304 cwnd_gain: f64,
305 /// equivalent to BBR.DefaultCwndGain: A constant specifying the minimum gain value that allows the sending rate to double each round (2) BBRStartupCwndGain.
306 /// Used by default in most phases for BBR.cwnd_gain.
307 default_cwnd_gain: f64,
308 /// used to generate random numbers when deciding how long to wait before probing again
309 /// using Pcg32 as it's a fast general purpose random number generator and fits our purpose here
310 /// these numbers will not be security critical as they're only used to decide when to probe the connection next.
311 probe_rng: Pcg32,
312 /// cwnd gain used when probing up <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
313 probe_bw_up_cwnd_gain: f64,
314 /// cwnd gain used when probing RTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.1>
315 probe_rtt_cwnd_gain: f64,
316 /// equivalent to BBR.state: The current state of a BBR flow in the BBR state machine. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-3.3>
317 state: BbrState,
318 /// equivalent to BBR.undo_state: The state of a BBR flow in the BBR state machine saved in case a loss episode is later declared spurious. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-3.3>
319 undo_state: BbrState,
320 /// equivalent to BBR.round_count: Count of packet-timed round trips elapsed so far.
321 round_count: u64,
322 /// equivalent to BBR.round_start: A boolean that BBR sets to true once per packet-timed round trip, on ACKs that advance BBR.round_count.
323 round_start: bool,
324 /// equivalent to BBR.next_round_delivered: P.delivered value denoting the end of a packet-timed round trip.
325 next_round_delivered: u64,
326 /// equivalent to BBR.idle_restart: A boolean that is true if and only if a connection is restarting after being idle.
327 idle_restart: bool,
328 /// equivalent to BBR.MinPipeCwnd: The minimal C.cwnd value BBR targets, to allow pipelining with endpoints that follow an "ACK every other packet" delayed-ACK policy: 4 * C.SMSS.
329 min_pipe_cwnd: u64,
330 /// equivalent to BBR.max_bw: The windowed maximum recent bandwidth sample, obtained using the BBR delivery rate sampling algorithm in
331 /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1>,
332 /// measured during the current or previous bandwidth probing cycle (or during Startup, if the flow is still in that state). (Part of the long-term model.)
333 max_bw: f64,
334 /// equivalent to BBR.bw_shortterm: The short-term maximum sending bandwidth that the algorithm estimates is safe for matching the current network path delivery rate,
335 /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_bw. (Part of the short-term model.)
336 bw_shortterm: f64,
337 /// equivalent to BBR.undo_bw_shortterm: The short-term maximum sending bandwidth that the algorithm estimates is safe for matching the current network path delivery rate,
338 /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_bw. (Part of the short-term model.)
339 /// saved state in case a loss episode is later declared spurious
340 undo_bw_shortterm: f64,
341 /// equivalent to BBR.bw: The maximum sending bandwidth that the algorithm estimates is appropriate for matching the current network path delivery rate,
342 /// given all available signals in the model, at any time scale. It is the min() of max_bw and bw_shortterm.
343 bw: f64,
344 /// equivalent to BBR.min_rtt: The windowed minimum round-trip time sample measured over the last BBR.MinRTTFilterLen = 10 seconds.
345 /// This attempts to estimate the two-way propagation delay of the network path when all connections sharing a bottleneck are using BBR,
346 /// but also allows BBR to estimate the value required for a BBR.bdp estimate that allows full throughput if there are legacy loss-based Reno or CUBIC flows sharing the bottleneck.
347 min_rtt: Duration,
348 /// equivalent to BBR.bdp: The estimate of the network path's BDP (Bandwidth-Delay Product), computed as: BBR.bdp = BBR.bw * BBR.min_rtt.
349 bdp: u64,
350 /// equivalent to BBR.extra_acked: A volume of data that is the estimate of the recent degree of aggregation in the network path.
351 extra_acked: u64,
352 /// equivalent to BBR.offload_budget: The estimate of the minimum volume of data necessary to achieve full throughput when using sender
353 /// (TSO/GSO) and receiver (LRO, GRO) host offload mechanisms.
354 offload_budget: u64,
355 /// equivalent to BBR.max_inflight: The estimate of C.inflight required to fully utilize the bottleneck bandwidth available to the flow,
356 /// based on the BDP estimate (BBR.bdp), the aggregation estimate (BBR.extra_acked), the offload budget (BBR.offload_budget), and BBR.MinPipeCwnd.
357 max_inflight: u64,
358 /// equivalent to BBR.inflight_longterm: The long-term maximum inflight that the algorithm estimates will produce acceptable queue pressure,
359 /// based on signals in the current or previous bandwidth probing cycle, as measured by loss. That is, if a flow is probing for bandwidth,
360 /// and observes that sending a particular inflight causes a loss rate higher than the loss rate threshold,
361 /// it sets inflight_longterm to that volume of data. (Part of the long-term model.)
362 inflight_longterm: u64,
363 /// equivalent to BBR.inflight_longterm: The long-term maximum inflight that the algorithm estimates will produce acceptable queue pressure,
364 /// based on signals in the current or previous bandwidth probing cycle, as measured by loss. That is, if a flow is probing for bandwidth,
365 /// and observes that sending a particular inflight causes a loss rate higher than the loss rate threshold,
366 /// it sets inflight_longterm to that volume of data. (Part of the long-term model.)
367 /// saved state in case a loss episode is later declared spurious
368 undo_inflight_longterm: u64,
369 /// equivalent to BBR.inflight_shortterm: Analogous to BBR.bw_shortterm,
370 /// the short-term maximum inflight that the algorithm estimates is safe for matching the current network path delivery process,
371 /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_inflight or inflight_longterm. (Part of the short-term model.)
372 inflight_shortterm: u64,
373 /// equivalent to BBR.undo_inflight_shortterm: Analogous to BBR.bw_shortterm,
374 /// the short-term maximum inflight that the algorithm estimates is safe for matching the current network path delivery process,
375 /// based on any loss signals in the current bandwidth probing cycle. This is generally lower than max_inflight or inflight_longterm. (Part of the short-term model.)
376 /// saved state in case a loss episode is later declared spurious
377 undo_inflight_shortterm: u64,
378 /// equivalent to BBR.bw_latest: a 1-round-trip max of delivered bandwidth (RS.delivery_rate).
379 bw_latest: f64,
380 /// equivalent to BBR.inflight_latest: a 1-round-trip max of delivered volume of data (RS.delivered).
381 inflight_latest: u64,
382 /// equivalent to BBR.max_bw_filter: A windowed max filter for RS.delivery_rate samples, for estimating BBR.max_bw.
383 max_bw_filter: MaxFilter,
384 /// equivalent to BBR.extra_acked_interval_start: The start of the time interval for estimating the excess amount of data acknowledged due to aggregation effects.
385 extra_acked_interval_start: Option<Instant>,
386 /// equivalent to BBR.extra_acked_delivered: The volume of data marked as delivered since BBR.extra_acked_interval_start.
387 extra_acked_delivered: u64,
388 /// equivalent to BBR.extra_acked_filter: A windowed max filter for tracking the degree of aggregation in the path.
389 extra_acked_filter: MaxFilter,
390 /// equivalent to BBR.full_bw_reached: A boolean that records whether BBR estimates that it has ever fully utilized its available bandwidth over the lifetime of the connection.
391 full_bw_reached: bool,
392 /// equivalent to BBR.full_bw_now: A boolean that records whether BBR estimates that it has fully utilized its available bandwidth since it most recetly started looking.
393 full_bw_now: bool,
394 /// equivalent to BBR.full_bw: A recent baseline BBR.max_bw to estimate if BBR has "filled the pipe" in Startup.
395 full_bw: f64,
396 /// equivalent to BBR.full_bw_count: The number of non-app-limited round trips without large increases in BBR.full_bw.
397 full_bw_count: u64,
398 /// equivalent to BBR.min_rtt_stamp: The wall clock time at which the current BBR.min_rtt sample was obtained.
399 min_rtt_stamp: Option<Instant>,
400 /// equivalent to BBR.ProbeRTTDuration: A constant specifying the minimum duration for which ProbeRTT state holds C.inflight to BBR.MinPipeCwnd or fewer packets: 200 ms.
401 probe_rtt_duration: Duration,
402 /// equivalent to BBR.ProbeRTTInterval: A constant specifying the minimum time interval between ProbeRTT states: 5 secs.
403 probe_rtt_interval: Duration,
404 /// equivalent to BBR.probe_rtt_min_delay: The minimum RTT sample recorded in the last ProbeRTTInterval.
405 probe_rtt_min_delay: Duration,
406 /// equivalent to BBR.probe_rtt_min_stamp: The wall clock time at which the current BBR.probe_rtt_min_delay sample was obtained.
407 probe_rtt_min_stamp: Option<Instant>,
408 /// equivalent to BBR.probe_rtt_expired: A boolean recording whether the BBR.probe_rtt_min_delay has expired and
409 /// is due for a refresh with an application idle period or a transition into ProbeRTT state.
410 probe_rtt_expired: bool,
411 /// equivalent to C.delivered_time: The wall clock time when C.delivered was last updated. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.1.2.1>
412 delivered_time: Option<Instant>,
413 /// equivalent to C.first_send_time: If packets are in flight, then this holds the send time of the packet that was most recently marked as delivered.
414 /// Else, if the connection was recently idle, then this holds the send time of most recently sent packet.
415 first_send_time: Option<Instant>,
416 /// equivalent to C.app_limited: The index of the last transmitted packet marked as application-limited, or 0 if the connection is not currently application-limited.
417 app_limited: u64,
418 /// equivalent to C.lost: the number of bytes that have been lost during the lifetime of this connection
419 lost: u64,
420 /// equivalent to C.srtt: The smoothed RTT, an exponentially weighted moving average of the observed RTT of the connection.
421 srtt: Duration,
422 /// collection of packets in flight or just acknowledged / lost.
423 packets: VecDeque<BbrPacket>,
424 /// equivalent to RS: Per-ACK Rate Sample State <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.2>
425 rs: Option<BbrRateSample>,
426 /// equivalent to BBR.rounds_since_bw_probe: rounds since last bw probe state.
427 rounds_since_bw_probe: u64,
428 /// equivalent to BBR.bw_probe_wait: random wait time before entering probing state again
429 bw_probe_wait: Duration,
430 /// equivalent to BBR.bw_probe_up_rounds: number of rounds that have been executed in probe up state
431 bw_probe_up_rounds: u32,
432 /// equivalent to BBR.bw_probe_up_acks: volume of data in bytes that has been acknowledged during probe up state
433 bw_probe_up_acks: u64,
434 /// equivalent to BBR.probe_up_cnt: count of the number of times we've grown the cwnd during probe up state
435 probe_up_cnt: u64,
436 /// equivalent to BBR.cycle_stamp: timestamp when we start probing down state
437 cycle_stamp: Option<Instant>,
438 /// equivalent to BBR.ack_phase: ACK phase during probing states
439 ack_phase: AckPhase,
440 /// equivalent to BBR.bw_probe_samples: <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2>
441 bw_probe_samples: bool,
442 /// equivalent to BBR.loss_round_delivered: C.delivered during the first loss of the round
443 loss_round_delivered: u64,
444 /// equivalent to BBR.loss_in_round: flag set to true when loss occurs during the round
445 loss_in_round: bool,
446 /// equivalent to BBR.probe_rtt_done_stamp: timestamp when probe RTT state is finished
447 probe_rtt_done_stamp: Option<Instant>,
448 /// equivalent to BBR.probe_rtt_round_done: set once per round when BBR.probe_rtt_done_stamp to check if we need to switch state
449 probe_rtt_round_done: bool,
450 /// equivalent to BBR.prior_cwnd: cwnd from last round
451 prior_cwnd: u64,
452 /// equivalent to BBR.loss_round_start: flag set to true at the very beginning of a round where loss occurred
453 loss_round_start: bool,
454 /// equivalent to BBR.drain_start_round: The value of round_count when Drain state started.
455 drain_start_round: u64,
456 /// Number of ack-eliciting packets the peer may receive before sending an immediate ACK,
457 /// as requested via the QUIC ACK frequency extension. Used when computing `offload_budget`
458 /// per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
459 ack_eliciting_threshold: u64,
460 /// `max_ack_delay` we requested the peer to use via the QUIC ACK frequency extension.
461 /// Used when computing `offload_budget` per
462 /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
463 max_ack_delay: Duration,
464}
465
466impl Bbr3 {
467 fn new(config: Arc<Bbr3Config>, current_mtu: u16) -> Self {
468 let probe_rng: Pcg32;
469 if let Some(probe_seed) = config.probe_rng_seed {
470 probe_rng = Pcg32::from_seed(probe_seed);
471 } else {
472 probe_rng = Pcg32::from_rng(&mut rand::rng());
473 }
474 let smss = min(
475 max(MIN_MAX_DATAGRAM_SIZE, current_mtu) as u64,
476 MAX_DATAGRAM_SIZE,
477 );
478 let initial_cwnd = config.initial_window;
479 let startup_pacing_gain = config.startup_pacing_gain.unwrap_or(STARTUP_PACING_GAIN);
480 let default_pacing_gain = config.default_pacing_gain.unwrap_or(DEFAULT_PACING_GAIN);
481 let probe_bw_down_pacing_gain = config
482 .probe_bw_down_pacing_gain
483 .unwrap_or(PROBE_BW_DOWN_PACING_GAIN);
484 let probe_bw_up_pacing_gain = config
485 .probe_bw_up_pacing_gain
486 .unwrap_or(PROBE_BW_UP_PACING_GAIN);
487 let drain_pacing_gain = config.drain_pacing_gain.unwrap_or(DRAIN_PACING_GAIN);
488 let pacing_margin_percent = config
489 .pacing_margin_percent
490 .unwrap_or(PACING_MARGIN_PERCENT);
491 let default_cwnd_gain = config.default_cwnd_gain.unwrap_or(DEFAULT_CWND_GAIN);
492 let probe_bw_up_cwnd_gain = config
493 .probe_bw_up_cwnd_gain
494 .unwrap_or(PROBE_BW_UP_CWND_GAIN);
495 let probe_rtt_cwnd_gain = config.probe_rtt_cwnd_gain.unwrap_or(PROBE_RTT_CWND_GAIN);
496 // the calculation for initial pacing rate described here <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-5>
497 let nominal_bandwidth = initial_cwnd as f64 / 0.001;
498 let pacing_rate = startup_pacing_gain * nominal_bandwidth;
499 Self {
500 smss,
501 initial_cwnd,
502 delivered: 0,
503 inflight: 0,
504 is_cwnd_limited: false,
505 cycle_count: 0,
506 cwnd: initial_cwnd,
507 pacing_rate,
508 send_quantum: 2 * smss, // we start high, but it will be adjusted in set_send_quantum <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.3>
509 pacing_gain: startup_pacing_gain,
510 startup_pacing_gain,
511 default_pacing_gain,
512 probe_bw_down_pacing_gain,
513 probe_bw_up_pacing_gain,
514 drain_pacing_gain,
515 pacing_margin_percent,
516 cwnd_gain: default_cwnd_gain,
517 default_cwnd_gain,
518 probe_rng,
519 probe_bw_up_cwnd_gain,
520 state: BbrState::Startup,
521 undo_state: BbrState::Startup,
522 round_count: 0,
523 round_start: true,
524 next_round_delivered: 0,
525 idle_restart: false,
526 min_pipe_cwnd: 4 * smss, // 4 * C.SMSS as defined in <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-2.7-4>
527 max_bw: 0.0,
528 bw_shortterm: f64::INFINITY,
529 undo_bw_shortterm: f64::INFINITY,
530 bw: 0.0,
531 min_rtt: Duration::from_secs(u64::MAX),
532 bdp: 0,
533 extra_acked: 0,
534 offload_budget: 0,
535 max_inflight: 0,
536 inflight_longterm: u64::MAX,
537 undo_inflight_longterm: u64::MAX,
538 inflight_shortterm: u64::MAX,
539 undo_inflight_shortterm: u64::MAX,
540 bw_latest: 0.0,
541 inflight_latest: 0,
542 max_bw_filter: MaxFilter::new(MAX_BW_FILTER_LEN as u64),
543 extra_acked_interval_start: None,
544 extra_acked_delivered: 0,
545 extra_acked_filter: MaxFilter::new(EXTRA_ACKED_FILTER_LEN as u64),
546 full_bw_reached: false,
547 full_bw_now: false,
548 full_bw: 0.0,
549 full_bw_count: 0,
550 min_rtt_stamp: None,
551 probe_rtt_cwnd_gain,
552 probe_rtt_duration: Duration::from_millis(PROBE_RTT_DURATION_MS),
553 probe_rtt_interval: Duration::from_secs(PROBE_RTT_INTERVAL_SEC),
554 probe_rtt_min_delay: Duration::ZERO,
555 probe_rtt_min_stamp: None,
556 probe_rtt_expired: false,
557 delivered_time: None,
558 first_send_time: None,
559 app_limited: 0,
560 lost: 0,
561 srtt: Duration::ZERO,
562 rs: None,
563 packets: VecDeque::new(),
564 rounds_since_bw_probe: 0,
565 bw_probe_wait: Duration::ZERO,
566 bw_probe_up_rounds: 0,
567 bw_probe_up_acks: 0,
568 probe_up_cnt: 0,
569 cycle_stamp: None,
570 ack_phase: AckPhase::ProbeStarting,
571 bw_probe_samples: false,
572 loss_round_delivered: 0,
573 loss_in_round: false,
574 probe_rtt_done_stamp: None,
575 probe_rtt_round_done: false,
576 prior_cwnd: 0,
577 loss_round_start: false,
578 drain_start_round: 0,
579 // Conservative defaults that match RFC 9000 §13.2.2 behavior (ACK every other
580 // ack-eliciting packet) and the default QUIC `max_ack_delay` of 25ms. Overridden
581 // when the connection supplies peer ACK-frequency parameters.
582 ack_eliciting_threshold: 1,
583 max_ack_delay: Duration::from_millis(25),
584 }
585 }
586
587 /// equivalent to BBREnterStartup <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.1-3>
588 fn enter_startup(&mut self) {
589 self.state = BbrState::Startup;
590 self.pacing_gain = self.startup_pacing_gain;
591 self.cwnd_gain = self.default_cwnd_gain;
592 }
593
594 /// equivalent to BBRResetFullBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-4>
595 fn reset_full_bw(&mut self) {
596 self.full_bw = 0.0;
597 self.full_bw_count = 0;
598 self.full_bw_now = false;
599 }
600
601 /// equivalent to BBRNoteLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
602 fn note_loss(&mut self) {
603 if !self.loss_in_round {
604 self.loss_round_delivered = self.delivered;
605 }
606 self.save_state_upon_loss();
607 self.loss_in_round = true;
608 }
609
610 /// equivalent to BBRSaveStateUponLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.11.1>
611 /// Save state in case a loss episode is later declared spurious
612 fn save_state_upon_loss(&mut self) {
613 self.undo_state = self.state;
614 self.undo_bw_shortterm = self.bw_shortterm;
615 self.undo_inflight_shortterm = self.inflight_shortterm;
616 self.undo_inflight_longterm = self.inflight_longterm;
617 }
618
619 /// equivalent to BBRInflightAtLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
620 /// We check at what prefix of packet did losses exceed `loss_thresh`
621 fn inflight_at_loss(&mut self, packet_size: u64) -> u64 {
622 if let Some(rate_sample) = self.rs {
623 let inflight_prev = rate_sample.tx_in_flight.saturating_sub(packet_size);
624 let inflight_prev_threshold = LOSS_THRESH * inflight_prev as f64;
625 let lost_prev = rate_sample.lost.saturating_sub(packet_size);
626 let compared_loss = (inflight_prev_threshold.round() as u64) - lost_prev;
627 let lost_prefix = compared_loss as f64 / (1.0 - LOSS_THRESH);
628 let inflight_at_loss = inflight_prev + lost_prefix as u64;
629 return inflight_at_loss;
630 }
631 0
632 }
633
634 /// equivalent to BBRSaveCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.4-13>
635 fn save_cwnd(&mut self) {
636 if !self.loss_in_round && self.state != BbrState::ProbeRtt {
637 self.prior_cwnd = self.cwnd;
638 } else {
639 self.prior_cwnd = max(self.prior_cwnd, self.cwnd);
640 }
641 }
642
643 /// equivalent to BBRRestoreCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.4-13>
644 fn restore_cwnd(&mut self) {
645 self.cwnd = max(self.cwnd, self.prior_cwnd);
646 }
647
648 /// equivalent to BBRProbeRTTCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.5-1>
649 fn probe_rtt_cwnd(&mut self) -> u64 {
650 let mut probe_rtt_cwnd = self.bdp_multiple(self.bw, self.probe_rtt_cwnd_gain);
651 probe_rtt_cwnd = max(probe_rtt_cwnd, self.min_pipe_cwnd);
652 probe_rtt_cwnd
653 }
654
655 /// equivalent to BBRBoundCwndForProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.5-1>
656 fn bound_cwnd_for_probe_rtt(&mut self) {
657 if self.state == BbrState::ProbeRtt {
658 self.cwnd = min(self.cwnd, self.probe_rtt_cwnd());
659 }
660 }
661
662 /// equivalent to BBRTargetInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
663 fn target_inflight(&self) -> u64 {
664 min(self.bdp, self.cwnd)
665 }
666
667 /// equivalent to BBRHandleInflightTooHigh <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-1>
668 fn handle_inflight_too_high(&mut self, now: Instant) {
669 self.bw_probe_samples = false;
670 if let Some(rate_sample) = self.rs
671 && !rate_sample.is_app_limited
672 {
673 self.inflight_longterm = max(
674 rate_sample.tx_in_flight,
675 (self.target_inflight() as f64 * BETA) as u64,
676 );
677 }
678
679 if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
680 self.start_probe_bw_down(now);
681 }
682 }
683
684 /// equivalent to IsInflightTooHigh <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-1>
685 fn is_inflight_too_high(&self) -> bool {
686 if let Some(rate_sample) = self.rs {
687 return rate_sample.lost as f64 > rate_sample.tx_in_flight as f64 * LOSS_THRESH;
688 }
689 false
690 }
691
692 /// equivalent to BBRCheckStartupHighLoss <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.3>
693 fn check_startup_high_loss(&mut self) {
694 if self.full_bw_reached {
695 return;
696 }
697
698 if self.is_inflight_too_high() {
699 let mut new_inflight_hi = self.bdp.max(self.inflight_latest);
700 if let Some(rate_sample) = self.rs
701 && new_inflight_hi < rate_sample.delivered
702 {
703 new_inflight_hi = rate_sample.delivered;
704 }
705 self.inflight_longterm = new_inflight_hi;
706 self.full_bw_reached = true;
707 }
708 }
709
710 /// equivalent to BBREnterProbeBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6>
711 fn enter_probe_bw(&mut self, now: Instant) {
712 self.cwnd_gain = self.default_cwnd_gain;
713 self.start_probe_bw_down(now);
714 }
715
716 /// equivalent to BBRPickProbeWait <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
717 fn pick_probe_wait(&mut self) {
718 // 0 or 1
719 self.rounds_since_bw_probe = self.probe_rng.random_bool(0.5) as u64;
720 self.bw_probe_wait = Duration::from_millis(
721 MIN_PROBE_WAIT_MS + self.probe_rng.random_range(0..=MAX_ADDED_PROBE_WAIT_MS),
722 );
723 }
724
725 /// equivalent to BBRHasElapsedInPhase <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
726 fn has_elapsed_in_phase(&mut self, interval: Duration, now: Instant) -> bool {
727 if let Some(cycle_stamp) = self.cycle_stamp {
728 now > cycle_stamp.checked_add(interval).unwrap_or(cycle_stamp)
729 } else {
730 true
731 }
732 }
733
734 /// equivalent to BBRExitProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.4>
735 fn exit_probe_rtt(&mut self, now: Instant) {
736 self.reset_short_term_model();
737 if self.full_bw_reached {
738 self.start_probe_bw_down(now);
739 self.start_probe_bw_cruise();
740 } else {
741 self.enter_startup();
742 }
743 }
744
745 /// equivalent to BBRCheckProbeRTTDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
746 fn check_probe_rtt_done(&mut self, now: Instant) {
747 if let Some(probe_rtt_done_stamp) = self.probe_rtt_done_stamp
748 && now > probe_rtt_done_stamp
749 {
750 self.probe_rtt_min_stamp = Some(now);
751 self.restore_cwnd();
752 self.exit_probe_rtt(now);
753 }
754 }
755
756 /// equivalent to BBRIsTimeToProbeBW <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
757 fn maybe_enter_probe_bw_refill(&mut self, now: Instant) -> bool {
758 if self.has_elapsed_in_phase(self.bw_probe_wait, now)
759 || self.is_reno_coexistence_probe_time()
760 {
761 self.start_probe_bw_refill();
762 return true;
763 }
764 false
765 }
766
767 /// equivalent to BBRIsTimeToGoDown <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-6>
768 fn maybe_go_down(&mut self) -> bool {
769 if self.is_cwnd_limited && self.cwnd >= self.inflight_longterm {
770 self.reset_full_bw();
771 if let Some(rate_sample) = self.rs {
772 self.full_bw = rate_sample.delivery_rate;
773 }
774 } else if self.full_bw_now {
775 return true;
776 }
777 false
778 }
779
780 /// equivalent to BBRIsRenoCoexistenceProbeTime <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.5.3-6>
781 fn is_reno_coexistence_probe_time(&self) -> bool {
782 let reno_rounds = self.target_inflight();
783 let rounds = min(reno_rounds, MAX_RENO_ROUNDS);
784 self.rounds_since_bw_probe >= rounds
785 }
786
787 /// equivalent to BBRBDPMultiple <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
788 fn bdp_multiple(&mut self, bw: f64, gain: f64) -> u64 {
789 if self.min_rtt == Duration::from_secs(u64::MAX) {
790 return self.initial_cwnd;
791 }
792 self.bdp = (bw * self.min_rtt.as_secs_f64()).round() as u64;
793 (gain * self.bdp as f64) as u64
794 }
795
796 /// equivalent to BBRUpdateOffloadBudget for QUIC per
797 /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.8.2>.
798 ///
799 /// The delayed-ACK term accounts for the QUIC ACK frequency extension:
800 /// `min(Ack-Eliciting Threshold, Requested Max Ack Delay * BBR.max_bw)`.
801 fn update_offload_budget(&mut self) {
802 let base = self.send_quantum;
803
804 // Ack-Eliciting Threshold is a packet count in the ACK_FREQUENCY frame; convert to
805 // bytes using the current SMSS. A threshold of 0 requires an immediate ACK per packet,
806 // so the delayed-ACK term contributes nothing in that case.
807 let threshold_bytes = self.ack_eliciting_threshold.saturating_mul(self.smss);
808 let delay_bytes = (self.max_ack_delay.as_secs_f64() * self.max_bw).round() as u64;
809 let delayed_ack_term = min(threshold_bytes, delay_bytes);
810
811 self.offload_budget = base.saturating_add(delayed_ack_term);
812 }
813
814 /// equivalent to BBRQuantizationBudget <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
815 fn quantization_budget(&mut self, inflight_cap: u64) -> u64 {
816 self.update_offload_budget();
817 let mut inflight_cap = max(inflight_cap, self.offload_budget);
818 inflight_cap = max(inflight_cap, self.min_pipe_cwnd);
819 if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
820 inflight_cap += 2 * self.smss;
821 }
822 inflight_cap
823 }
824
825 /// equivalent to BBRInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
826 fn get_inflight(&mut self, gain: f64) -> u64 {
827 let inflight_cap = self.bdp_multiple(self.max_bw, gain);
828 self.quantization_budget(inflight_cap)
829 }
830
831 /// equivalent to BBRUpdateMaxInflight <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.2-2>
832 fn update_max_inflight(&mut self) {
833 let mut inflight_cap = self.bdp_multiple(self.max_bw, self.cwnd_gain);
834 inflight_cap += self.extra_acked;
835 self.max_inflight = self.quantization_budget(inflight_cap);
836 }
837
838 /// equivalent to BBRResetCongestionSignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
839 fn reset_congestion_signals(&mut self) {
840 self.loss_in_round = false;
841 self.bw_latest = 0.0;
842 self.inflight_latest = 0;
843 }
844
845 /// equivalent to BBRStartRound <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1-9>
846 fn start_round(&mut self) {
847 self.next_round_delivered = self.delivered;
848 self.is_cwnd_limited = false;
849 }
850
851 /// equivalent to BBRUpdateRound <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.1-9>
852 fn update_round(&mut self, packet: BbrPacket) {
853 if packet.delivered >= self.next_round_delivered {
854 self.start_round();
855 self.round_count += 1;
856 self.rounds_since_bw_probe += 1;
857 self.round_start = true;
858 } else {
859 self.round_start = false;
860 }
861 }
862
863 /// equivalent to BBRStartProbeBW_DOWN <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
864 fn start_probe_bw_down(&mut self, now: Instant) {
865 self.reset_congestion_signals();
866 self.probe_up_cnt = u64::MAX;
867 self.pick_probe_wait();
868 self.cycle_stamp = Some(now);
869 self.ack_phase = AckPhase::ProbeStopping;
870 self.start_round();
871 self.pacing_gain = self.probe_bw_down_pacing_gain;
872 self.cwnd_gain = self.default_cwnd_gain;
873 self.state = BbrState::ProbeBw(ProbeBwSubstate::Down);
874 }
875
876 /// equivalent to BBRInflightWithHeadroom <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
877 fn inflight_with_headroom(&self) -> u64 {
878 if self.inflight_longterm == u64::MAX {
879 return u64::MAX;
880 }
881 let total_headroom = max(self.smss, (HEADROOM * self.inflight_longterm as f64) as u64);
882 if let Some(inflight_with_headroom) = self.inflight_longterm.checked_sub(total_headroom) {
883 max(inflight_with_headroom, self.min_pipe_cwnd)
884 } else {
885 self.min_pipe_cwnd
886 }
887 }
888
889 /// equivalent to BBRSetPacingRateWithGain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-7>
890 fn set_pacing_rate_with_gain(&mut self, gain: f64) {
891 let rate = gain * self.bw * (100.0 - self.pacing_margin_percent) / 100.0;
892 if self.full_bw_reached || rate > self.pacing_rate {
893 self.pacing_rate = rate;
894 }
895 }
896
897 /// equivalent to BBRRaiseInflightLongtermSlope <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
898 fn raise_inflight_long_term_slope(&mut self) {
899 let growth_this_round = self
900 .smss
901 .checked_shl(self.bw_probe_up_rounds)
902 .unwrap_or(u64::MAX);
903 self.bw_probe_up_rounds = min(self.bw_probe_up_rounds + 1, MAX_LONG_TERM_PROBE_UP_ROUNDS);
904 self.probe_up_cnt = max(self.cwnd / growth_this_round, 1);
905 }
906
907 /// equivalent to BBRProbeInflightLongtermUpward <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
908 fn probe_inflight_long_term_upward(&mut self) {
909 if !self.is_cwnd_limited || self.cwnd < self.inflight_longterm {
910 return;
911 }
912 if let Some(rate_sample) = self.rs {
913 self.bw_probe_up_acks += rate_sample.newly_acked;
914 }
915 if self.bw_probe_up_acks >= self.probe_up_cnt && self.probe_up_cnt > 0 {
916 let delta = self.bw_probe_up_acks / self.probe_up_cnt;
917 self.bw_probe_up_acks -= delta * self.probe_up_cnt;
918 self.inflight_longterm += delta;
919 if self.round_start {
920 self.raise_inflight_long_term_slope();
921 }
922 }
923 }
924
925 /// equivalent to BBRAdvanceMaxBwFilter <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.6>
926 fn advance_max_bw_filter(&mut self) {
927 self.cycle_count = self.cycle_count.saturating_add(1);
928 }
929
930 /// equivalent to BBRAdaptLongTermModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
931 fn adapt_long_term_model(&mut self) {
932 if self.ack_phase == AckPhase::ProbeStarting && self.round_start {
933 self.ack_phase = AckPhase::ProbeFeedback;
934 }
935 if self.ack_phase == AckPhase::ProbeStopping
936 && self.round_start
937 && let BbrState::ProbeBw(_) = self.state
938 && let Some(rate_sample) = self.rs
939 && !rate_sample.is_app_limited
940 {
941 self.advance_max_bw_filter();
942 }
943 if !self.is_inflight_too_high() {
944 if self.inflight_longterm == u64::MAX {
945 return;
946 }
947 if let Some(rate_sample) = self.rs
948 && rate_sample.tx_in_flight > self.inflight_longterm
949 {
950 self.inflight_longterm = rate_sample.tx_in_flight;
951 }
952 if self.state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
953 self.probe_inflight_long_term_upward();
954 }
955 }
956 }
957
958 /// equivalent to BBRIsTimeToCruise <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-8>
959 fn maybe_update_budget_and_time_to_cruise(&mut self) -> bool {
960 if self.inflight > self.inflight_with_headroom() {
961 return false;
962 }
963 if self.inflight > self.get_inflight(1.0) {
964 return false;
965 }
966 true
967 }
968
969 /// equivalent to BBRStartProbeBW_CRUISE <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.4-4>
970 fn start_probe_bw_cruise(&mut self) {
971 self.state = BbrState::ProbeBw(ProbeBwSubstate::Cruise);
972 self.pacing_gain = self.default_pacing_gain;
973 self.cwnd_gain = self.default_cwnd_gain;
974 }
975
976 /// equivalent to BBRResetShortTermModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
977 fn reset_short_term_model(&mut self) {
978 self.bw_shortterm = f64::INFINITY;
979 self.inflight_shortterm = u64::MAX;
980 }
981
982 /// equivalent to BBRInitLowerBounds <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
983 fn init_lower_bounds(&mut self) {
984 if self.bw_shortterm == f64::INFINITY {
985 self.bw_shortterm = self.max_bw;
986 }
987 if self.inflight_shortterm == u64::MAX {
988 self.inflight_shortterm = self.cwnd;
989 }
990 }
991
992 /// equivalent to BBRLossLowerBounds <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
993 fn loss_lower_bounds(&mut self) {
994 // gives max of both f64
995 self.bw_shortterm = [self.bw_latest, BETA * self.bw_shortterm]
996 .iter()
997 .copied()
998 .fold(f64::NAN, f64::max);
999 self.inflight_shortterm = max(
1000 self.inflight_latest,
1001 (BETA * self.inflight_shortterm as f64) as u64,
1002 );
1003 }
1004
1005 /// equivalent to BBRBoundBWForModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
1006 fn bound_bw_for_model(&mut self) {
1007 // gives min of both f64
1008 self.bw = [self.max_bw, self.bw_shortterm]
1009 .iter()
1010 .copied()
1011 .fold(f64::NAN, f64::min);
1012 }
1013
1014 /// equivalent to BBRStartProbeBW_REFILL <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
1015 fn start_probe_bw_refill(&mut self) {
1016 self.reset_short_term_model();
1017 self.bw_probe_up_rounds = 0;
1018 self.bw_probe_up_acks = 0;
1019 self.ack_phase = AckPhase::Refilling;
1020 self.start_round();
1021 self.cwnd_gain = self.default_cwnd_gain;
1022 self.pacing_gain = self.default_pacing_gain;
1023 self.state = BbrState::ProbeBw(ProbeBwSubstate::Refill);
1024 }
1025
1026 /// equivalent to BBRStartProbeBW_UP <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-4>
1027 fn start_probe_bw_up(&mut self) {
1028 self.ack_phase = AckPhase::ProbeStarting;
1029 self.start_round();
1030 self.reset_full_bw();
1031 if let Some(rate_sample) = self.rs {
1032 self.full_bw = rate_sample.delivery_rate;
1033 }
1034 self.state = BbrState::ProbeBw(ProbeBwSubstate::Up);
1035 self.pacing_gain = self.probe_bw_up_pacing_gain;
1036 self.cwnd_gain = self.probe_bw_up_cwnd_gain;
1037 self.raise_inflight_long_term_slope();
1038 }
1039
1040 /// equivalent to BBREnterProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
1041 fn enter_probe_rtt(&mut self) {
1042 self.state = BbrState::ProbeRtt;
1043 self.pacing_gain = self.default_pacing_gain;
1044 self.cwnd_gain = self.probe_rtt_cwnd_gain;
1045 }
1046
1047 /// equivalent to BBRHandleRestartFromIdle <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.4.1>
1048 fn handle_restart_from_idle(&mut self, now: Instant) {
1049 if self.inflight == 0 && self.app_limited != 0 {
1050 self.idle_restart = true;
1051 self.extra_acked_interval_start = Some(now);
1052 match self.state {
1053 BbrState::ProbeBw(_) => {
1054 self.set_pacing_rate_with_gain(1.0);
1055 }
1056 BbrState::ProbeRtt => {
1057 self.check_probe_rtt_done(now);
1058 }
1059 _ => {}
1060 }
1061 }
1062 }
1063
1064 /// equivalent to BBRUpdateProbeBWCyclePhase <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.3.6-6>
1065 fn update_probe_bw_cycle_phase(&mut self, now: Instant) {
1066 if !self.full_bw_reached {
1067 return;
1068 }
1069 self.adapt_long_term_model();
1070 let state = self.state;
1071 match state {
1072 BbrState::ProbeBw(ProbeBwSubstate::Down) => {
1073 if self.maybe_enter_probe_bw_refill(now) {
1074 return;
1075 }
1076 if self.maybe_update_budget_and_time_to_cruise() {
1077 self.start_probe_bw_cruise();
1078 }
1079 }
1080 BbrState::ProbeBw(ProbeBwSubstate::Cruise) if self.maybe_enter_probe_bw_refill(now) => {
1081 }
1082 BbrState::ProbeBw(ProbeBwSubstate::Refill) if self.round_start => {
1083 self.bw_probe_samples = true;
1084 self.start_probe_bw_up();
1085 }
1086 BbrState::ProbeBw(ProbeBwSubstate::Up) if self.maybe_go_down() => {
1087 self.start_probe_bw_down(now);
1088 }
1089 _ => {}
1090 }
1091 }
1092
1093 /// equivalent to BBRUpdateLatestDeliverySignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
1094 fn update_latest_delivery_signals(&mut self) {
1095 self.loss_round_start = false;
1096 if let Some(rate_sample) = self.rs {
1097 self.bw_latest = [self.bw_latest, rate_sample.delivery_rate]
1098 .iter()
1099 .copied()
1100 .fold(f64::NAN, f64::max);
1101 self.inflight_latest = max(self.inflight_latest, rate_sample.delivered);
1102
1103 if rate_sample.prior_delivered >= self.loss_round_delivered {
1104 self.loss_round_delivered = self.delivered;
1105 self.loss_round_start = true;
1106 }
1107 }
1108 }
1109
1110 /// equivalent to BBRAdaptLowerBoundsFromCongestion <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
1111 fn adapt_lower_bounds_from_congestion(&mut self) {
1112 match self.state {
1113 BbrState::ProbeBw(ProbeBwSubstate::Refill)
1114 | BbrState::ProbeBw(ProbeBwSubstate::Up)
1115 | BbrState::Startup => {}
1116 _ => {
1117 if self.loss_in_round {
1118 self.init_lower_bounds();
1119 self.loss_lower_bounds();
1120 }
1121 }
1122 }
1123 }
1124
1125 /// equivalent to BBRUpdateMaxBw <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.5>
1126 fn update_max_bw(&mut self, p: BbrPacket) {
1127 self.update_round(p);
1128 if let Some(rate_sample) = self.rs
1129 && rate_sample.delivery_rate > 0.0
1130 && (rate_sample.delivery_rate >= self.max_bw || !rate_sample.is_app_limited)
1131 {
1132 self.max_bw_filter
1133 .update_max(self.cycle_count, rate_sample.delivery_rate.round() as u64);
1134
1135 self.max_bw = self.max_bw_filter.get_max() as f64;
1136 }
1137 }
1138
1139 /// equivalent to BBRUpdateCongestionSignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
1140 fn update_congestion_signals(&mut self, p: BbrPacket) {
1141 self.update_max_bw(p);
1142 if !self.loss_round_start {
1143 return;
1144 }
1145 self.adapt_lower_bounds_from_congestion();
1146 self.loss_in_round = false;
1147 }
1148
1149 /// equivalent to BBRUpdateACKAggregation <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.9>
1150 fn update_ack_aggregation(&mut self, now: Instant) {
1151 let interval;
1152 if let Some(extra_acked_interval_start) = self.extra_acked_interval_start {
1153 interval = now - extra_acked_interval_start;
1154 } else {
1155 interval = Duration::from_secs(0);
1156 }
1157 let mut expected_delivered = (self.bw * interval.as_secs_f64()) as u64;
1158 if self.extra_acked_delivered <= expected_delivered {
1159 self.extra_acked_delivered = 0;
1160 self.extra_acked_interval_start = Some(now);
1161 expected_delivered = 0;
1162 }
1163 if let Some(rate_sample) = self.rs {
1164 self.extra_acked_delivered += rate_sample.newly_acked;
1165 }
1166
1167 let mut extra = self
1168 .extra_acked_delivered
1169 .saturating_sub(expected_delivered);
1170 extra = min(extra, self.cwnd);
1171 if self.full_bw_reached {
1172 self.extra_acked_filter.update_max(self.round_count, extra);
1173 self.extra_acked = self.extra_acked_filter.get_max();
1174 } else {
1175 self.extra_acked = extra; // In startup, just remember 1 round
1176 }
1177 }
1178
1179 /// equivalent to BBRCheckFullBWReached <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.2-6>
1180 fn check_full_bw_reached(&mut self) {
1181 if self.full_bw_now || !self.round_start {
1182 return;
1183 }
1184 if let Some(rate_sample) = self.rs {
1185 if rate_sample.is_app_limited {
1186 return;
1187 }
1188 if rate_sample.delivery_rate >= self.full_bw * FULL_BW_GROWTH {
1189 self.reset_full_bw();
1190 self.full_bw = rate_sample.delivery_rate;
1191 return;
1192 }
1193 }
1194 self.full_bw_count += 1;
1195 self.full_bw_now = self.full_bw_count >= MAX_FULL_BW_COUNT;
1196 if self.full_bw_now {
1197 self.full_bw_reached = true;
1198 }
1199 }
1200
1201 /// equivalent to BBREnterDrain <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2>
1202 fn enter_drain(&mut self) {
1203 self.state = BbrState::Drain;
1204 self.pacing_gain = self.drain_pacing_gain;
1205 self.cwnd_gain = self.default_cwnd_gain;
1206 self.drain_start_round = self.round_count;
1207 }
1208
1209 /// equivalent to BBRCheckStartupDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.1.1-6>
1210 fn check_startup_done(&mut self) {
1211 self.check_startup_high_loss();
1212 if self.state == BbrState::Startup && self.full_bw_reached {
1213 self.enter_drain();
1214 }
1215 }
1216
1217 /// equivalent to BBRCheckDrainDone <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.2-3>
1218 fn check_drain_done(&mut self, now: Instant) {
1219 if self.state == BbrState::Drain
1220 && (self.inflight <= self.get_inflight(1.0)
1221 || self.round_count > self.drain_start_round + 3)
1222 {
1223 self.enter_probe_bw(now);
1224 }
1225 }
1226
1227 /// equivalent to BBRUpdateMinRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3>
1228 fn update_min_rtt(&mut self, now: Instant) {
1229 if let Some(probe_rtt_min_stamp) = self.probe_rtt_min_stamp {
1230 self.probe_rtt_expired = now
1231 > probe_rtt_min_stamp
1232 .checked_add(self.probe_rtt_interval)
1233 .unwrap_or(probe_rtt_min_stamp);
1234 } else {
1235 self.probe_rtt_expired = true;
1236 }
1237 if let Some(rate_sample) = self.rs
1238 && rate_sample.rtt >= Duration::from_secs(0)
1239 && (rate_sample.rtt < self.probe_rtt_min_delay || self.probe_rtt_expired)
1240 {
1241 self.probe_rtt_min_delay = rate_sample.rtt;
1242 self.probe_rtt_min_stamp = Some(now);
1243 }
1244
1245 let min_rtt_expired;
1246 if let Some(min_rtt_stamp) = self.min_rtt_stamp {
1247 min_rtt_expired = now
1248 > min_rtt_stamp
1249 .checked_add(Duration::from_secs(MIN_RTT_FILTER_LEN))
1250 .unwrap_or(min_rtt_stamp);
1251 } else {
1252 min_rtt_expired = true;
1253 }
1254 if self.probe_rtt_min_delay < self.min_rtt || min_rtt_expired {
1255 self.min_rtt = self.probe_rtt_min_delay;
1256 self.min_rtt_stamp = self.probe_rtt_min_stamp;
1257 }
1258 }
1259
1260 /// equivalent to BBRHandleProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
1261 fn handle_probe_rtt(&mut self, now: Instant) {
1262 if self.probe_rtt_done_stamp.is_none() && self.inflight <= self.probe_rtt_cwnd() {
1263 self.probe_rtt_done_stamp =
1264 Some(now.checked_add(self.probe_rtt_duration).unwrap_or(now));
1265 self.probe_rtt_round_done = false;
1266 self.start_round();
1267 } else if self.probe_rtt_done_stamp.is_some() {
1268 if self.round_start {
1269 self.probe_rtt_round_done = true;
1270 }
1271 if self.probe_rtt_round_done {
1272 self.check_probe_rtt_done(now);
1273 }
1274 }
1275 }
1276
1277 /// equivalent to BBRCheckProbeRTT <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.3.4.3-4>
1278 fn check_probe_rtt(&mut self, now: Instant) {
1279 match self.state {
1280 BbrState::ProbeRtt => {
1281 self.handle_probe_rtt(now);
1282 }
1283 _ => {
1284 if self.probe_rtt_expired && !self.idle_restart {
1285 self.enter_probe_rtt();
1286 self.save_cwnd();
1287 self.probe_rtt_done_stamp = None;
1288 self.ack_phase = AckPhase::ProbeStopping;
1289 self.start_round();
1290 }
1291 }
1292 }
1293 if let Some(rate_sample) = self.rs
1294 && rate_sample.delivered > 0
1295 {
1296 self.idle_restart = false;
1297 }
1298 }
1299
1300 /// equivalent to BBRAdvanceLatestDeliverySignals <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.3-8>
1301 fn advance_latest_delivery_signals(&mut self) {
1302 if self.loss_round_start
1303 && let Some(rate_sample) = self.rs
1304 {
1305 self.bw_latest = rate_sample.delivery_rate;
1306 self.inflight_latest = rate_sample.delivered;
1307 }
1308 }
1309
1310 /// equivalent to BBRUpdateModelAndState <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.2.3>
1311 fn update_model_and_state(&mut self, p: BbrPacket, now: Instant) {
1312 self.update_latest_delivery_signals();
1313 self.reset_congestion_signals();
1314 self.update_congestion_signals(p);
1315 self.update_ack_aggregation(now);
1316 self.check_full_bw_reached();
1317 self.check_startup_done();
1318 self.check_drain_done(now);
1319 self.update_probe_bw_cycle_phase(now);
1320 self.update_min_rtt(now);
1321 self.check_probe_rtt(now);
1322 self.advance_latest_delivery_signals();
1323 self.bound_bw_for_model();
1324 }
1325
1326 /// equivalent to BBRSetPacingRate <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.2-7>
1327 fn set_pacing_rate(&mut self) {
1328 self.set_pacing_rate_with_gain(self.pacing_gain);
1329 }
1330
1331 /// equivalent to BBRSetSendQuantum <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.3>
1332 /// this version is based on a version of bbr2 from quiche
1333 fn set_send_quantum(&mut self) {
1334 self.send_quantum = match self.pacing_rate {
1335 rate if rate < PACING_RATE_1_2MBPS => MAX_DATAGRAM_SIZE,
1336 rate if rate < PACING_RATE_24MBPS => 2 * MAX_DATAGRAM_SIZE,
1337 _ => min((self.pacing_rate / 1000.0) as u64, HIGH_PACE_MAX_QUANTUM),
1338 };
1339 }
1340
1341 /// equivalent to BBRBoundCwndForModel <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.7>
1342 fn bound_cwnd_for_model(&mut self) {
1343 let mut cap = u64::MAX;
1344 match self.state {
1345 BbrState::ProbeRtt => {
1346 cap = self.inflight_with_headroom();
1347 }
1348 BbrState::ProbeBw(ProbeBwSubstate::Cruise) => {
1349 cap = self.inflight_with_headroom();
1350 }
1351 BbrState::ProbeBw(_) => {
1352 cap = self.inflight_longterm;
1353 }
1354 _ => {}
1355 }
1356 cap = min(cap, self.inflight_shortterm);
1357 cap = max(cap, self.min_pipe_cwnd);
1358 self.cwnd = min(self.cwnd, cap);
1359 }
1360
1361 /// equivalent to BBRSetCwnd <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.6.4.6>
1362 fn set_cwnd(&mut self) {
1363 self.update_max_inflight();
1364 if self.full_bw_reached {
1365 if let Some(rate_sample) = self.rs {
1366 self.cwnd = min(self.cwnd + rate_sample.newly_acked, self.max_inflight);
1367 } else {
1368 self.cwnd = min(self.cwnd, self.max_inflight);
1369 }
1370 } else if (self.cwnd < self.max_inflight || self.delivered < self.initial_cwnd)
1371 && let Some(rate_sample) = self.rs
1372 {
1373 self.cwnd += rate_sample.newly_acked;
1374 }
1375 self.cwnd = max(self.cwnd, self.min_pipe_cwnd);
1376 self.bound_cwnd_for_probe_rtt();
1377 self.bound_cwnd_for_model();
1378 }
1379
1380 /// equivalent to BBRUpdateControlParameters <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.2.3>
1381 fn update_control_parameters(&mut self) {
1382 self.set_pacing_rate();
1383 self.set_send_quantum();
1384 self.set_cwnd();
1385 }
1386
1387 /// equivalent to IsNewestPacket <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.3-3>
1388 fn is_newest_packet(&self, send_time: Instant, end_seq: u64) -> bool {
1389 if let Some(first_send_time) = self.first_send_time {
1390 if send_time > first_send_time {
1391 return true;
1392 }
1393 if let Some(rate_sample) = self.rs
1394 && end_seq > rate_sample.last_end_seq
1395 {
1396 return true;
1397 }
1398 }
1399 false
1400 }
1401
1402 /// equivalent to BBRHandleLostPacket <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.10.2-11>
1403 fn process_lost_packet(&mut self, lost_bytes: u64, packet_index: usize, now: Instant) {
1404 let p = self.packets[packet_index];
1405 self.note_loss();
1406 if !self.bw_probe_samples {
1407 self.packets.remove(packet_index);
1408 return;
1409 }
1410 if let Some(mut rate_sample) = self.rs {
1411 rate_sample.newly_lost += lost_bytes;
1412 rate_sample.tx_in_flight = p.tx_in_flight;
1413 rate_sample.lost = self.lost.saturating_sub(p.lost);
1414 rate_sample.is_app_limited = p.is_app_limited;
1415 if self.is_inflight_too_high() {
1416 rate_sample.tx_in_flight = self.inflight_at_loss(p.size as u64);
1417 self.handle_inflight_too_high(now);
1418 }
1419 self.rs = Some(rate_sample);
1420 }
1421 self.packets.remove(packet_index);
1422 }
1423}
1424impl Controller for Bbr3 {
1425 fn on_packet_sent(&mut self, now: Instant, bytes: u16, pn: u64) {
1426 if self.inflight == 0 {
1427 self.first_send_time = Some(now);
1428 self.delivered_time = Some(now);
1429 }
1430 let added_bytes = bytes as u64;
1431 self.inflight += added_bytes;
1432 self.packets.push_back(BbrPacket {
1433 delivered: self.delivered,
1434 delivered_time: self.delivered_time.unwrap_or(now),
1435 first_send_time: self.first_send_time.unwrap_or(now),
1436 send_time: now,
1437 is_app_limited: self.app_limited != 0,
1438 tx_in_flight: self.inflight,
1439 packet_number: pn,
1440 size: bytes,
1441 lost: self.lost,
1442 acknowledged: false,
1443 stale: false,
1444 round_count: self.round_count,
1445 });
1446 self.handle_restart_from_idle(now);
1447 }
1448
1449 fn on_ack(
1450 &mut self,
1451 now: Instant,
1452 sent: Instant,
1453 bytes: u64,
1454 pn: u64,
1455 _app_limited: bool,
1456 rtt: &RttEstimator,
1457 ) {
1458 if let Some(mut rate_sample) = self.rs {
1459 rate_sample.newly_acked += bytes;
1460 self.rs = Some(rate_sample);
1461 self.delivered += bytes;
1462 self.delivered_time = Some(now);
1463 }
1464 let p_index_result = self.packets.binary_search_by_key(&pn, |p| p.packet_number);
1465 let is_newest_packet = self.is_newest_packet(sent, pn);
1466 if let Ok(p_index) = p_index_result
1467 && let Some(p) = self.packets.get_mut(p_index)
1468 {
1469 p.acknowledged = true;
1470 if let Some(mut rate_sample) = self.rs {
1471 rate_sample.rtt = now - p.send_time;
1472 if is_newest_packet {
1473 self.srtt = rtt.get();
1474 rate_sample.prior_delivered = p.delivered;
1475 rate_sample.prior_time = p.delivered_time;
1476 rate_sample.is_app_limited = p.is_app_limited;
1477 rate_sample.tx_in_flight = p.tx_in_flight;
1478 rate_sample.send_elapsed = p.send_time - p.first_send_time;
1479 rate_sample.ack_elapsed = self.delivered_time.unwrap_or(now) - p.delivered_time;
1480 rate_sample.last_end_seq = pn;
1481 self.first_send_time = Some(p.send_time);
1482 rate_sample.last_packet = *p;
1483 self.rs = Some(rate_sample);
1484 self.update_model_and_state(rate_sample.last_packet, now);
1485 self.update_control_parameters();
1486 }
1487 } else {
1488 let rate_sample = BbrRateSample {
1489 rtt: rtt.get(),
1490 prior_time: p.delivered_time,
1491 interval: Duration::ZERO,
1492 delivery_rate: 0.0,
1493 is_app_limited: p.is_app_limited,
1494 delivered: 0,
1495 prior_delivered: p.delivered,
1496 tx_in_flight: p.tx_in_flight,
1497 send_elapsed: p.send_time - p.first_send_time,
1498 ack_elapsed: self.delivered_time.unwrap_or(now) - p.delivered_time,
1499 newly_acked: bytes,
1500 newly_lost: 0,
1501 lost: 0,
1502 last_end_seq: pn,
1503 last_packet: *p,
1504 };
1505 self.rs = Some(rate_sample);
1506 self.first_send_time = Some(p.send_time);
1507 self.srtt = rate_sample.rtt;
1508 self.update_model_and_state(rate_sample.last_packet, now);
1509 self.update_control_parameters();
1510 }
1511 }
1512 }
1513
1514 fn on_end_acks(
1515 &mut self,
1516 _now: Instant,
1517 in_flight: u64,
1518 app_limited: bool,
1519 largest_packet_num_acked: Option<u64>,
1520 ) {
1521 self.inflight = in_flight;
1522 if let Some(largest_packet_num) = largest_packet_num_acked {
1523 if self.app_limited != 0 && largest_packet_num > self.app_limited {
1524 self.app_limited = 0;
1525 } else if app_limited {
1526 self.app_limited = self.app_limited.max(largest_packet_num);
1527 }
1528 self.packets.retain(|&p| !p.stale);
1529 for p in self.packets.iter_mut() {
1530 if p.acknowledged || self.round_count - p.round_count > ROUND_COUNT_WINDOW {
1531 p.stale = true;
1532 }
1533 }
1534 if let Some(mut rate_sample) = self.rs {
1535 if rate_sample.prior_delivered == 0 {
1536 return;
1537 }
1538 rate_sample.interval = max(rate_sample.send_elapsed, rate_sample.ack_elapsed);
1539 rate_sample.delivered = self.delivered.saturating_sub(rate_sample.prior_delivered);
1540 // ignore this condition on an initially high min rtt as per <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-4.1.2.3-5>
1541 if rate_sample.interval < self.min_rtt
1542 && self.min_rtt != Duration::from_secs(u64::MAX)
1543 {
1544 return;
1545 }
1546 if rate_sample.interval != Duration::ZERO {
1547 rate_sample.delivery_rate =
1548 rate_sample.delivered as f64 / rate_sample.interval.as_secs_f64();
1549 }
1550 if rate_sample.delivered >= self.cwnd {
1551 self.is_cwnd_limited = true;
1552 }
1553 self.rs = Some(rate_sample);
1554 rate_sample.newly_acked = 0;
1555 rate_sample.lost = 0;
1556 rate_sample.newly_lost = 0;
1557 self.rs = Some(rate_sample);
1558 }
1559 }
1560 }
1561
1562 fn on_congestion_event(
1563 &mut self,
1564 now: Instant,
1565 _sent: Instant,
1566 is_persistent_congestion: bool,
1567 is_ecn: bool,
1568 lost_bytes: u64,
1569 largest_lost_pn: u64,
1570 ) {
1571 // only process ecn here, regular packet loss is detected per packet in on_packet_lost.
1572 if is_ecn {
1573 self.lost += lost_bytes;
1574 let p_index_result = self
1575 .packets
1576 .binary_search_by_key(&largest_lost_pn, |p| p.packet_number);
1577 if let Ok(p_index) = p_index_result {
1578 self.process_lost_packet(lost_bytes, p_index, now);
1579 }
1580 if is_persistent_congestion {
1581 self.cwnd = self.min_pipe_cwnd;
1582 }
1583 }
1584 }
1585
1586 fn on_packet_lost(&mut self, lost_bytes: u16, pn: u64, now: Instant) {
1587 let lost_bytes_64 = lost_bytes as u64;
1588 self.lost += lost_bytes_64;
1589 let p_index_result = self.packets.binary_search_by_key(&pn, |p| p.packet_number);
1590 if let Ok(p_index) = p_index_result {
1591 self.process_lost_packet(lost_bytes_64, p_index, now);
1592 }
1593 }
1594
1595 /// equivalent to BBRHandleSpuriousLossDetection: <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html#section-5.5.11.2>
1596 fn on_spurious_congestion_event(&mut self) {
1597 self.loss_in_round = false;
1598 self.reset_full_bw();
1599 self.bw_shortterm = [self.bw_shortterm, self.undo_bw_shortterm]
1600 .iter()
1601 .copied()
1602 .fold(f64::NAN, f64::max);
1603 self.inflight_shortterm = max(self.inflight_shortterm, self.undo_inflight_shortterm);
1604 self.inflight_longterm = max(self.inflight_longterm, self.undo_inflight_longterm);
1605 if self.state != BbrState::ProbeRtt && self.state != self.undo_state {
1606 if self.undo_state == BbrState::Startup {
1607 self.enter_startup();
1608 } else if self.undo_state == BbrState::ProbeBw(ProbeBwSubstate::Up) {
1609 self.start_probe_bw_up();
1610 }
1611 }
1612 }
1613
1614 fn on_mtu_update(&mut self, new_mtu: u16) {
1615 self.smss = min(
1616 max(MIN_MAX_DATAGRAM_SIZE, new_mtu) as u64,
1617 MAX_DATAGRAM_SIZE,
1618 );
1619 self.set_cwnd();
1620 }
1621
1622 fn on_ack_frequency_update(
1623 &mut self,
1624 ack_eliciting_threshold: u64,
1625 requested_max_ack_delay: Duration,
1626 ) {
1627 self.ack_eliciting_threshold = ack_eliciting_threshold;
1628 self.max_ack_delay = requested_max_ack_delay;
1629 }
1630
1631 fn window(&self) -> u64 {
1632 self.cwnd
1633 }
1634
1635 fn metrics(&self) -> ControllerMetrics {
1636 ControllerMetrics {
1637 congestion_window: self.window(),
1638 ssthresh: None,
1639 pacing_rate: Some(self.pacing_rate.round() as u64),
1640 send_quantum: Some(self.send_quantum),
1641 }
1642 }
1643
1644 fn clone_box(&self) -> Box<dyn Controller> {
1645 Box::new(self.clone())
1646 }
1647
1648 fn initial_window(&self) -> u64 {
1649 self.initial_cwnd
1650 }
1651
1652 fn into_any(self: Box<Self>) -> Box<dyn Any> {
1653 self
1654 }
1655}
1656
1657/// Configuration for the `Bbr3` congestion controller
1658///
1659/// Different pacing_gains can be set to modify the multiplier used to
1660/// increase the sending rates.
1661/// Different cwnd_gains can be set to modify the multiplier used to increase
1662/// the congestion windows.
1663/// All of these parameters are specific to different states of the algorithm: see `BbrState`
1664/// `pacing_margin_percent` is used to set a margin when calculating the `pacing_rate` in order
1665/// to not send at 100% capacity when calculating pacing.
1666#[derive(Debug, Clone)]
1667pub struct Bbr3Config {
1668 initial_window: u64,
1669 probe_rng_seed: Option<[u8; 16]>,
1670 startup_pacing_gain: Option<f64>,
1671 default_pacing_gain: Option<f64>,
1672 probe_bw_down_pacing_gain: Option<f64>,
1673 probe_bw_up_pacing_gain: Option<f64>,
1674 probe_bw_up_cwnd_gain: Option<f64>,
1675 probe_rtt_cwnd_gain: Option<f64>,
1676 drain_pacing_gain: Option<f64>,
1677 pacing_margin_percent: Option<f64>,
1678 default_cwnd_gain: Option<f64>,
1679}
1680
1681impl Bbr3Config {
1682 /// Default limit on the amount of outstanding data in bytes.
1683 ///
1684 /// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
1685 pub fn initial_window(&mut self, value: u64) -> &mut Self {
1686 self.initial_window = value;
1687 self
1688 }
1689}
1690
1691impl Default for Bbr3Config {
1692 fn default() -> Self {
1693 Self {
1694 initial_window: 14720.clamp(2 * MAX_DATAGRAM_SIZE, 10 * MAX_DATAGRAM_SIZE),
1695 probe_rng_seed: None,
1696 startup_pacing_gain: None,
1697 default_pacing_gain: None,
1698 probe_bw_down_pacing_gain: None,
1699 probe_bw_up_pacing_gain: None,
1700 probe_bw_up_cwnd_gain: None,
1701 probe_rtt_cwnd_gain: None,
1702 drain_pacing_gain: None,
1703 pacing_margin_percent: None,
1704 default_cwnd_gain: None,
1705 }
1706 }
1707}
1708
1709impl ControllerFactory for Bbr3Config {
1710 fn build(self: Arc<Self>, _now: Instant, current_mtu: u16) -> Box<dyn Controller> {
1711 Box::new(Bbr3::new(self, current_mtu))
1712 }
1713}
1714
1715#[cfg(test)]
1716mod test {
1717 use super::*;
1718
1719 #[test]
1720 fn test_probe_rng() {
1721 let seed: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1722 let config = Bbr3Config {
1723 initial_window: 14720.clamp(2 * MAX_DATAGRAM_SIZE, 10 * MAX_DATAGRAM_SIZE),
1724 probe_rng_seed: Some(seed),
1725 startup_pacing_gain: None,
1726 default_pacing_gain: None,
1727 probe_bw_down_pacing_gain: None,
1728 probe_bw_up_pacing_gain: None,
1729 probe_bw_up_cwnd_gain: None,
1730 probe_rtt_cwnd_gain: None,
1731 drain_pacing_gain: None,
1732 pacing_margin_percent: None,
1733 default_cwnd_gain: None,
1734 };
1735 let mut bbr3 = Bbr3::new(Arc::new(config), 2500);
1736 bbr3.pick_probe_wait();
1737 assert_eq!(bbr3.rounds_since_bw_probe, 1);
1738 assert_eq!(bbr3.bw_probe_wait, Duration::from_millis(2652));
1739 bbr3.pick_probe_wait();
1740 assert_eq!(bbr3.rounds_since_bw_probe, 1);
1741 assert_eq!(bbr3.bw_probe_wait, Duration::from_millis(2570));
1742 }
1743}