pub struct MtuDiscoveryConfig { /* private fields */ }
Expand description

Parameters governing MTU discovery.

The why of MTU discovery

By design, QUIC ensures during the handshake that the network path between the client and the server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, once the connection is established, we know that the network path’s maximum transmission unit (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can split outgoing data in packets of 1200 bytes, with confidence that the network will be able to deliver them (if the endpoint were to send bigger packets, they could prove too big and end up being dropped).

There is, however, a significant overhead associated to sending a packet. If the same information can be sent in fewer packets, that results in higher throughput. The amount of packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the bigger the packets that can be sent, and the fewer packets that are needed to transmit a given amount of bytes.

Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the path’s MTU and, if it turns out to be higher, start sending bigger packets.

MTU discovery internals

Quinn implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU Discovery), described in section 14.3 of RFC 9000. This method consists of sending QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote peer, which in turn means that the network path’s MTU is of at least the packet’s size. If the probe is lost, it is sent another 2 times before concluding that the MTU is lower than the packet’s size.

MTU discovery runs on a schedule (e.g. every 600 seconds) specified through MtuDiscoveryConfig::interval. The first run happens right after the handshake, and subsequent discoveries are scheduled to run when the interval has elapsed, starting from the last time when MTU discovery completed.

Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest is 65527), Quinn performs a binary search to keep the number of probes as low as possible. The lower bound of the search is equal to TransportConfig::initial_mtu in the initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The upper bound is determined by the minimum of MtuDiscoveryConfig::upper_bound and the max_udp_payload_size transport parameter received from the peer during the handshake.

Black hole detection

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, MTU discovery will be triggered after MtuDiscoveryConfig::black_hole_cooldown (ignoring the timer that was set based on MtuDiscoveryConfig::interval).

Interaction between peers

There is no guarantee that the MTU on the path between A and B is the same as the MTU of the path between B and A. Therefore, each peer in the connection needs to run MTU discovery independently in order to discover the path’s MTU.

Implementations§

source§

impl MtuDiscoveryConfig

source

pub fn interval(&mut self, value: Duration) -> &mut Self

Specifies the time to wait after completing MTU discovery before starting a new MTU discovery run.

Defaults to 600 seconds, as recommended by RFC 8899.

source

pub fn upper_bound(&mut self, value: u16) -> &mut Self

Specifies the upper bound to the max UDP payload size that MTU discovery 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 on IPv6.

It is safe to use an arbitrarily high upper bound, regardless of the network path’s MTU. The only drawback is that MTU discovery might take more time to finish.

source

pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self

Specifies the amount of time that MTU discovery 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 MTU discovery again after a short period of time.

Trait Implementations§

source§

impl Clone for MtuDiscoveryConfig

source§

fn clone(&self) -> MtuDiscoveryConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for MtuDiscoveryConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for MtuDiscoveryConfig

source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more