Skip to content

Commit

Permalink
Notify congestion controllers of MTU updates
Browse files Browse the repository at this point in the history
Sponsored by Stormshield
  • Loading branch information
aochagavia committed Apr 27, 2023
1 parent 86ea9de commit 699a914
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 104 deletions.
7 changes: 6 additions & 1 deletion quinn-proto/src/congestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub trait Controller: Send {
lost_bytes: u64,
);

/// The known MTU for the current network path has been updated
fn on_mtu_update(&mut self, current_mtu: u16);

/// Number of ack-eliciting bytes that may be in flight
fn window(&self) -> u64;

Expand All @@ -74,5 +77,7 @@ pub trait Controller: Send {
/// Constructs controllers on demand
pub trait ControllerFactory {
/// Construct a fresh `Controller`
fn build(&self, now: Instant) -> Box<dyn Controller>;
fn build(&self, now: Instant, current_mtu: u16) -> Box<dyn Controller>;
}

const BASE_DATAGRAM_SIZE: u64 = 1200;
52 changes: 21 additions & 31 deletions quinn-proto/src/congestion/bbr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::congestion::bbr::bw_estimation::BandwidthEstimation;
use crate::congestion::bbr::min_max::MinMax;
use crate::connection::RttEstimator;

use super::{Controller, ControllerFactory};
use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE};

