Skip to content

Commit 56a97a5

Browse files
committed
feat(congestion-control): Implement BBR
1 parent 17012eb commit 56a97a5

File tree

7 files changed

+1068
-6
lines changed

7 files changed

+1068
-6
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ defmt = ["dep:defmt", "heapless/defmt"]
9393
# Enable Reno TCP congestion control algorithm, and it is used as a default congestion controller.
9494
"socket-tcp-reno" = []
9595

96+
# Enable BBR TCP congestion control algorithm, and it is used as a default congestion controller.
97+
"socket-tcp-bbr" = []
98+
9699
"packetmeta-id" = []
97100

98101
"async" = []

examples/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn main() {
3131
// Create interface
3232
let mut config = match device.capabilities().medium {
3333
Medium::Ethernet => {
34-
Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into())
34+
Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]).into())
3535
}
3636
Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip),
3737
Medium::Ieee802154 => todo!(),
@@ -41,7 +41,7 @@ fn main() {
4141
let mut iface = Interface::new(config, &mut device, Instant::now());
4242
iface.update_ip_addrs(|ip_addrs| {
4343
ip_addrs
44-
.push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24))
44+
.push(IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24))
4545
.unwrap();
4646
ip_addrs
4747
.push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64))

src/socket/tcp.rs

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ const RTTE_MIN_RTO: u32 = 1000;
161161
// seconds
162162
const RTTE_MAX_RTO: u32 = 60_000;
163163

164+
// BBR: Window length for min_rtt filter (in seconds)
165+
// This matches the Linux kernel BBR implementation (tcp_bbr.c:135)
166+
#[cfg(feature = "socket-tcp-bbr")]
167+
const BBR_MIN_RTT_WIN_SEC: u64 = 10;
168+
164169
#[derive(Debug, Clone, Copy)]
165170
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
166171
struct RttEstimator {
@@ -176,6 +181,12 @@ struct RttEstimator {
176181
timestamp: Option<(Instant, TcpSeqNumber)>,
177182
max_seq_sent: Option<TcpSeqNumber>,
178183
rto_count: u8,
184+
/// BBR: Minimum RTT observed in the last 10 seconds
185+
#[cfg(feature = "socket-tcp-bbr")]
186+
min_rtt_value: u32,
187+
/// BBR: Timestamp when min_rtt was last updated
188+
#[cfg(feature = "socket-tcp-bbr")]
189+
min_rtt_stamp: Option<Instant>,
179190
}
180191

181192
impl Default for RttEstimator {
@@ -188,6 +199,10 @@ impl Default for RttEstimator {
188199
timestamp: None,
189200
max_seq_sent: None,
190201
rto_count: 0,
202+
#[cfg(feature = "socket-tcp-bbr")]
203+
min_rtt_value: u32::MAX,
204+
#[cfg(feature = "socket-tcp-bbr")]
205+
min_rtt_stamp: None,
191206
}
192207
}
193208
}
@@ -197,7 +212,33 @@ impl RttEstimator {
197212
Duration::from_millis(self.rto as _)
198213
}
199214

