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