s2n_quic_core/path/
mtu.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    counter::{Counter, Saturating},
6    event::{self, builder::MtuUpdatedCause, IntoEvent},
7    frame, inet,
8    packet::number::PacketNumber,
9    path,
10    path::mtu,
11    recovery::{congestion_controller, CongestionController},
12    time::{timer, Timer, Timestamp},
13    transmission,
14};
15use core::{
16    fmt,
17    fmt::{Display, Formatter},
18    num::NonZeroU16,
19    time::Duration,
20};
21use s2n_codec::EncoderValue;
22
23#[cfg(test)]
24mod tests;
25
26#[cfg(any(test, feature = "testing"))]
27pub mod testing {
28    use super::*;
29    use crate::inet::{IpV4Address, SocketAddressV4};
30
31    /// Creates a new mtu::Controller with an IPv4 address and the given `max_mtu`
32    pub fn new_controller(max_mtu: u16) -> Controller {
33        let ip = IpV4Address::new([127, 0, 0, 1]);
34        let addr = inet::SocketAddress::IpV4(SocketAddressV4::new(ip, 443));
35        Controller::new(
36            Config {
37                max_mtu: max_mtu.try_into().unwrap(),
38                ..Default::default()
39            },
40            &addr,
41        )
42    }
43
44    /// Creates a new mtu::Controller with the given mtu and probed size
45    pub fn test_controller(mtu: u16, probed_size: u16) -> Controller {
46        let mut controller = new_controller(u16::MAX);
47        controller.plpmtu = mtu;
48        controller.probed_size = probed_size;
49        controller
50    }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
54enum State {
55    /// EARLY_SEARCH_REQUESTED indicates the initial MTU was configured higher
56    /// than the base MTU, to allow for quick confirmation or rejection of the
57    /// initial MTU
58    EarlySearchRequested,
59    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.2
60    //# The DISABLED state is the initial state before probing has started.
61    Disabled,
62    /// SEARCH_REQUESTED is used to indicate a probe packet has been requested
63    /// to be transmitted, but has not been transmitted yet.
64    SearchRequested,
65    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.2
66    //# The SEARCHING state is the main probing state.
67    Searching(PacketNumber, Timestamp),
68    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.2
69    //# The SEARCH_COMPLETE state indicates that a search has completed.
70    SearchComplete,
71}
72
73impl State {
74    /// Returns true if the MTU controller is in the early search requested state
75    fn is_early_search_requested(&self) -> bool {
76        matches!(self, State::EarlySearchRequested)
77    }
78
79    /// Returns true if the MTU controller is in the disabled state
80    fn is_disabled(&self) -> bool {
81        matches!(self, State::Disabled)
82    }
83
84    /// Returns true if the MTU controller is in the search complete state
85    fn is_search_complete(&self) -> bool {
86        matches!(self, State::SearchComplete)
87    }
88}
89
90//= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.2
91//# The MAX_PROBES is the maximum value of the PROBE_COUNT
92//# counter (see Section 5.1.3).  MAX_PROBES represents the limit for
93//# the number of consecutive probe attempts of any size.  Search
94//# algorithms benefit from a MAX_PROBES value greater than 1 because
95//# this can provide robustness to isolated packet loss.  The default
96//# value of MAX_PROBES is 3.
97const MAX_PROBES: u8 = 3;
98
99/// The minimum length of the data field of a packet sent over an
100/// Ethernet is 1500 octets, thus the maximum length of an IP datagram
101/// sent over an Ethernet is 1500 octets.
102/// See https://www.rfc-editor.org/rfc/rfc894.txt
103const ETHERNET_MTU: u16 = 1500;
104
105/// If the next value to probe is within the PROBE_THRESHOLD bytes of
106/// the current Path MTU, probing will be considered complete.
107const PROBE_THRESHOLD: u16 = 20;
108
109/// When the black_hole_counter exceeds this threshold, on_black_hole_detected will be
110/// called to reduce the MTU to the BASE_PLPMTU. The black_hole_counter is incremented when
111/// a burst of consecutive packets is lost that starts with a packet that is:
112///      1) not an MTU probe
113///      2) larger than the BASE_PLPMTU
114///      3) sent after the largest MTU-sized acknowledged packet number
115/// This is a possible indication that the path cannot support the MTU that was previously confirmed.
116const BLACK_HOLE_THRESHOLD: u8 = 3;
117
118/// After a black hole has been detected, the mtu::Controller will wait this duration
119/// before probing for a larger MTU again.
120const BLACK_HOLE_COOL_OFF_DURATION: Duration = Duration::from_secs(60);
121
122//= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.1
123//# The PMTU_RAISE_TIMER is configured to the period a
124//# sender will continue to use the current PLPMTU, after which it
125//# reenters the Search Phase.  This timer has a period of 600
126//# seconds, as recommended by PLPMTUD [RFC4821].
127const PMTU_RAISE_TIMER_DURATION: Duration = Duration::from_secs(600);
128
129//= https://www.rfc-editor.org/rfc/rfc9000#section-14
130//# QUIC MUST NOT be used if the network path cannot support a
131//# maximum datagram size of at least 1200 bytes.
132
133//= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.2
134//# When using IPv4, there is no currently equivalent size specified,
135//# and a default BASE_PLPMTU of 1200 bytes is RECOMMENDED.
136pub const MINIMUM_MAX_DATAGRAM_SIZE: u16 = 1200;
137
138// Length is the length in octets of this user datagram  including  this
139// header and the data. (This means the minimum value of the length is
140// eight.)
141// See https://www.rfc-editor.org/rfc/rfc768.txt
142const UDP_HEADER_LEN: u16 = 8;
143
144// IPv4 header ranges from 20-60 bytes, depending on Options
145const IPV4_MIN_HEADER_LEN: u16 = 20;
146// IPv6 header is always 40 bytes, plus extensions
147const IPV6_MIN_HEADER_LEN: u16 = 40;
148
149// The minimum allowed Max MTU is the minimum UDP datagram size of 1200 bytes plus
150// the UDP header length and minimal IP header length
151const fn const_min(a: u16, b: u16) -> u16 {
152    if a < b {
153        a
154    } else {
155        b
156    }
157}
158
159const MINIMUM_MTU: u16 = MINIMUM_MAX_DATAGRAM_SIZE
160    + UDP_HEADER_LEN
161    + const_min(IPV4_MIN_HEADER_LEN, IPV6_MIN_HEADER_LEN);
162
163macro_rules! impl_mtu {
164    ($name:ident, $default:expr) => {
165        #[derive(Clone, Copy, Debug, PartialEq)]
166        pub struct $name(NonZeroU16);
167
168        impl $name {
169            /// The minimum value required for path MTU
170            pub const MIN: Self = Self(unsafe { NonZeroU16::new_unchecked(MINIMUM_MTU) });
171
172            /// The largest size of a QUIC datagram that can be sent on a path that supports this
173            /// MTU. This does not include the size of UDP and IP headers.
174            #[inline]
175            pub fn max_datagram_size(&self, peer_socket_address: &inet::SocketAddress) -> u16 {
176                let min_ip_header_len = match peer_socket_address {
177                    inet::SocketAddress::IpV4(_) => IPV4_MIN_HEADER_LEN,
178                    inet::SocketAddress::IpV6(_) => IPV6_MIN_HEADER_LEN,
179                };
180                (u16::from(*self) - UDP_HEADER_LEN - min_ip_header_len)
181                    .max(MINIMUM_MAX_DATAGRAM_SIZE)
182            }
183        }
184
185        impl Default for $name {
186            #[inline]
187            fn default() -> Self {
188                $default
189            }
190        }
191
192        impl TryFrom<u16> for $name {
193            type Error = MtuError;
194
195            fn try_from(value: u16) -> Result<Self, Self::Error> {
196                if value < MINIMUM_MTU {
197                    return Err(MtuError);
198                }
199
200                Ok($name(value.try_into().expect(
201                    "Value must be greater than zero according to the check above",
202                )))
203            }
204        }
205
206        impl From<$name> for usize {
207            #[inline]
208            fn from(value: $name) -> Self {
209                value.0.get() as usize
210            }
211        }
212
213        impl From<$name> for u16 {
214            #[inline]
215            fn from(value: $name) -> Self {
216                value.0.get()
217            }
218        }
219    };
220}
221
222// Safety: 1500 and MINIMUM_MTU are greater than zero
223const DEFAULT_MAX_MTU: MaxMtu = MaxMtu(unsafe { NonZeroU16::new_unchecked(1500) });
224const DEFAULT_BASE_MTU: BaseMtu = BaseMtu(unsafe { NonZeroU16::new_unchecked(MINIMUM_MTU) });
225const DEFAULT_INITIAL_MTU: InitialMtu =
226    InitialMtu(unsafe { NonZeroU16::new_unchecked(MINIMUM_MTU) });
227
228impl_mtu!(MaxMtu, DEFAULT_MAX_MTU);
229impl_mtu!(InitialMtu, DEFAULT_INITIAL_MTU);
230impl_mtu!(BaseMtu, DEFAULT_BASE_MTU);
231
232#[derive(Debug, Eq, PartialEq)]
233pub struct MtuError;
234
235impl Display for MtuError {
236    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
237        write!(
238            f,
239            "MTU must have {} <= base_mtu (default: {}) <= initial_mtu (default: {}) <= max_mtu (default: {})",
240            MINIMUM_MTU, DEFAULT_BASE_MTU.0, DEFAULT_INITIAL_MTU.0, DEFAULT_MAX_MTU.0
241        )
242    }
243}
244
245#[cfg(feature = "std")]
246impl std::error::Error for MtuError {}
247
248/// Information about the path that may be used when generating MTU configuration.
249#[non_exhaustive]
250pub struct PathInfo<'a> {
251    pub remote_address: event::api::SocketAddress<'a>,
252}
253
254impl<'a> PathInfo<'a> {
255    #[inline]
256    #[doc(hidden)]
257    pub fn new(remote_address: &'a inet::SocketAddress) -> Self {
258        PathInfo {
259            remote_address: remote_address.into_event(),
260        }
261    }
262}
263
264/// MTU configuration manager.
265#[derive(Debug)]
266pub struct Manager<E: mtu::Endpoint> {
267    provider: E,
268    endpoint_mtu_config: Config,
269}
270
271impl<E: mtu::Endpoint> Manager<E> {
272    pub fn new(provider: E) -> Self {
273        Manager {
274            provider,
275            // Instantiate the Manager with default values since the endpoint is
276            // created before the IO provider (IO provider sets the actual config `set_mtu_config()`).
277            endpoint_mtu_config: Default::default(),
278        }
279    }
280
281    pub fn config(&mut self, remote_address: &inet::SocketAddress) -> Result<Config, MtuError> {
282        let info = mtu::PathInfo::new(remote_address);
283        if let Some(conn_config) = self.provider.on_path(&info, self.endpoint_mtu_config) {
284            ensure!(conn_config.is_valid(), Err(MtuError));
285            ensure!(
286                u16::from(conn_config.max_mtu) <= u16::from(self.endpoint_mtu_config.max_mtu()),
287                Err(MtuError)
288            );
289
290            Ok(conn_config)
291        } else {
292            Ok(self.endpoint_mtu_config)
293        }
294    }
295
296    pub fn set_endpoint_config(&mut self, config: Config) {
297        self.endpoint_mtu_config = config;
298    }
299
300    pub fn endpoint_config(&self) -> &Config {
301        &self.endpoint_mtu_config
302    }
303}
304
305/// Specify MTU configuration for the given path.
306pub trait Endpoint: 'static + Send {
307    /// Provide path specific MTU config.
308    ///
309    /// The MTU provider is invoked for each new path established during a
310    /// connection. Returning `None` means that the path should inherit
311    /// the endpoint configured values.
312    ///
313    /// Application must ensure that `max_mtu <= endpoint_mtu_config.max_mtu()`.
314    fn on_path(&mut self, info: &mtu::PathInfo, endpoint_mtu_config: Config)
315        -> Option<mtu::Config>;
316}
317
318/// Inherit the endpoint configured values.
319#[derive(Debug, Default)]
320pub struct Inherit {}
321
322impl Endpoint for Inherit {
323    fn on_path(
324        &mut self,
325        _info: &mtu::PathInfo,
326        _endpoint_mtu_config: Config,
327    ) -> Option<mtu::Config> {
328        None
329    }
330}
331
332/// MTU configuration.
333#[derive(Copy, Clone, Debug, Default)]
334pub struct Config {
335    initial_mtu: InitialMtu,
336    base_mtu: BaseMtu,
337    max_mtu: MaxMtu,
338}
339
340impl Endpoint for Config {
341    fn on_path(
342        &mut self,
343        _info: &mtu::PathInfo,
344        _endpoint_mtu_config: Config,
345    ) -> Option<mtu::Config> {
346        Some(*self)
347    }
348}
349
350impl Config {
351    pub const MIN: Self = Self {
352        initial_mtu: InitialMtu::MIN,
353        base_mtu: BaseMtu::MIN,
354        max_mtu: MaxMtu::MIN,
355    };
356
357    pub fn builder() -> Builder {
358        Builder::default()
359    }
360
361    /// The maximum transmission unit (MTU) to use when initiating a connection.
362    pub fn initial_mtu(&self) -> InitialMtu {
363        self.initial_mtu
364    }
365
366    /// The smallest maximum transmission unit (MTU) to use when transmitting.
367    pub fn base_mtu(&self) -> BaseMtu {
368        self.base_mtu
369    }
370
371    /// The largest maximum transmission unit (MTU) that can be sent on a path.
372    pub fn max_mtu(&self) -> MaxMtu {
373        self.max_mtu
374    }
375
376    /// Returns true if the MTU configuration is valid
377    ///
378    /// A valid MTU configuration must have base_mtu <= initial_mtu <= max_mtu
379    #[inline]
380    pub fn is_valid(&self) -> bool {
381        self.base_mtu.0 <= self.initial_mtu.0 && self.initial_mtu.0 <= self.max_mtu.0
382    }
383}
384
385#[derive(Debug, Default)]
386pub struct Builder {
387    initial_mtu: Option<InitialMtu>,
388    base_mtu: Option<BaseMtu>,
389    max_mtu: Option<MaxMtu>,
390}
391
392impl Builder {
393    /// Sets the maximum transmission unit (MTU) to use when initiating a connection (default: 1228)
394    ///
395    /// For a detailed description see the [with_initial_mtu] documentation in the IO provider.
396    ///
397    /// [with_initial_mtu]: https://docs.rs/s2n-quic/latest/s2n_quic/provider/io/tokio/struct.Builder.html#method.with_initial_mtu
398    pub fn with_initial_mtu(mut self, initial_mtu: u16) -> Result<Self, MtuError> {
399        if let Some(base_mtu) = self.base_mtu {
400            ensure!(initial_mtu >= base_mtu.0.get(), Err(MtuError));
401        }
402
403        if let Some(max_mtu) = self.max_mtu {
404            ensure!(initial_mtu <= max_mtu.0.get(), Err(MtuError));
405        }
406
407        self.initial_mtu = Some(initial_mtu.try_into()?);
408        Ok(self)
409    }
410
411    /// Sets the largest maximum transmission unit (MTU) that can be sent on a path (default: 1500)
412    ///
413    /// For a detailed description see the [with_base_mtu] documentation in the IO provider.
414    ///
415    /// [with_base_mtu]: https://docs.rs/s2n-quic/latest/s2n_quic/provider/io/tokio/struct.Builder.html#method.with_base_mtu
416    pub fn with_base_mtu(mut self, base_mtu: u16) -> Result<Self, MtuError> {
417        if let Some(initial_mtu) = self.initial_mtu {
418            ensure!(initial_mtu.0.get() >= base_mtu, Err(MtuError));
419        }
420
421        if let Some(max_mtu) = self.max_mtu {
422            ensure!(base_mtu <= max_mtu.0.get(), Err(MtuError));
423        }
424
425        self.base_mtu = Some(base_mtu.try_into()?);
426        Ok(self)
427    }
428
429    /// Sets the largest maximum transmission unit (MTU) that can be sent on a path (default: 1500)
430    ///
431    /// Application must ensure that max_mtu <= endpoint_mtu_config.max_mtu(). For a detailed
432    /// description see the [with_max_mtu] documentation in the IO provider.
433    ///
434    /// [with_max_mtu]: https://docs.rs/s2n-quic/latest/s2n_quic/provider/io/tokio/struct.Builder.html#method.with_max_mtu
435    pub fn with_max_mtu(mut self, max_mtu: u16) -> Result<Self, MtuError> {
436        if let Some(initial_mtu) = self.initial_mtu {
437            ensure!(initial_mtu.0.get() <= max_mtu, Err(MtuError));
438        }
439
440        if let Some(base_mtu) = self.base_mtu {
441            ensure!(base_mtu.0.get() <= max_mtu, Err(MtuError));
442        }
443
444        self.max_mtu = Some(max_mtu.try_into()?);
445        Ok(self)
446    }
447
448    pub fn build(self) -> Result<Config, MtuError> {
449        let base_mtu = self.base_mtu.unwrap_or_default();
450        let max_mtu = self.max_mtu.unwrap_or_default();
451        let mut initial_mtu = self.initial_mtu.unwrap_or_default();
452
453        if self.initial_mtu.is_none() {
454            // The initial_mtu was not configured, so adjust the value from the default
455            // based on the default or configured base and max MTUs
456            initial_mtu = initial_mtu
457                .0
458                .max(base_mtu.0)
459                .min(max_mtu.0)
460                .get()
461                .try_into()?
462        };
463
464        let config = Config {
465            initial_mtu,
466            max_mtu,
467            base_mtu,
468        };
469
470        ensure!(config.is_valid(), Err(MtuError));
471        Ok(config)
472    }
473}
474
475#[derive(Eq, PartialEq, Debug)]
476pub enum MtuResult {
477    NoChange,
478    MtuUpdated(u16),
479}
480
481#[derive(Clone, Debug)]
482pub struct Controller {
483    state: State,
484    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.2
485    //# The BASE_PLPMTU is a configured size expected to work for most paths.
486    //# The size is equal to or larger than the MIN_PLPMTU and smaller than
487    //# the MAX_PLPMTU.
488    base_plpmtu: u16,
489    //= https://www.rfc-editor.org/rfc/rfc8899#section-2
490    //# The Packetization Layer PMTU is an estimate of the largest size
491    //# of PL datagram that can be sent by a path, controlled by PLPMTUD
492    plpmtu: u16,
493    /// The maximum size the UDP payload can reach for any probe packet.
494    max_udp_payload: u16,
495    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.3
496    //# The PROBED_SIZE is the size of the current probe packet
497    //# as determined at the PL.  This is a tentative value for the
498    //# PLPMTU, which is awaiting confirmation by an acknowledgment.
499    probed_size: u16,
500    /// The maximum size datagram to probe for. In contrast to the max_udp_payload,
501    /// this value will decrease if probes are not acknowledged.
502    max_probe_size: u16,
503    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.3
504    //# The PROBE_COUNT is a count of the number of successive
505    //# unsuccessful probe packets that have been sent.
506    probe_count: u8,
507    /// A count of the number of packets with a size > base_plpmtu lost since
508    /// the last time a packet with size equal to the current MTU was acknowledged.
509    black_hole_counter: Counter<u8, Saturating>,
510    /// The largest acknowledged packet with size >= the plpmtu. Used when tracking
511    /// packets that have been lost for the purpose of detecting a black hole.
512    largest_acked_mtu_sized_packet: Option<PacketNumber>,
513    //= https://www.rfc-editor.org/rfc/rfc8899#section-5.1.1
514    //# The PMTU_RAISE_TIMER is configured to the period a
515    //# sender will continue to use the current PLPMTU, after which it
516    //# reenters the Search Phase.
517    pmtu_raise_timer: Timer,
518}
519
520impl Controller {
521    /// Construct a new mtu::Controller with the given `max_mtu` and `peer_socket_address`
522    ///
523    /// The UDP header length and IP header length will be subtracted from `max_mtu` to
524    /// determine the max_udp_payload used for limiting the payload length of probe packets.
525    /// max_mtu is the maximum allowed mtu, e.g. for jumbo frames this value is expected to
526    /// be over 9000.
527    #[inline]
528    pub fn new(config: Config, peer_socket_address: &inet::SocketAddress) -> Self {
529        debug_assert!(config.is_valid(), "Invalid MTU configuration {config:?}");
530
531        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.3
532        //# Endpoints SHOULD set the initial value of BASE_PLPMTU (Section 5.1 of
533        //# [DPLPMTUD]) to be consistent with QUIC's smallest allowed maximum
534        //# datagram size.
535        let base_plpmtu = config.base_mtu.max_datagram_size(peer_socket_address);
536        let max_udp_payload = config.max_mtu.max_datagram_size(peer_socket_address);
537
538        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
539        //# Datagrams containing Initial packets MAY exceed 1200 bytes if the sender
540        //# believes that the network path and peer both support the size that it chooses.
541        let plpmtu = config.initial_mtu.max_datagram_size(peer_socket_address);
542
543        let initial_probed_size = if u16::from(config.initial_mtu) > ETHERNET_MTU - PROBE_THRESHOLD
544        {
545            // An initial MTU was provided within the probe threshold of the Ethernet MTU, so we can
546            // instead try probing for an MTU larger than the Ethernet MTU
547            Self::next_probe_size(plpmtu, max_udp_payload)
548        } else {
549            // The UDP payload size for the most likely MTU is based on standard Ethernet MTU minus
550            // the minimum length IP headers (without IPv4 options or IPv6 extensions) and UPD header
551            let min_ip_header_len = match peer_socket_address {
552                inet::SocketAddress::IpV4(_) => IPV4_MIN_HEADER_LEN,
553                inet::SocketAddress::IpV6(_) => IPV6_MIN_HEADER_LEN,
554            };
555            ETHERNET_MTU - UDP_HEADER_LEN - min_ip_header_len
556        }
557        .min(max_udp_payload);
558
559        let state = if plpmtu > base_plpmtu {
560            // The initial MTU has been configured higher than the base MTU
561            State::EarlySearchRequested
562        } else if initial_probed_size - base_plpmtu < PROBE_THRESHOLD {
563            // The next probe size is within the probe threshold of the
564            // base MTU, so no probing will occur and the search is complete
565            State::SearchComplete
566        } else {
567            // Otherwise wait for regular MTU probing to be enabled
568            State::Disabled
569        };
570
571        Self {
572            state,
573            base_plpmtu,
574            plpmtu,
575            probed_size: initial_probed_size,
576            max_udp_payload,
577            max_probe_size: max_udp_payload,
578            probe_count: 0,
579            black_hole_counter: Default::default(),
580            largest_acked_mtu_sized_packet: None,
581            pmtu_raise_timer: Timer::default(),
582        }
583    }
584
585    /// Enable path MTU probing
586    #[inline]
587    pub fn enable(&mut self) {
588        // ensure we haven't already enabled the controller
589        ensure!(self.state.is_disabled() || self.state.is_early_search_requested());
590
591        // TODO: Look up current MTU in a cache. If there is a cache hit
592        //       move directly to SearchComplete and arm the PMTU raise timer.
593        //       Otherwise, start searching for a larger PMTU immediately
594        self.request_new_search(None);
595    }
596
597    /// Called when the connection timer expires
598    #[inline]
599    pub fn on_timeout(&mut self, now: Timestamp) {
600        ensure!(self.pmtu_raise_timer.poll_expiration(now).is_ready());
601        self.request_new_search(None);
602    }
603
604    //= https://www.rfc-editor.org/rfc/rfc8899#section-4.2
605    //# When
606    //# supported, this mechanism MAY also be used by DPLPMTUD to acknowledge
607    //# reception of a probe packet.
608    /// This method gets called when a packet delivery got acknowledged
609    #[inline]
610    pub fn on_packet_ack<CC: CongestionController, Pub: event::ConnectionPublisher>(
611        &mut self,
612        packet_number: PacketNumber,
613        sent_bytes: u16,
614        congestion_controller: &mut CC,
615        path_id: path::Id,
616        publisher: &mut Pub,
617    ) -> MtuResult {
618        if self.state.is_early_search_requested() && sent_bytes > self.base_plpmtu {
619            if self.is_next_probe_size_above_threshold() {
620                // Early probing has succeeded, but the max MTU is higher still so
621                // wait for regular MTU probing to be enabled to attempt higher MTUs
622                self.state = State::Disabled;
623            } else {
624                self.state = State::SearchComplete;
625            }
626
627            // Publish an `on_mtu_updated` event since the cause
628            // and possibly search_complete status have changed
629            publisher.on_mtu_updated(event::builder::MtuUpdated {
630                path_id: path_id.into_event(),
631                mtu: self.plpmtu,
632                cause: MtuUpdatedCause::InitialMtuPacketAcknowledged,
633                search_complete: self.state.is_search_complete(),
634            });
635        }
636
637        // no need to process anything in the disabled state
638        ensure!(self.state != State::Disabled, MtuResult::NoChange);
639
640        // MTU probes are only sent in application data space
641        ensure!(
642            packet_number.space().is_application_data(),
643            MtuResult::NoChange
644        );
645
646        if sent_bytes >= self.plpmtu
647            && self
648                .largest_acked_mtu_sized_packet
649                .is_none_or(|pn| packet_number > pn)
650        {
651            // Reset the black hole counter since a packet the size of the current MTU or larger
652            // has been acknowledged, indicating the path can still support the current MTU
653            self.black_hole_counter = Default::default();
654            self.largest_acked_mtu_sized_packet = Some(packet_number);
655        }
656
657        if let State::Searching(probe_packet_number, transmit_time) = self.state {
658            if packet_number == probe_packet_number {
659                self.plpmtu = self.probed_size;
660                // A new MTU has been confirmed, notify the congestion controller
661                congestion_controller.on_mtu_update(
662                    self.plpmtu,
663                    &mut congestion_controller::PathPublisher::new(publisher, path_id),
664                );
665
666                self.update_probed_size();
667
668                //= https://www.rfc-editor.org/rfc/rfc8899#section-8
669                //# To avoid excessive load, the interval between individual probe
670                //# packets MUST be at least one RTT, and the interval between rounds of
671                //# probing is determined by the PMTU_RAISE_TIMER.
672
673                // Subsequent probe packets are sent based on the round trip transmission and
674                // acknowledgement/loss of a packet, so the interval will be at least 1 RTT.
675                self.request_new_search(Some(transmit_time));
676
677                publisher.on_mtu_updated(event::builder::MtuUpdated {
678                    path_id: path_id.into_event(),
679                    mtu: self.plpmtu,
680                    cause: MtuUpdatedCause::ProbeAcknowledged,
681                    search_complete: self.state.is_search_complete(),
682                });
683
684                return MtuResult::MtuUpdated(self.plpmtu);
685            }
686        }
687
688        MtuResult::NoChange
689    }
690
691    //= https://www.rfc-editor.org/rfc/rfc8899#section-3
692    //# The PL is REQUIRED to be
693    //# robust in the case where probe packets are lost due to other
694    //# reasons (including link transmission error, congestion).
695    /// This method gets called when a packet loss is reported
696    #[inline]
697    pub fn on_packet_loss<CC: CongestionController, Pub: event::ConnectionPublisher>(
698        &mut self,
699        packet_number: PacketNumber,
700        lost_bytes: u16,
701        new_loss_burst: bool,
702        now: Timestamp,
703        congestion_controller: &mut CC,
704        path_id: path::Id,
705        publisher: &mut Pub,
706    ) -> MtuResult {
707        // MTU probes are only sent in the application data space, but since early packet
708        // spaces will use the `InitialMtu` prior to MTU probing being enabled, we need
709        // to check for potentially MTU-related packet loss if an early search has been requested
710        ensure!(
711            self.state.is_early_search_requested() || packet_number.space().is_application_data(),
712            MtuResult::NoChange
713        );
714
715        match &self.state {
716            State::Disabled => {}
717            State::EarlySearchRequested => {
718                // MTU probing hasn't been enabled yet, but since the initial MTU was configured
719                // higher than the base PLPMTU and this setting resulted in a lost packet
720                // we drop back down to the base PLPMTU.
721                self.plpmtu = self.base_plpmtu;
722
723                congestion_controller.on_mtu_update(
724                    self.plpmtu,
725                    &mut congestion_controller::PathPublisher::new(publisher, path_id),
726                );
727
728                if self.is_next_probe_size_above_threshold() {
729                    // Resume regular probing when the MTU controller is enabled
730                    self.state = State::Disabled;
731                } else {
732                    // The next probe is within the threshold, so move directly
733                    // to the SearchComplete state
734                    self.state = State::SearchComplete;
735                }
736
737                publisher.on_mtu_updated(event::builder::MtuUpdated {
738                    path_id: path_id.into_event(),
739                    mtu: self.plpmtu,
740                    cause: MtuUpdatedCause::InitialMtuPacketLost,
741                    search_complete: self.state.is_search_complete(),
742                });
743
744                return MtuResult::MtuUpdated(self.plpmtu);
745            }
746            State::Searching(probe_pn, _) if *probe_pn == packet_number => {
747                // The MTU probe was lost
748                if self.probe_count == MAX_PROBES {
749                    // We've sent MAX_PROBES without acknowledgement, so
750                    // attempt a smaller probe size
751                    self.max_probe_size = self.probed_size;
752                    self.update_probed_size();
753                    self.request_new_search(None);
754
755                    if self.is_search_completed() {
756                        // Emit an on_mtu_updated event as the search has now completed
757                        publisher.on_mtu_updated(event::builder::MtuUpdated {
758                            path_id: path_id.into_event(),
759                            mtu: self.plpmtu,
760                            cause: MtuUpdatedCause::LargerProbesLost,
761                            search_complete: true,
762                        })
763                    }
764                } else {
765                    // Try the same probe size again
766                    self.state = State::SearchRequested
767                }
768            }
769            State::Searching(_, _) | State::SearchComplete | State::SearchRequested => {
770                if (self.base_plpmtu + 1..=self.plpmtu).contains(&lost_bytes)
771                    && self
772                        .largest_acked_mtu_sized_packet
773                        .is_none_or(|pn| packet_number > pn)
774                    && new_loss_burst
775                {
776                    // A non-probe packet larger than the BASE_PLPMTU that was sent after the last
777                    // acknowledged MTU-sized packet has been lost
778                    self.black_hole_counter += 1;
779                }
780
781                if self.black_hole_counter > BLACK_HOLE_THRESHOLD {
782                    return self.on_black_hole_detected(
783                        now,
784                        congestion_controller,
785                        path_id,
786                        publisher,
787                    );
788                }
789            }
790        }
791
792        MtuResult::NoChange
793    }
794
795    /// Gets the currently validated maximum QUIC datagram size
796    ///
797    /// This does not include the size of UDP and IP headers.
798    #[inline]
799    pub fn max_datagram_size(&self) -> usize {
800        self.plpmtu as usize
801    }
802
803    /// Gets the max datagram size currently being probed for
804    #[inline]
805    pub fn probed_sized(&self) -> usize {
806        self.probed_size as usize
807    }
808
809    /// Returns true if probing for the MTU has completed
810    pub fn is_search_completed(&self) -> bool {
811        self.state.is_search_complete()
812    }
813
814    /// Sets `probed_size` to the next MTU size to probe for based on a binary search
815    #[inline]
816    fn update_probed_size(&mut self) {
817        //= https://www.rfc-editor.org/rfc/rfc8899#section-5.3.2
818        //# Implementations SHOULD select the set of probe packet sizes to
819        //# maximize the gain in PLPMTU from each search step.
820        self.probed_size = Self::next_probe_size(self.plpmtu, self.max_probe_size);
821    }
822
823    /// Calculates the next probe size as halfway from the current to the max size
824    #[inline]
825    fn next_probe_size(current: u16, max: u16) -> u16 {
826        current + ((max - current) / 2)
827    }
828
829    #[inline]
830    fn is_next_probe_size_above_threshold(&self) -> bool {
831        self.probed_size - self.plpmtu >= PROBE_THRESHOLD
832    }
833
834    /// Requests a new search to be initiated
835    ///
836    /// If `last_probe_time` is supplied, the PMTU Raise Timer will be armed as
837    /// necessary if the probed_size is already within the PROBE_THRESHOLD
838    /// of the current PLPMTU
839    #[inline]
840    fn request_new_search(&mut self, last_probe_time: Option<Timestamp>) {
841        if self.is_next_probe_size_above_threshold() {
842            self.probe_count = 0;
843            self.state = State::SearchRequested;
844        } else {
845            // The next probe size is within the threshold of the current MTU
846            // so its not worth additional probing.
847            self.state = State::SearchComplete;
848
849            if let Some(last_probe_time) = last_probe_time {
850                self.arm_pmtu_raise_timer(last_probe_time + PMTU_RAISE_TIMER_DURATION);
851            }
852        }
853    }
854
855    /// Called when an excessive number of packets larger than the BASE_PLPMTU have been lost
856    #[inline]
857    fn on_black_hole_detected<CC: CongestionController, Pub: event::ConnectionPublisher>(
858        &mut self,
859        now: Timestamp,
860        congestion_controller: &mut CC,
861        path_id: path::Id,
862        publisher: &mut Pub,
863    ) -> MtuResult {
864        self.black_hole_counter = Default::default();
865        self.largest_acked_mtu_sized_packet = None;
866        // Reset the plpmtu back to the base_plpmtu and notify the congestion controller
867        self.plpmtu = self.base_plpmtu;
868        congestion_controller.on_mtu_update(
869            self.plpmtu,
870            &mut congestion_controller::PathPublisher::new(publisher, path_id),
871        );
872        // Cancel any current probes
873        self.state = State::SearchComplete;
874        // Arm the PMTU raise timer to try a larger MTU again after a cooling off period
875        self.arm_pmtu_raise_timer(now + BLACK_HOLE_COOL_OFF_DURATION);
876
877        publisher.on_mtu_updated(event::builder::MtuUpdated {
878            path_id: path_id.into_event(),
879            mtu: self.plpmtu,
880            cause: MtuUpdatedCause::Blackhole,
881            search_complete: self.state.is_search_complete(),
882        });
883
884        MtuResult::MtuUpdated(self.plpmtu)
885    }
886
887    /// Arm the PMTU Raise Timer if there is still room to increase the
888    /// MTU before hitting the max plpmtu
889    #[inline]
890    fn arm_pmtu_raise_timer(&mut self, timestamp: Timestamp) {
891        // Reset the max_probe_size to the max_udp_payload to allow for larger probe sizes
892        self.max_probe_size = self.max_udp_payload;
893        self.update_probed_size();
894
895        if self.is_next_probe_size_above_threshold() {
896            // There is still some room to try a larger MTU again,
897            // so arm the pmtu raise timer
898            self.pmtu_raise_timer.set(timestamp);
899        }
900    }
901}
902
903impl timer::Provider for Controller {
904    #[inline]
905    fn timers<Q: timer::Query>(&self, query: &mut Q) -> timer::Result {
906        self.pmtu_raise_timer.timers(query)?;
907
908        Ok(())
909    }
910}
911
912impl transmission::Provider for Controller {
913    /// Queries the component for any outgoing frames that need to get sent
914    ///
915    /// This method assumes that no other data (other than the packet header) has been written
916    /// to the supplied `WriteContext`. This necessitates the caller ensuring the probe packet
917    /// written by this method to be in its own connection transmission.
918    #[inline]
919    fn on_transmit<W: transmission::Writer>(&mut self, context: &mut W) {
920        //= https://www.rfc-editor.org/rfc/rfc8899#section-5.2
921        //# When used with an acknowledged PL (e.g., SCTP), DPLPMTUD SHOULD NOT continue to
922        //# generate PLPMTU probes in this state.
923        ensure!(self.state == State::SearchRequested);
924
925        ensure!(context.transmission_mode().is_mtu_probing());
926
927        // Each packet contains overhead in the form of a packet header and an authentication tag.
928        // This overhead contributes to the overall size of the packet, so the payload we write
929        // to the packet will account for this overhead to reach the target probed size.
930        let probe_payload_size =
931            self.probed_size as usize - context.header_len() - context.tag_len();
932
933        if context.remaining_capacity() < probe_payload_size {
934            // There isn't enough capacity in the buffer to write the datagram we
935            // want to probe, so we've reached the maximum pmtu and the search is complete.
936            self.state = State::SearchComplete;
937            return;
938        }
939
940        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.4
941        //# Endpoints could limit the content of PMTU probes to PING and PADDING
942        //# frames, since packets that are larger than the current maximum
943        //# datagram size are more likely to be dropped by the network.
944
945        //= https://www.rfc-editor.org/rfc/rfc8899#section-3
946        //# Probe loss recovery: It is RECOMMENDED to use probe packets that
947        //# do not carry any user data that would require retransmission if
948        //# lost.
949
950        //= https://www.rfc-editor.org/rfc/rfc8899#section-4.1
951        //# DPLPMTUD MAY choose to use only one of these methods to simplify the
952        //# implementation.
953
954        context.write_frame(&frame::Ping);
955        let padding_size = probe_payload_size - frame::Ping.encoding_size();
956        if let Some(packet_number) = context.write_frame(&frame::Padding {
957            length: padding_size,
958        }) {
959            self.probe_count += 1;
960            self.state = State::Searching(packet_number, context.current_time());
961        }
962    }
963}
964
965impl transmission::interest::Provider for Controller {
966    #[inline]
967    fn transmission_interest<Q: transmission::interest::Query>(
968        &self,
969        query: &mut Q,
970    ) -> transmission::interest::Result {
971        match self.state {
972            State::SearchRequested => query.on_new_data(),
973            _ => Ok(()),
974        }
975    }
976}