200-
fn sample(&mut self, new_rtt: u32) {
215+
#[cfg(feature = "socket-tcp-bbr")]
216+
pub(super) fn min_rtt(&self) -> Duration {
217+
// Return the actual minimum RTT observed in the window, not SRTT
218+
// If no measurement yet, fall back to SRTT as a reasonable estimate
219+
if self.min_rtt_value == u32::MAX {
220+
Duration::from_millis(self.srtt as _)
221+
} else {
222+
Duration::from_millis(self.min_rtt_value as _)
223+
}
224+
}
225+
226+
#[cfg(feature = "socket-tcp-bbr")]
227+
pub(super) fn is_min_rtt_expired(&self, now: Instant) -> bool {
228+
// Check if the min_rtt window has expired (10 seconds)
229+
// This matches Linux kernel BBR behavior (tcp_bbr.c:948-949)
230+
if let Some(stamp) = self.min_rtt_stamp {
231+
if now >= stamp {
232+
(now - stamp) > Duration::from_secs(BBR_MIN_RTT_WIN_SEC)
233+
} else {
234+
false // Time went backwards, don't consider expired
235+
}
236+
} else {
237+
true // No measurement yet, consider expired
238+
}
239+
}
240+
241+
fn sample(&mut self, new_rtt: u32, #[allow(unused_variables)] now: Instant) {
201242
if self.have_measurement {
202243
// RFC 6298 (2.3) When a subsequent RTT measurement R' is made, a host MUST set (...)
203244
let diff = (self.srtt as i32 - new_rtt as i32).unsigned_abs();
@@ -216,6 +257,27 @@ impl RttEstimator {
216257

217258
self.rto_count = 0;
218259

260+
// BBR: Track minimum RTT in a sliding 10-second window
261+
// This matches Linux kernel BBR behavior (tcp_bbr.c:947-955)
262+
#[cfg(feature = "socket-tcp-bbr")]
263+
{
264+
let expired = self.is_min_rtt_expired(now);
265+
266+
// Update min_rtt if:
267+
// 1. New sample is lower than current minimum, OR
268+
// 2. The window has expired (need fresh measurement)
269+
if new_rtt < self.min_rtt_value || expired {
270+
self.min_rtt_value = new_rtt;
271+
self.min_rtt_stamp = Some(now);
272+
273+
tcp_trace!(
274+
"rtte: min_rtt updated to {:?}ms (expired={})",
275+
new_rtt,
276+
expired
277+
);
278+
}
279+
}
280+
219281
tcp_trace!(
220282
"rtte: sample={:?} srtt={:?} rttvar={:?} rto={:?}",
221283
new_rtt,
@@ -242,7 +304,7 @@ impl RttEstimator {
242304
fn on_ack(&mut self, timestamp: Instant, seq: TcpSeqNumber) {
243305
if let Some((sent_timestamp, sent_seq)) = self.timestamp {
244306
if seq >= sent_seq {
245-
self.sample((timestamp - sent_timestamp).total_millis() as u32);
307+
self.sample((timestamp - sent_timestamp).total_millis() as u32, timestamp);
246308
self.timestamp = None;
247309
}
248310
}
@@ -454,6 +516,9 @@ pub enum CongestionControl {
454516

455517
#[cfg(feature = "socket-tcp-cubic")]
456518
Cubic,
519+
520+
#[cfg(feature = "socket-tcp-bbr")]
521+
Bbr,
457522
}
458523

459524
/// A Transmission Control Protocol socket.
@@ -657,6 +722,9 @@ impl<'a> Socket<'a> {
657722

658723
#[cfg(feature = "socket-tcp-cubic")]
659724
CongestionControl::Cubic => AnyController::Cubic(cubic::Cubic::new()),
725+
726+
#[cfg(feature = "socket-tcp-bbr")]
727+
CongestionControl::Bbr => AnyController::Bbr(bbr::Bbr::new()),
660728
}
661729
}
662730

@@ -672,6 +740,9 @@ impl<'a> Socket<'a> {
672740

673741
#[cfg(feature = "socket-tcp-cubic")]
674742
AnyController::Cubic(_) => CongestionControl::Cubic,
743+
744+
#[cfg(feature = "socket-tcp-bbr")]
745+
AnyController::Bbr(_) => CongestionControl::Bbr,
675746
}
676747
}
677748

@@ -2368,6 +2439,12 @@ impl<'a> Socket<'a> {
23682439
.inner_mut()
23692440
.pre_transmit(cx.now());
23702441

2442+
// Notify congestion controller about available data for app-limited tracking
2443+
let bytes_available = self.tx_buffer.len();
2444+
self.congestion_controller
2445+
.inner_mut()
2446+
.on_send_ready(cx.now(), bytes_available);
2447+
23712448
// Check if any state needs to be changed because of a timer.
23722449
if self.timed_out(cx.now()) {
23732450
// If a timeout expires, we should abort the connection.
@@ -8570,9 +8647,11 @@ mod test {
85708647
2076, 2060, 2048, 2036, 2028, 2024, 2020, 2016, 2012, 2012,
85718648
];
85728649

8650+
let mut now = Instant::from_millis(0);
85738651
for &rto in rtos {
8574-
r.sample(2000);
8652+
r.sample(2000, now);
85758653
assert_eq!(r.retransmission_timeout(), Duration::from_millis(rto));
8654+
now += Duration::from_millis(100); // Advance time
85768655
}
85778656
}
85788657

src/socket/tcp/congestion.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ pub(super) mod cubic;
1010
#[cfg(feature = "socket-tcp-reno")]
1111
pub(super) mod reno;
1212

13+
#[cfg(feature = "socket-tcp-bbr")]
14+
pub(super) mod bbr;
15+
1316
#[allow(unused_variables)]
1417
pub(super) trait Controller {
1518
/// Returns the number of bytes that can be sent.
@@ -30,10 +33,17 @@ pub(super) trait Controller {
3033

3134
/// Set the maximum segment size.
3235
fn set_mss(&mut self, mss: usize) {}
36+
37+
/// Called when the socket is about to send data.
38+
/// `bytes_available` indicates how many bytes are waiting in the send buffer.
39+
/// This allows the congestion controller to track whether the application
40+
/// is app-limited (not enough data to send) or cwnd-limited.
41+
fn on_send_ready(&mut self, now: Instant, bytes_available: usize) {}
3342
}
3443

3544
#[derive(Debug)]
3645
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46+
#[allow(clippy::large_enum_variant)]
3747
pub(super) enum AnyController {
3848
None(no_control::NoControl),
3949

@@ -42,15 +52,20 @@ pub(super) enum AnyController {
4252

4353
#[cfg(feature = "socket-tcp-cubic")]
4454
Cubic(cubic::Cubic),
55+
56+
#[cfg(feature = "socket-tcp-bbr")]
57+
Bbr(bbr::Bbr),
4558
}
4659

4760
impl AnyController {
4861
/// Create a new congestion controller.
4962
/// `AnyController::new()` selects the best congestion controller based on the features.
5063
///
64+
/// - If `socket-tcp-bbr` feature is enabled, it will use `Bbr`.
5165
/// - If `socket-tcp-cubic` feature is enabled, it will use `Cubic`.
5266
/// - If `socket-tcp-reno` feature is enabled, it will use `Reno`.
53-
/// - If both `socket-tcp-cubic` and `socket-tcp-reno` features are enabled, it will use `Cubic`.
67+
/// - Priority: BBR > Cubic > Reno > NoControl
68+
/// - `BBR` is optimized for high bandwidth-delay product networks.
5469
/// - `Cubic` is more efficient regarding throughput.
5570
/// - `Reno` is more conservative and is suitable for low-power devices.
5671
/// - If no congestion controller is available, it will use `NoControl`.
@@ -60,6 +75,11 @@ impl AnyController {
6075
#[allow(unreachable_code)]
6176
#[inline]
6277
pub fn new() -> Self {
78+
#[cfg(feature = "socket-tcp-bbr")]
79+
{
80+
return AnyController::Bbr(bbr::Bbr::new());
81+
}
82+
6383
#[cfg(feature = "socket-tcp-cubic")]
6484
{
6585
return AnyController::Cubic(cubic::Cubic::new());
@@ -83,6 +103,9 @@ impl AnyController {
83103

84104
#[cfg(feature = "socket-tcp-cubic")]
85105
AnyController::Cubic(c) => c,
106+
107+
#[cfg(feature = "socket-tcp-bbr")]
108+
AnyController::Bbr(b) => b,
86109
}
87110
}
88111

@@ -96,6 +119,9 @@ impl AnyController {
96119

97120
#[cfg(feature = "socket-tcp-cubic")]
98121
AnyController::Cubic(c) => c,
122+
123+
#[cfg(feature = "socket-tcp-bbr")]
124+
AnyController::Bbr(b) => b,
99125
}
100126
}
101127
}

0 commit comments

Comments
 (0)