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