Skip to content

Commit

Permalink
Implement PLPMTUD
Browse files Browse the repository at this point in the history
Sponsored by Stormshield
  • Loading branch information
aochagavia committed Mar 27, 2023
1 parent 4b726aa commit b6d032d
Show file tree
Hide file tree
Showing 9 changed files with 1,169 additions and 63 deletions.
100 changes: 93 additions & 7 deletions quinn-proto/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
congestion,
crypto::{self, HandshakeTokenKey, HmacKey},
VarInt, VarIntBoundsExceeded, DEFAULT_SUPPORTED_VERSIONS, INITIAL_MAX_UDP_PAYLOAD_SIZE,
MAX_UDP_PAYLOAD,
};

/// Parameters governing the core QUIC state machine
Expand Down Expand Up @@ -37,6 +38,7 @@ pub struct TransportConfig {
pub(crate) time_threshold: f32,
pub(crate) initial_rtt: Duration,
pub(crate) initial_max_udp_payload_size: u16,
pub(crate) plpmtud_config: Option<PlpmtudConfig>,

pub(crate) persistent_congestion_threshold: u32,
pub(crate) keep_alive_interval: Option<Duration>,
Expand Down Expand Up @@ -154,23 +156,32 @@ impl TransportConfig {
self
}

/// UDP payload size that the network must be capable of carrying
///
/// Effective max UDP payload size may change over the life of the connection in the future due
/// to path MTU detection, but will never fall below this value.
/// The initial value to be used as the maximum UDP payload size before running PLPMTUD (see
/// [`TransportConfig::plpmtud_config`]).
///
/// Must be at least 1200, which is the default, and known to be safe for typical internet
/// applications. Larger values are more efficient, but increase the risk of unpredictable
/// catastrophic packet loss due to exceeding the network path's IP MTU.
/// catastrophic packet loss due to exceeding the network path's IP MTU. If the provided value
/// is higher than what the network path actually supports, packet loss will eventually trigger
/// black hole detection and bring it down to 1200.
///
/// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links
/// outside of either endpoint's control. Extreme caution should be used when raising this value
/// for connections outside of private networks where these factors are fully controlled.
/// outside of either endpoint's control. Caution should be used when raising this value for
/// connections outside of private networks where these factors are fully controlled.
pub fn initial_max_udp_payload_size(&mut self, value: u16) -> &mut Self {
self.initial_max_udp_payload_size = value.max(INITIAL_MAX_UDP_PAYLOAD_SIZE);
self
}

/// Specifies the PLPMTUD config (see [`PlpmtudConfig`] for details). Defaults to
/// [`PlpmtudConfig::default`].
///
/// Setting this value to `None` disables PLPMTUD altogether.
pub fn plpmtud_config(&mut self, value: Option<PlpmtudConfig>) -> &mut Self {
self.plpmtud_config = value;
self
}

/// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.
pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self {
self.persistent_congestion_threshold = value;
Expand Down Expand Up @@ -267,6 +278,7 @@ impl Default for TransportConfig {
time_threshold: 9.0 / 8.0,
initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT
initial_max_udp_payload_size: INITIAL_MAX_UDP_PAYLOAD_SIZE,
plpmtud_config: Some(PlpmtudConfig::default()),

persistent_congestion_threshold: 3,
keep_alive_interval: None,
Expand Down Expand Up @@ -316,6 +328,80 @@ impl fmt::Debug for TransportConfig {
}
}

/// Parameters governing PLPMTUD.
///
/// PLPMTUD performs a binary search, trying to find the highest packet size that is still
/// supported by the current network path. The lower bound of the search is equal to
/// [`TransportConfig::initial_max_udp_payload_size`] in the initial PLPMTUD round, and is equal
/// to the currently discovered MTU in subsequent runs. The upper bound is determined by
/// [`PlpmtudConfig::max_udp_payload_size_upper_bound`].
///
/// If, at some point, the network path no longer accepts packets of the detected size, packet
/// loss will eventually trigger black hole detection and reset the detected MTU to 1200. In
/// that case, PLPMTUD will be triggered after [`PlpmtudConfig::black_hole_cooldown`] (ignoring the
/// timer that was set based on [`PlpmtudConfig::interval`]).
#[derive(Clone)]
pub struct PlpmtudConfig {
pub(crate) interval: Duration,
pub(crate) max_udp_payload_size_upper_bound: u16,
pub(crate) black_hole_cooldown: Duration,
}

impl PlpmtudConfig {
/// Specifies the time to wait after completing PLPMTUD before starting a new MTU discovery
/// round. Defaults to 600 seconds, as recommended by
/// [RFC 8899](https://www.rfc-editor.org/rfc/rfc8899).
pub fn interval(&mut self, value: Duration) -> &mut Self {
self.interval = value;
self
}

/// Specifies the upper bound to the max UDP payload size that PLPMTUD will search for.
///
/// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest
/// allowed value is 65527, which corresponds to the maximum permitted UDP payload.
///
/// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The
/// only drawback is that PLPMTUD might take more time to finish.
pub fn max_udp_payload_size_upper_bound(&mut self, value: u16) -> &mut Self {
self.max_udp_payload_size_upper_bound = value.min(MAX_UDP_PAYLOAD);
self
}

/// Specifies the amount of time that PLPMTUD should wait after a black hole was detected before
/// running again. Defaults to one minute.
///
/// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to
/// try PLPMTUD again after a short period of time.
pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self {
self.black_hole_cooldown = value;
self
}
}

impl Default for PlpmtudConfig {
fn default() -> Self {
PlpmtudConfig {
interval: Duration::from_secs(600),
max_udp_payload_size_upper_bound: 1452,
black_hole_cooldown: Duration::from_secs(60),
}
}
}

impl fmt::Debug for PlpmtudConfig {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("PlpmtudConfig")
.field("interval", &self.interval)
.field(
"max_udp_payload_size_upper_bound",
&self.max_udp_payload_size_upper_bound,
)
.field("black_hole_cooldown", &self.black_hole_cooldown)
.finish()
}
}

/// Global configuration for the endpoint, affecting all connections
///
/// Default values should be suitable for most internet applications.
Expand Down
3 changes: 1 addition & 2 deletions quinn-proto/src/connection/datagrams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ impl<'a> Datagrams<'a> {
///
/// Not necessarily the maximum size of received datagrams.
pub fn max_size(&self) -> Option<usize> {
// This is usually 1162 bytes, but we shouldn't document that without a doctest.
let max_size = self.conn.path.max_udp_payload_size as usize
let max_size = self.conn.path.current_mtu() as usize
- 1 // flags byte
- self.conn.rem_cids.active().len()
- 4 // worst-case packet number size
Expand Down
Loading

0 comments on commit b6d032d

Please sign in to comment.