mod bw_estimation;
mod min_max;
Expand All @@ -23,6 +23,7 @@ mod min_max;
#[derive(Debug, Clone)]
pub struct Bbr {
config: Arc<BbrConfig>,
current_mtu: u64,
max_bandwidth: BandwidthEstimation,
acked_bytes: u64,
mode: Mode,
Expand Down Expand Up @@ -59,11 +60,11 @@ pub struct Bbr {

impl Bbr {
/// Construct a state using the given `config` and current time `now`
pub fn new(config: Arc<BbrConfig>) -> Self {
pub fn new(config: Arc<BbrConfig>, current_mtu: u16) -> Self {
let initial_window = config.initial_window;
let min_window = config.minimum_window;
Self {
config,
current_mtu: current_mtu as u64,
max_bandwidth: BandwidthEstimation::default(),
acked_bytes: 0,
mode: Mode::Startup,
Expand All @@ -79,7 +80,7 @@ impl Bbr {
last_cycle_start: None,
current_cycle_offset: 0,
init_cwnd: initial_window,
min_cwnd: min_window,
min_cwnd: calculate_min_window(current_mtu as u64),
prev_in_flight_count: 0,
exit_probe_rtt_at: None,
probe_rtt_last_started_at: None,
Expand Down Expand Up @@ -238,7 +239,7 @@ impl Bbr {
// ProbeRtt. The CWND during ProbeRtt is
// kMinimumCongestionWindow, but we allow an extra packet since QUIC
// checks CWND before sending a packet.
if bytes_in_flight < self.get_probe_rtt_cwnd() + MAX_DATAGRAM_SIZE {
if bytes_in_flight < self.get_probe_rtt_cwnd() + self.current_mtu {
const K_PROBE_RTT_TIME: Duration = Duration::from_millis(200);
self.exit_probe_rtt_at = Some(now + K_PROBE_RTT_TIME);
}
Expand Down Expand Up @@ -344,8 +345,8 @@ impl Bbr {
if self.recovery_window >= bytes_lost {
self.recovery_window -= bytes_lost;
} else {
const K_MAX_SEGMENT_SIZE: u64 = MAX_DATAGRAM_SIZE;
self.recovery_window = K_MAX_SEGMENT_SIZE;
let k_max_segment_size: u64 = self.current_mtu;
self.recovery_window = k_max_segment_size;
}
// In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH,
// release additional |bytes_acked| to achieve a slow-start-like behavior.
Expand Down Expand Up @@ -468,6 +469,13 @@ impl Controller for Bbr {
self.loss_state.lost_bytes += lost_bytes;
}

fn on_mtu_update(&mut self, current_mtu: u16) {
self.current_mtu = current_mtu as u64;
self.min_cwnd = calculate_min_window(self.current_mtu);
self.init_cwnd = self.config.initial_window.max(self.min_cwnd);
self.cwnd = self.cwnd.max(self.min_cwnd);
}

fn window(&self) -> u64 {
if self.mode == Mode::ProbeRtt {
return self.get_probe_rtt_cwnd();
Expand All @@ -493,50 +501,30 @@ impl Controller for Bbr {
/// Configuration for the [`Bbr`] congestion controller
#[derive(Debug, Clone)]
pub struct BbrConfig {
max_datagram_size: u64,
initial_window: u64,
minimum_window: u64,
}

impl BbrConfig {
/// The sender’s maximum UDP payload size. Does not include UDP or IP overhead.
///
/// Used for calculating initial and minimum congestion windows.
pub fn max_datagram_size(&mut self, value: u64) -> &mut Self {
self.max_datagram_size = value;
self
}

/// Default limit on the amount of outstanding data in bytes.
///
/// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
pub fn initial_window(&mut self, value: u64) -> &mut Self {
self.initial_window = value;
self
}

/// Default minimum congestion window.
///
/// Recommended value: `2 * max_datagram_size`.
pub fn minimum_window(&mut self, value: u64) -> &mut Self {
self.minimum_window = value;
self
}
}

impl Default for BbrConfig {
fn default() -> Self {
Self {
max_datagram_size: MAX_DATAGRAM_SIZE,
initial_window: K_MAX_INITIAL_CONGESTION_WINDOW * MAX_DATAGRAM_SIZE,
minimum_window: 4 * MAX_DATAGRAM_SIZE,
initial_window: K_MAX_INITIAL_CONGESTION_WINDOW * BASE_DATAGRAM_SIZE,
}
}
}

impl ControllerFactory for Arc<BbrConfig> {
fn build(&self, _now: Instant) -> Box<dyn Controller> {
Box::new(Bbr::new(self.clone()))
fn build(&self, _now: Instant, current_mtu: u16) -> Box<dyn Controller> {
Box::new(Bbr::new(self.clone(), current_mtu))
}
}

Expand Down Expand Up @@ -628,7 +616,9 @@ impl LossState {
}
}

const MAX_DATAGRAM_SIZE: u64 = 1232;
fn calculate_min_window(current_mtu: u64) -> u64 {
4 * current_mtu
}

// The gain used for the STARTUP, equal to 2/ln(2).
const K_DEFAULT_HIGH_GAIN: f32 = 2.885;
Expand Down
66 changes: 26 additions & 40 deletions quinn-proto/src/congestion/cubic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::any::Any;
use std::sync::Arc;
use std::time::{Duration, Instant};

use super::{Controller, ControllerFactory};
use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE};
use crate::connection::RttEstimator;
use std::cmp;

Expand Down Expand Up @@ -69,19 +69,25 @@ pub struct Cubic {
/// after this time is acknowledged, QUIC exits recovery.
recovery_start_time: Option<Instant>,
cubic_state: State,
current_mtu: u64,
}

impl Cubic {
/// Construct a state using the given `config` and current time `now`
pub fn new(config: Arc<CubicConfig>, _now: Instant) -> Self {
pub fn new(config: Arc<CubicConfig>, _now: Instant, current_mtu: u16) -> Self {
Self {
window: config.initial_window,
ssthresh: u64::MAX,
recovery_start_time: None,
config,
cubic_state: Default::default(),
current_mtu: current_mtu as u64,
}
}

fn minimum_window(&self) -> u64 {
2 * self.current_mtu
}
}

impl Controller for Cubic {
Expand Down Expand Up @@ -125,14 +131,10 @@ impl Controller for Cubic {
let t = now - ca_start_time;

// w_cubic(t + rtt)
let w_cubic = self
.cubic_state
.w_cubic(t + rtt.get(), self.config.max_datagram_size);
let w_cubic = self.cubic_state.w_cubic(t + rtt.get(), self.current_mtu);

// w_est(t)
let w_est = self
.cubic_state
.w_est(t, rtt.get(), self.config.max_datagram_size);
let w_est = self.cubic_state.w_est(t, rtt.get(), self.current_mtu);

let mut cubic_cwnd = self.window;

Expand All @@ -141,8 +143,8 @@ impl Controller for Cubic {
cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64);
} else if cubic_cwnd < w_cubic as u64 {
// Concave region or convex region use same increment.
let cubic_inc = (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64
* self.config.max_datagram_size as f64;
let cubic_inc =
(w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64 * self.current_mtu as f64;

cubic_cwnd += cubic_inc as u64;
}
Expand All @@ -153,8 +155,8 @@ impl Controller for Cubic {
// cwnd_inc can be more than 1 MSS in the late stage of max probing.
// however RFC9002 §7.3.3 (Congestion Avoidance) limits
// the increase of cwnd to 1 max_datagram_size per cwnd acknowledged.
if self.cubic_state.cwnd_inc >= self.config.max_datagram_size {
self.window += self.config.max_datagram_size;
if self.cubic_state.cwnd_inc >= self.current_mtu {
self.window += self.current_mtu;
self.cubic_state.cwnd_inc = 0;
}
}
Expand Down Expand Up @@ -188,10 +190,10 @@ impl Controller for Cubic {

self.ssthresh = cmp::max(
(self.cubic_state.w_max * BETA_CUBIC) as u64,
self.config.minimum_window,
self.minimum_window(),
);
self.window = self.ssthresh;
self.cubic_state.k = self.cubic_state.cubic_k(self.config.max_datagram_size);
self.cubic_state.k = self.cubic_state.cubic_k(self.current_mtu);

self.cubic_state.cwnd_inc = (self.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as u64;

Expand All @@ -202,15 +204,20 @@ impl Controller for Cubic {
// 4.7 Timeout - reduce ssthresh based on BETA_CUBIC
self.ssthresh = cmp::max(
(self.window as f64 * BETA_CUBIC) as u64,
self.config.minimum_window,
self.minimum_window(),
);

self.cubic_state.cwnd_inc = 0;

self.window = self.config.minimum_window;
self.window = self.minimum_window();
}
}

fn on_mtu_update(&mut self, current_mtu: u16) {
self.current_mtu = current_mtu as u64;
self.window = self.window.max(self.minimum_window());
}

fn window(&self) -> u64 {
self.window
}
Expand All @@ -231,50 +238,29 @@ impl Controller for Cubic {
/// Configuration for the `Cubic` congestion controller
#[derive(Debug, Clone)]
pub struct CubicConfig {
max_datagram_size: u64,
initial_window: u64,
minimum_window: u64,
}

impl CubicConfig {
/// The sender’s maximum UDP payload size. Does not include UDP or IP overhead.
///
/// Used for calculating initial and minimum congestion windows.
pub fn max_datagram_size(&mut self, value: u64) -> &mut Self {
self.max_datagram_size = value;
self
}

/// Default limit on the amount of outstanding data in bytes.
///
/// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))`
pub fn initial_window(&mut self, value: u64) -> &mut Self {
self.initial_window = value;
self
}

/// Default minimum congestion window.
///
/// Recommended value: `2 * max_datagram_size`.
pub fn minimum_window(&mut self, value: u64) -> &mut Self {
self.minimum_window = value;
self
}
}

impl Default for CubicConfig {
fn default() -> Self {
const MAX_DATAGRAM_SIZE: u64 = 1232;
Self {
max_datagram_size: MAX_DATAGRAM_SIZE,
initial_window: 14720.clamp(2 * MAX_DATAGRAM_SIZE, 10 * MAX_DATAGRAM_SIZE),
minimum_window: 2 * MAX_DATAGRAM_SIZE,
initial_window: 14720.clamp(2 * BASE_DATAGRAM_SIZE, 10 * BASE_DATAGRAM_SIZE),
}
}
}

impl ControllerFactory for Arc<CubicConfig> {
fn build(&self, now: Instant) -> Box<dyn Controller> {
Box::new(Cubic::new(self.clone(), now))
fn build(&self, now: Instant, current_mtu: u16) -> Box<dyn Controller> {
Box::new(Cubic::new(self.clone(), now, current_mtu))
}
}
Loading

0 comments on commit 699a914

Please sign in to comment.