Skip to main content

netstack3_ip/
path_mtu.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Module for IP level paths' maximum transmission unit (PMTU) size
6//! cache support.
7
8use core::num::NonZeroUsize;
9use core::time::Duration;
10
11use log::trace;
12use lru::LruCache;
13use net_types::ip::{GenericOverIp, Ip, IpAddress, IpVersionMarker, Mtu};
14use netstack3_base::{
15    CoreTimerContext, HandleableTimer, Instant, InstantBindingsTypes, TimerBindingsTypes,
16    TimerContext,
17};
18
19/// Time between PMTU maintenance operations.
20///
21/// Maintenance operations are things like resetting cached PMTU data to force
22/// restart PMTU discovery to detect increases in a PMTU.
23///
24/// 1 hour.
25// TODO(ghanan): Make this value configurable by runtime options.
26const MAINTENANCE_PERIOD: Duration = Duration::from_secs(3600);
27
28/// Time for a PMTU value to be considered stale.
29///
30/// 3 hours.
31// TODO(ghanan): Make this value configurable by runtime options.
32const PMTU_STALE_TIMEOUT: Duration = Duration::from_secs(10800);
33
34const MAX_ENTRIES: NonZeroUsize = NonZeroUsize::new(256).unwrap();
35
36/// The timer ID for the path MTU cache.
37#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, GenericOverIp)]
38#[generic_over_ip(I, Ip)]
39pub struct PmtuTimerId<I: Ip>(IpVersionMarker<I>);
40
41/// The core context for the path MTU cache.
42pub trait PmtuContext<I: Ip, BT: PmtuBindingsTypes> {
43    /// Calls a function with a mutable reference to the PMTU cache.
44    fn with_state_mut<O, F: FnOnce(&mut PmtuCache<I, BT>) -> O>(&mut self, cb: F) -> O;
45}
46
47/// The bindings types for path MTU discovery.
48pub trait PmtuBindingsTypes: TimerBindingsTypes + InstantBindingsTypes {}
49impl<BT> PmtuBindingsTypes for BT where BT: TimerBindingsTypes + InstantBindingsTypes {}
50
51/// The bindings execution context for path MTU discovery.
52trait PmtuBindingsContext: PmtuBindingsTypes + TimerContext {}
53impl<BC> PmtuBindingsContext for BC where BC: PmtuBindingsTypes + TimerContext {}
54
55/// A handler for incoming PMTU events.
56///
57/// `PmtuHandler` is intended to serve as the interface between ICMP the IP
58/// layer, which holds the PMTU cache. In production, method calls are delegated
59/// to a real [`PmtuCache`], while in testing, method calls may be delegated to
60/// a fake implementation.
61pub(crate) trait PmtuHandler<I: Ip, BC> {
62    /// Updates the PMTU between `src_ip` and `dst_ip` if `new_mtu` is less than
63    /// the current PMTU and does not violate the minimum MTU size requirements
64    /// for an IP.
65    ///
66    /// Returns the current PMTU after the update, whether or not it was updated.
67    /// `None` indicates that the PMTU was not updated and there was no existing
68    /// value in the cache.
69    //
70    // TODO(https://fxbug.dev/383355972): consider enforcing an IP version-specific
71    // minimum MTU with a type-safe MTU type.
72    fn update_pmtu_if_less(
73        &mut self,
74        bindings_ctx: &mut BC,
75        src_ip: I::Addr,
76        dst_ip: I::Addr,
77        new_mtu: Mtu,
78    ) -> Option<Mtu>;
79
80    /// Updates the PMTU between `src_ip` and `dst_ip` to the next lower
81    /// estimate from `from`.
82    ///
83    /// Returns the current PMTU after the update, whether or not it was updated.
84    /// `None` indicates that the PMTU was not updated and there was no existing
85    /// value in the cache.
86    //
87    // TODO(https://fxbug.dev/383355972): consider enforcing an IP version-specific
88    // minimum MTU with a type-safe MTU type.
89    fn update_pmtu_next_lower(
90        &mut self,
91        bindings_ctx: &mut BC,
92        src_ip: I::Addr,
93        dst_ip: I::Addr,
94        from: Mtu,
95    ) -> Option<Mtu>;
96}
97
98fn maybe_schedule_timer<BC: PmtuBindingsContext>(
99    bindings_ctx: &mut BC,
100    timer: &mut BC::Timer,
101    cache_is_empty: bool,
102) {
103    // Only attempt to create the next maintenance task if we still have
104    // PMTU entries in the cache. If we don't, it would be a waste to
105    // schedule the timer. We will let the next creation of a PMTU entry
106    // create the timer.
107    if cache_is_empty {
108        return;
109    }
110
111    match bindings_ctx.scheduled_instant(timer) {
112        Some(scheduled_at) => {
113            let _: BC::Instant = scheduled_at;
114            // Timer already set, nothing to do.
115        }
116        None => {
117            // We only enter this match arm if a timer was not already set.
118            assert_eq!(bindings_ctx.schedule_timer(MAINTENANCE_PERIOD, timer), None)
119        }
120    }
121}
122
123/// Returns the current MTU after an update to the cache.
124fn handle_update_result<BC: PmtuBindingsContext>(
125    bindings_ctx: &mut BC,
126    timer: &mut BC::Timer,
127    result: UpdateResult,
128    cache_is_empty: bool,
129) -> Option<Mtu> {
130    match result {
131        UpdateResult::Updated(new_mtu) => {
132            maybe_schedule_timer(bindings_ctx, timer, cache_is_empty);
133            Some(new_mtu)
134        }
135        // TODO(https://fxbug.dev/42174290): should we handle failure to update PMTU
136        // differently?
137        UpdateResult::NotUpdated(mtu) => mtu,
138    }
139}
140
141impl<I: Ip, BC: PmtuBindingsContext, CC: PmtuContext<I, BC>> PmtuHandler<I, BC> for CC {
142    fn update_pmtu_if_less(
143        &mut self,
144        bindings_ctx: &mut BC,
145        src_ip: I::Addr,
146        dst_ip: I::Addr,
147        new_mtu: Mtu,
148    ) -> Option<Mtu> {
149        self.with_state_mut(|cache| {
150            let now = bindings_ctx.now();
151            let res = cache.update_pmtu_if_less(src_ip, dst_ip, new_mtu, now);
152            let is_empty = cache.is_empty();
153            handle_update_result(bindings_ctx, &mut cache.timer, res, is_empty)
154        })
155    }
156
157    fn update_pmtu_next_lower(
158        &mut self,
159        bindings_ctx: &mut BC,
160        src_ip: I::Addr,
161        dst_ip: I::Addr,
162        from: Mtu,
163    ) -> Option<Mtu> {
164        self.with_state_mut(|cache| {
165            let now = bindings_ctx.now();
166            let res = cache.update_pmtu_next_lower(src_ip, dst_ip, from, now);
167            let is_empty = cache.is_empty();
168            handle_update_result(bindings_ctx, &mut cache.timer, res, is_empty)
169        })
170    }
171}
172
173impl<I: Ip, BC: PmtuBindingsContext, CC: PmtuContext<I, BC>> HandleableTimer<CC, BC>
174    for PmtuTimerId<I>
175{
176    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
177        let Self(IpVersionMarker { .. }) = self;
178        core_ctx.with_state_mut(|cache| {
179            let now = bindings_ctx.now();
180            cache.handle_timer(now);
181            let is_empty = cache.is_empty();
182            maybe_schedule_timer(bindings_ctx, &mut cache.timer, is_empty);
183        })
184    }
185}
186
187/// The key used to identify a path.
188///
189/// This is a tuple of (src_ip, dst_ip) as a path is only identified by the
190/// source and destination addresses.
191// TODO(ghanan): Should device play a part in the key-ing of a path?
192#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
193pub(crate) struct PmtuCacheKey<A: IpAddress>(A, A);
194
195impl<A: IpAddress> PmtuCacheKey<A> {
196    fn new(src_ip: A, dst_ip: A) -> Self {
197        Self(src_ip, dst_ip)
198    }
199}
200
201/// IP layer PMTU cache data.
202#[derive(Debug, PartialEq)]
203pub(crate) struct PmtuCacheData<I> {
204    pmtu: Mtu,
205    last_updated: I,
206}
207
208impl<I: Instant> PmtuCacheData<I> {
209    /// Construct a new `PmtuCacheData`.
210    ///
211    /// `last_updated` will be set to `now`.
212    fn new(pmtu: Mtu, now: I) -> Self {
213        Self { pmtu, last_updated: now }
214    }
215}
216
217/// A path MTU cache.
218pub struct PmtuCache<I: Ip, BT: PmtuBindingsTypes> {
219    cache: LruCache<PmtuCacheKey<I::Addr>, PmtuCacheData<BT::Instant>>,
220    timer: BT::Timer,
221}
222
223impl<I: Ip, BC: PmtuBindingsTypes + TimerContext> PmtuCache<I, BC> {
224    pub(crate) fn new<CC: CoreTimerContext<PmtuTimerId<I>, BC>>(bindings_ctx: &mut BC) -> Self {
225        Self {
226            cache: LruCache::new(MAX_ENTRIES),
227            timer: CC::new_timer(bindings_ctx, PmtuTimerId::default()),
228        }
229    }
230}
231
232enum UpdateResult {
233    Updated(Mtu),
234    NotUpdated(Option<Mtu>),
235}
236
237impl<I: Ip, BT: PmtuBindingsTypes> PmtuCache<I, BT> {
238    /// Gets the PMTU between `src_ip` and `dst_ip`.
239    pub fn get_pmtu(&mut self, src_ip: I::Addr, dst_ip: I::Addr) -> Option<Mtu> {
240        self.cache.get_mut(&PmtuCacheKey::new(src_ip, dst_ip)).map(|x| x.pmtu)
241    }
242
243    /// Updates the PMTU between `src_ip` and `dst_ip` if `new_mtu` is less than the
244    /// current PMTU and does not violate the minimum MTU size requirements for an
245    /// IP.
246    ///
247    /// Returns the PMTU after updating the cache. Note that this could be `None` if
248    /// the cache was previously empty and the new PMTU was invalid, and could also
249    /// be a value that was already in the cache.
250    fn update_pmtu_if_less(
251        &mut self,
252        src_ip: I::Addr,
253        dst_ip: I::Addr,
254        new_mtu: Mtu,
255        now: BT::Instant,
256    ) -> UpdateResult {
257        match self.get_pmtu(src_ip, dst_ip) {
258            // No PMTU exists so update.
259            None => self.update_pmtu(src_ip, dst_ip, new_mtu, now),
260            // A PMTU exists but it is greater than `new_mtu` so update.
261            Some(prev_mtu) if new_mtu < prev_mtu => self.update_pmtu(src_ip, dst_ip, new_mtu, now),
262            // A PMTU exists but it is less than or equal to `new_mtu` so no need to
263            // update.
264            Some(prev_mtu) => {
265                trace!(
266                    "update_pmtu_if_less: Not updating the PMTU between src {src_ip} and dst
267                    {dst_ip} to {new_mtu:?}; is {prev_mtu:?}"
268                );
269                UpdateResult::NotUpdated(Some(prev_mtu))
270            }
271        }
272    }
273
274    /// Updates the PMTU between `src_ip` and `dst_ip` to the next lower
275    /// estimate from `from`.
276    ///
277    /// Returns `Ok(x)` on successful update (either PMTU is already lower than
278    /// `from`, in which case `x` is `None`, or a lower PMTU value exists that does
279    /// not violate IP specific minimum MTU requirements and it is less than the
280    /// current PMTU estimate, in which case `x` is `Some(a)` where `a` is the new
281    /// lower value.
282    ///
283    /// Returns `Err(x)` if no suitable lower PMTU value exists, where `x` is the
284    /// existing PMTU in the cache.
285    fn update_pmtu_next_lower(
286        &mut self,
287        src_ip: I::Addr,
288        dst_ip: I::Addr,
289        from: Mtu,
290        now: BT::Instant,
291    ) -> UpdateResult {
292        if let Some(next_pmtu) = next_lower_pmtu_plateau(from) {
293            trace!(
294                "update_pmtu_next_lower: Attempting to update PMTU between src {src_ip} and dst \
295                {dst_ip} to {next_pmtu:?}"
296            );
297
298            self.update_pmtu_if_less(src_ip, dst_ip, next_pmtu, now)
299        } else {
300            // TODO(https://fxbug.dev/383355972): Should we make sure the current PMTU value
301            // is set to the IP specific minimum MTU value?
302            trace!(
303                "update_pmtu_next_lower: Not updating PMTU between src {src_ip} and dst {dst_ip} \
304                as there is no lower PMTU value from {from:?}"
305            );
306            UpdateResult::NotUpdated(self.get_pmtu(src_ip, dst_ip))
307        }
308    }
309
310    /// Updates the PMTU between `src_ip` and `dst_ip` if `new_mtu` does not violate
311    /// IP-specific minimum MTU requirements.
312    ///
313    /// Returns the PMTU after updating the cache. Note that this could be `None` if
314    /// the cache was previously empty and the new PMTU was invalid, and could also
315    /// be a value that was already in the cache.
316    fn update_pmtu(
317        &mut self,
318        src_ip: I::Addr,
319        dst_ip: I::Addr,
320        new_mtu: Mtu,
321        now: BT::Instant,
322    ) -> UpdateResult {
323        // New MTU must not be smaller than the minimum MTU for an IP.
324        //
325        // TODO(https://fxbug.dev/383355972): consider enforcing this invariant with a
326        // type-safe MTU type that forces the caller to provide a valid MTU for the
327        // given IP version.
328        if new_mtu < I::MINIMUM_LINK_MTU {
329            return UpdateResult::NotUpdated(self.get_pmtu(src_ip, dst_ip));
330        }
331        let _previous =
332            self.cache.put(PmtuCacheKey::new(src_ip, dst_ip), PmtuCacheData::new(new_mtu, now));
333
334        log::debug!("updated PMTU for path {src_ip} -> {dst_ip} to {new_mtu:?}");
335
336        UpdateResult::Updated(new_mtu)
337    }
338
339    fn handle_timer(&mut self, now: BT::Instant) {
340        // Make sure we expected this timer to fire.
341        assert!(!self.cache.is_empty());
342
343        // Remove all stale PMTU data to force restart the PMTU discovery
344        // process. This will be ok because the next time we try to send a
345        // packet to some node, we will update the PMTU with the first known
346        // potential PMTU (the first link's (connected to the node attempting
347        // PMTU discovery)) PMTU.
348        //
349        // TODO(ghanan): Add per-path options as per RFC 1981 section 5.3.
350        //               Specifically, some links/paths may not need to have
351        //               PMTU rediscovered as the PMTU will never change.
352        //
353        // TODO(ghanan): Consider not simply deleting all stale PMTU data as
354        //               this may cause packets to be dropped every time the
355        //               data seems to get stale when really it is still
356        //               valid. Considering the use case, PMTU value changes
357        //               may be infrequent so it may be enough to just use a
358        //               long stale timer.
359        //
360        // TODO(https://fxbug.dev/404629697): once we actually use the PMTU
361        // cache to inform IP fragmentation, consider discarding least-recently-
362        // used entries rather than, or in addition to, entries that have been
363        // in the cache for a long time.
364        //
365        self.cache
366            .retain(|_k, v| now.saturating_duration_since(v.last_updated) < PMTU_STALE_TIMEOUT);
367    }
368
369    fn is_empty(&self) -> bool {
370        self.cache.is_empty()
371    }
372}
373
374/// Get next lower PMTU plateau value, if one exists.
375fn next_lower_pmtu_plateau(start_mtu: Mtu) -> Option<Mtu> {
376    /// Common MTU values taken from [RFC 1191 section 7.1].
377    ///
378    /// This list includes lower bounds of groups of common MTU values that are
379    /// relatively close to each other, sorted in descending order.
380    ///
381    /// Note, the RFC does not actually include the value 1280 in the list of
382    /// plateau values, but we include it here because it is the minimum IPv6
383    /// MTU value and is not expected to be an uncommon value for MTUs.
384    ///
385    /// This list MUST be sorted in descending order; methods such as
386    /// `next_lower_pmtu_plateau` assume `PMTU_PLATEAUS` has this property.
387    ///
388    /// We use this list when estimating PMTU values when doing PMTU discovery
389    /// with IPv4 on paths with nodes that do not implement RFC 1191. This list
390    /// is useful as in practice, relatively few MTU values are in use.
391    ///
392    /// [RFC 1191 section 7.1]: https://tools.ietf.org/html/rfc1191#section-7.1
393    const PMTU_PLATEAUS: [Mtu; 12] = [
394        Mtu::new(65535),
395        Mtu::new(32000),
396        Mtu::new(17914),
397        Mtu::new(8166),
398        Mtu::new(4352),
399        Mtu::new(2002),
400        Mtu::new(1492),
401        Mtu::new(1280),
402        Mtu::new(1006),
403        Mtu::new(508),
404        Mtu::new(296),
405        Mtu::new(68),
406    ];
407
408    for i in 0..PMTU_PLATEAUS.len() {
409        let pmtu = PMTU_PLATEAUS[i];
410
411        if pmtu < start_mtu {
412            // Current PMTU is less than `start_mtu` and we know `PMTU_PLATEAUS`
413            // is sorted so this is the next best PMTU estimate.
414            return Some(pmtu);
415        }
416    }
417
418    None
419}
420
421#[cfg(test)]
422#[macro_use]
423pub(crate) mod testutil {
424    /// Implement the `PmtuHandler<$ip_version>` trait by just panicking.
425    macro_rules! impl_pmtu_handler {
426        ($ty:ty, $ctx:ty, $ip_version:ident) => {
427            impl PmtuHandler<net_types::ip::$ip_version, $ctx> for $ty {
428                fn update_pmtu_if_less(
429                    &mut self,
430                    _ctx: &mut $ctx,
431                    _src_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
432                    _dst_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
433                    _new_mtu: Mtu,
434                ) -> Option<Mtu> {
435                    unimplemented!()
436                }
437
438                fn update_pmtu_next_lower(
439                    &mut self,
440                    _ctx: &mut $ctx,
441                    _src_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
442                    _dst_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
443                    _from: Mtu,
444                ) -> Option<Mtu> {
445                    unimplemented!()
446                }
447            }
448        };
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455
456    use ip_test_macro::ip_test;
457    use net_types::{SpecifiedAddr, Witness};
458    use netstack3_base::testutil::{
459        FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeTimerCtxExt, TestIpExt, assert_empty,
460    };
461    use netstack3_base::{CtxPair, InstantContext, IntoCoreTimerCtx};
462    use test_case::test_case;
463
464    struct FakePmtuContext<I: Ip> {
465        cache: PmtuCache<I, FakeBindingsCtxImpl<I>>,
466    }
467
468    type FakeCtxImpl<I> = CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>>;
469    type FakeCoreCtxImpl<I> = FakeCoreCtx<FakePmtuContext<I>, (), ()>;
470    type FakeBindingsCtxImpl<I> = FakeBindingsCtx<PmtuTimerId<I>, (), (), ()>;
471
472    impl<I: Ip> PmtuContext<I, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
473        fn with_state_mut<O, F: FnOnce(&mut PmtuCache<I, FakeBindingsCtxImpl<I>>) -> O>(
474            &mut self,
475            cb: F,
476        ) -> O {
477            cb(&mut self.state.cache)
478        }
479    }
480
481    fn new_context<I: Ip>() -> FakeCtxImpl<I> {
482        FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
483            FakeCoreCtxImpl::with_state(FakePmtuContext {
484                cache: PmtuCache::new::<IntoCoreTimerCtx>(bindings_ctx),
485            })
486        })
487    }
488
489    /// Get an IPv4 or IPv6 address within the same subnet as that of
490    /// `TEST_ADDRS_*`, but with the last octet set to `3`.
491    fn get_other_ip_address<I: TestIpExt>() -> SpecifiedAddr<I::Addr> {
492        I::get_other_ip_address(3)
493    }
494
495    impl<I: Ip, BT: PmtuBindingsTypes> PmtuCache<I, BT> {
496        /// Gets the last updated [`Instant`] when the PMTU between `src_ip` and
497        /// `dst_ip` was updated.
498        ///
499        /// [`Instant`]: Instant
500        fn get_last_updated(&mut self, src_ip: I::Addr, dst_ip: I::Addr) -> Option<BT::Instant> {
501            self.cache.get_mut(&PmtuCacheKey::new(src_ip, dst_ip)).map(|x| x.last_updated.clone())
502        }
503    }
504
505    #[test_case(Mtu::new(65536) => Some(Mtu::new(65535)))]
506    #[test_case(Mtu::new(65535) => Some(Mtu::new(32000)))]
507    #[test_case(Mtu::new(65534) => Some(Mtu::new(32000)))]
508    #[test_case(Mtu::new(32001) => Some(Mtu::new(32000)))]
509    #[test_case(Mtu::new(32000) => Some(Mtu::new(17914)))]
510    #[test_case(Mtu::new(31999) => Some(Mtu::new(17914)))]
511    #[test_case(Mtu::new(1281)  => Some(Mtu::new(1280)))]
512    #[test_case(Mtu::new(1280)  => Some(Mtu::new(1006)))]
513    #[test_case(Mtu::new(69)    => Some(Mtu::new(68)))]
514    #[test_case(Mtu::new(68)    => None)]
515    #[test_case(Mtu::new(67)    => None)]
516    #[test_case(Mtu::new(0)     => None)]
517    fn test_next_lower_pmtu_plateau(start: Mtu) -> Option<Mtu> {
518        next_lower_pmtu_plateau(start)
519    }
520
521    fn get_pmtu<I: Ip>(
522        core_ctx: &mut FakeCoreCtxImpl<I>,
523        src_ip: I::Addr,
524        dst_ip: I::Addr,
525    ) -> Option<Mtu> {
526        core_ctx.state.cache.get_pmtu(src_ip, dst_ip)
527    }
528
529    fn get_last_updated<I: Ip>(
530        core_ctx: &mut FakeCoreCtxImpl<I>,
531        src_ip: I::Addr,
532        dst_ip: I::Addr,
533    ) -> Option<FakeInstant> {
534        core_ctx.state.cache.get_last_updated(src_ip, dst_ip)
535    }
536
537    #[ip_test(I)]
538    fn test_ip_path_mtu_cache_ctx<I: TestIpExt>() {
539        let fake_config = I::TEST_ADDRS;
540        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
541
542        // Nothing in the cache yet
543        assert_eq!(
544            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
545            None
546        );
547        assert_eq!(
548            get_last_updated(
549                &mut core_ctx,
550                fake_config.local_ip.get(),
551                fake_config.remote_ip.get()
552            ),
553            None
554        );
555
556        let new_mtu1 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 50);
557        let start_time = bindings_ctx.now();
558        let duration = Duration::from_secs(1);
559
560        // Advance time to 1s.
561        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
562
563        // Update pmtu from local to remote. PMTU should be updated to
564        // `new_mtu1` and last updated instant should be updated to the start of
565        // the test + 1s.
566        assert_eq!(
567            PmtuHandler::update_pmtu_if_less(
568                &mut core_ctx,
569                &mut bindings_ctx,
570                fake_config.local_ip.get(),
571                fake_config.remote_ip.get(),
572                new_mtu1,
573            ),
574            Some(new_mtu1)
575        );
576
577        // Advance time to 2s.
578        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
579
580        // Make sure the update worked. PMTU should be updated to `new_mtu1` and
581        // last updated instant should be updated to the start of the test + 1s
582        // (when the update occurred.
583        assert_eq!(
584            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
585                .unwrap(),
586            new_mtu1
587        );
588        assert_eq!(
589            get_last_updated(
590                &mut core_ctx,
591                fake_config.local_ip.get(),
592                fake_config.remote_ip.get()
593            )
594            .unwrap(),
595            start_time + duration
596        );
597
598        let new_mtu2 = Mtu::new(u32::from(new_mtu1) - 1);
599
600        // Advance time to 3s.
601        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
602
603        // Updating again should return the last pmtu PMTU should be updated to
604        // `new_mtu2` and last updated instant should be updated to the start of
605        // the test + 3s.
606        assert_eq!(
607            PmtuHandler::update_pmtu_if_less(
608                &mut core_ctx,
609                &mut bindings_ctx,
610                fake_config.local_ip.get(),
611                fake_config.remote_ip.get(),
612                new_mtu2,
613            ),
614            Some(new_mtu2)
615        );
616
617        // Advance time to 4s.
618        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
619
620        // Make sure the update worked. PMTU should be updated to `new_mtu2` and
621        // last updated instant should be updated to the start of the test + 3s
622        // (when the update occurred).
623        assert_eq!(
624            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
625                .unwrap(),
626            new_mtu2
627        );
628        assert_eq!(
629            get_last_updated(
630                &mut core_ctx,
631                fake_config.local_ip.get(),
632                fake_config.remote_ip.get()
633            )
634            .unwrap(),
635            start_time + (duration * 3)
636        );
637
638        let new_mtu3 = Mtu::new(u32::from(new_mtu2) - 1);
639
640        // Advance time to 5s.
641        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
642
643        // Make sure update only if new PMTU is less than current (it is). PMTU
644        // should be updated to `new_mtu3` and last updated instant should be
645        // updated to the start of the test + 5s.
646        assert_eq!(
647            PmtuHandler::update_pmtu_if_less(
648                &mut core_ctx,
649                &mut bindings_ctx,
650                fake_config.local_ip.get(),
651                fake_config.remote_ip.get(),
652                new_mtu3,
653            ),
654            Some(new_mtu3)
655        );
656
657        // Advance time to 6s.
658        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
659
660        // Make sure the update worked. PMTU should be updated to `new_mtu3` and
661        // last updated instant should be updated to the start of the test + 5s
662        // (when the update occurred).
663        assert_eq!(
664            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
665                .unwrap(),
666            new_mtu3
667        );
668        let last_updated = start_time + (duration * 5);
669        assert_eq!(
670            get_last_updated(
671                &mut core_ctx,
672                fake_config.local_ip.get(),
673                fake_config.remote_ip.get()
674            )
675            .unwrap(),
676            last_updated
677        );
678
679        let new_mtu4 = Mtu::new(u32::from(new_mtu3) + 50);
680
681        // Advance time to 7s.
682        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
683
684        // Make sure update only if new PMTU is less than current (it isn't)
685        assert_eq!(
686            PmtuHandler::update_pmtu_if_less(
687                &mut core_ctx,
688                &mut bindings_ctx,
689                fake_config.local_ip.get(),
690                fake_config.remote_ip.get(),
691                new_mtu4,
692            ),
693            Some(new_mtu3)
694        );
695
696        // Advance time to 8s.
697        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
698
699        // Make sure the update didn't work. PMTU and last updated should not
700        // have changed.
701        assert_eq!(
702            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
703                .unwrap(),
704            new_mtu3
705        );
706        assert_eq!(
707            get_last_updated(
708                &mut core_ctx,
709                fake_config.local_ip.get(),
710                fake_config.remote_ip.get()
711            )
712            .unwrap(),
713            last_updated
714        );
715
716        let low_mtu = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) - 1);
717
718        // Advance time to 9s.
719        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
720
721        // Updating with MTU value less than the minimum MTU should fail.
722        assert_eq!(
723            PmtuHandler::update_pmtu_if_less(
724                &mut core_ctx,
725                &mut bindings_ctx,
726                fake_config.local_ip.get(),
727                fake_config.remote_ip.get(),
728                low_mtu,
729            ),
730            Some(new_mtu3)
731        );
732
733        // Advance time to 10s.
734        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
735
736        // Make sure the update didn't work. PMTU and last updated should not
737        // have changed.
738        assert_eq!(
739            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
740                .unwrap(),
741            new_mtu3
742        );
743        assert_eq!(
744            get_last_updated(
745                &mut core_ctx,
746                fake_config.local_ip.get(),
747                fake_config.remote_ip.get()
748            )
749            .unwrap(),
750            last_updated
751        );
752    }
753
754    #[ip_test(I)]
755    fn test_ip_pmtu_task<I: TestIpExt>() {
756        let fake_config = I::TEST_ADDRS;
757        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
758
759        // Make sure there are no timers.
760        bindings_ctx.timers.assert_no_timers_installed();
761
762        let new_mtu1 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 50);
763        let start_time = bindings_ctx.now();
764        let duration = Duration::from_secs(1);
765
766        // Advance time to 1s.
767        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
768
769        // Update pmtu from local to remote. PMTU should be updated to
770        // `new_mtu1` and last updated instant should be updated to the start of
771        // the test + 1s.
772        assert_eq!(
773            PmtuHandler::update_pmtu_if_less(
774                &mut core_ctx,
775                &mut bindings_ctx,
776                fake_config.local_ip.get(),
777                fake_config.remote_ip.get(),
778                new_mtu1,
779            ),
780            Some(new_mtu1)
781        );
782
783        // Make sure a task got scheduled.
784        bindings_ctx.timers.assert_timers_installed([(
785            PmtuTimerId::default(),
786            FakeInstant::from(MAINTENANCE_PERIOD + Duration::from_secs(1)),
787        )]);
788
789        // Advance time to 2s.
790        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
791
792        // Make sure the update worked. PMTU should be updated to `new_mtu1` and
793        // last updated instant should be updated to the start of the test + 1s
794        // (when the update occurred.
795        assert_eq!(
796            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
797                .unwrap(),
798            new_mtu1
799        );
800        assert_eq!(
801            get_last_updated(
802                &mut core_ctx,
803                fake_config.local_ip.get(),
804                fake_config.remote_ip.get()
805            )
806            .unwrap(),
807            start_time + duration
808        );
809
810        // Advance time to 30mins.
811        assert_empty(bindings_ctx.trigger_timers_for(duration * 1798, &mut core_ctx));
812
813        // Update pmtu from local to another remote. PMTU should be updated to
814        // `new_mtu1` and last updated instant should be updated to the start of
815        // the test + 1s.
816        let other_ip = get_other_ip_address::<I>();
817        let new_mtu2 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 100);
818        assert_eq!(
819            PmtuHandler::update_pmtu_if_less(
820                &mut core_ctx,
821                &mut bindings_ctx,
822                fake_config.local_ip.get(),
823                other_ip.get(),
824                new_mtu2,
825            ),
826            Some(new_mtu2)
827        );
828
829        // Make sure there is still a task scheduled. (we know no timers got
830        // triggered because the `run_for` methods returned 0 so far).
831        bindings_ctx.timers.assert_timers_installed([(
832            PmtuTimerId::default(),
833            FakeInstant::from(MAINTENANCE_PERIOD + Duration::from_secs(1)),
834        )]);
835
836        // Make sure the update worked. PMTU should be updated to `new_mtu2` and
837        // last updated instant should be updated to the start of the test +
838        // 30mins + 2s (when the update occurred.
839        assert_eq!(
840            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
841            new_mtu2
842        );
843        assert_eq!(
844            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
845            start_time + (duration * 1800)
846        );
847        // Make sure first update is still in the cache.
848        assert_eq!(
849            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
850                .unwrap(),
851            new_mtu1
852        );
853        assert_eq!(
854            get_last_updated(
855                &mut core_ctx,
856                fake_config.local_ip.get(),
857                fake_config.remote_ip.get()
858            )
859            .unwrap(),
860            start_time + duration
861        );
862
863        // Advance time to 1hr + 1s. Should have triggered a timer.
864        bindings_ctx.trigger_timers_for_and_expect(
865            duration * 1801,
866            [PmtuTimerId::default()],
867            &mut core_ctx,
868        );
869        // Make sure none of the cache data has been marked as stale and
870        // removed.
871        assert_eq!(
872            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
873                .unwrap(),
874            new_mtu1
875        );
876        assert_eq!(
877            get_last_updated(
878                &mut core_ctx,
879                fake_config.local_ip.get(),
880                fake_config.remote_ip.get()
881            )
882            .unwrap(),
883            start_time + duration
884        );
885        assert_eq!(
886            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
887            new_mtu2
888        );
889        assert_eq!(
890            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
891            start_time + (duration * 1800)
892        );
893        // Should still have another task scheduled.
894        bindings_ctx.timers.assert_timers_installed([(
895            PmtuTimerId::default(),
896            FakeInstant::from(MAINTENANCE_PERIOD * 2 + Duration::from_secs(1)),
897        )]);
898
899        // Advance time to 3hr + 1s. Should have triggered 2 timers.
900        bindings_ctx.trigger_timers_for_and_expect(
901            duration * 7200,
902            [PmtuTimerId::default(), PmtuTimerId::default()],
903            &mut core_ctx,
904        );
905        // Make sure only the earlier PMTU data got marked as stale and removed.
906        assert_eq!(
907            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
908            None
909        );
910        assert_eq!(
911            get_last_updated(
912                &mut core_ctx,
913                fake_config.local_ip.get(),
914                fake_config.remote_ip.get()
915            ),
916            None
917        );
918        assert_eq!(
919            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
920            new_mtu2
921        );
922        assert_eq!(
923            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
924            start_time + (duration * 1800)
925        );
926        // Should still have another task scheduled.
927        bindings_ctx.timers.assert_timers_installed([(
928            PmtuTimerId::default(),
929            FakeInstant::from(MAINTENANCE_PERIOD * 4 + Duration::from_secs(1)),
930        )]);
931
932        // Advance time to 4hr + 1s. Should have triggered 1 timers.
933        bindings_ctx.trigger_timers_for_and_expect(
934            duration * 3600,
935            [PmtuTimerId::default()],
936            &mut core_ctx,
937        );
938        // Make sure both PMTU data got marked as stale and removed.
939        assert_eq!(
940            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
941            None
942        );
943        assert_eq!(
944            get_last_updated(
945                &mut core_ctx,
946                fake_config.local_ip.get(),
947                fake_config.remote_ip.get()
948            ),
949            None
950        );
951        assert_eq!(get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()), None);
952        assert_eq!(
953            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()),
954            None
955        );
956        // Should not have a task scheduled since there is no more PMTU data.
957        bindings_ctx.timers.assert_no_timers_installed();
958    }
959
960    #[ip_test(I)]
961    fn discard_lru<I: TestIpExt>() {
962        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
963
964        // Fill the cache to capacity.
965        //
966        // If this assertion trips because we've increased `MAX_ENTRIES`, we'll need to
967        // update this test to use a different method than `get_other_ip_address` since
968        // it only allows us to choose a single byte of the address.
969        assert!(MAX_ENTRIES.get() <= usize::from(u8::MAX) + 1);
970        for i in 0..MAX_ENTRIES.get() {
971            let i = u8::try_from(i).unwrap();
972            assert_eq!(
973                PmtuHandler::update_pmtu_if_less(
974                    &mut core_ctx,
975                    &mut bindings_ctx,
976                    *I::TEST_ADDRS.local_ip,
977                    *I::get_other_ip_address(i),
978                    Mtu::max(),
979                ),
980                Some(Mtu::max())
981            );
982        }
983        assert_eq!(core_ctx.state.cache.cache.len(), MAX_ENTRIES.get());
984
985        // The next insertion should cause the LRU entry to be discarded.
986        assert_eq!(
987            PmtuHandler::update_pmtu_if_less(
988                &mut core_ctx,
989                &mut bindings_ctx,
990                *I::TEST_ADDRS.remote_ip,
991                *I::TEST_ADDRS.local_ip,
992                Mtu::max(),
993            ),
994            Some(Mtu::max())
995        );
996        assert_eq!(core_ctx.state.cache.cache.len(), MAX_ENTRIES.get());
997        assert_eq!(
998            core_ctx.state.cache.get_pmtu(*I::TEST_ADDRS.local_ip, *I::get_other_ip_address(0)),
999            None
1000        );
1001        assert_eq!(
1002            core_ctx.state.cache.get_pmtu(*I::TEST_ADDRS.remote_ip, *I::TEST_ADDRS.local_ip),
1003            Some(Mtu::max())
1004        );
1005    }
1006}