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 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.insert(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        // TODO(https://fxbug.dev/406779050): use `LruCache::retain` when such a
366        // method is available to avoid allocating a separate `Vec` of entries
367        // to remove.
368        let to_remove: Vec<_> = self
369            .cache
370            .iter()
371            .filter_map(|(k, v)| {
372                (now.saturating_duration_since(v.last_updated) >= PMTU_STALE_TIMEOUT).then_some(*k)
373            })
374            .collect();
375        for key in to_remove {
376            let _: Option<_> = self.cache.remove(&key);
377        }
378    }
379
380    fn is_empty(&self) -> bool {
381        self.cache.is_empty()
382    }
383}
384
385/// Get next lower PMTU plateau value, if one exists.
386fn next_lower_pmtu_plateau(start_mtu: Mtu) -> Option<Mtu> {
387    /// Common MTU values taken from [RFC 1191 section 7.1].
388    ///
389    /// This list includes lower bounds of groups of common MTU values that are
390    /// relatively close to each other, sorted in descending order.
391    ///
392    /// Note, the RFC does not actually include the value 1280 in the list of
393    /// plateau values, but we include it here because it is the minimum IPv6
394    /// MTU value and is not expected to be an uncommon value for MTUs.
395    ///
396    /// This list MUST be sorted in descending order; methods such as
397    /// `next_lower_pmtu_plateau` assume `PMTU_PLATEAUS` has this property.
398    ///
399    /// We use this list when estimating PMTU values when doing PMTU discovery
400    /// with IPv4 on paths with nodes that do not implement RFC 1191. This list
401    /// is useful as in practice, relatively few MTU values are in use.
402    ///
403    /// [RFC 1191 section 7.1]: https://tools.ietf.org/html/rfc1191#section-7.1
404    const PMTU_PLATEAUS: [Mtu; 12] = [
405        Mtu::new(65535),
406        Mtu::new(32000),
407        Mtu::new(17914),
408        Mtu::new(8166),
409        Mtu::new(4352),
410        Mtu::new(2002),
411        Mtu::new(1492),
412        Mtu::new(1280),
413        Mtu::new(1006),
414        Mtu::new(508),
415        Mtu::new(296),
416        Mtu::new(68),
417    ];
418
419    for i in 0..PMTU_PLATEAUS.len() {
420        let pmtu = PMTU_PLATEAUS[i];
421
422        if pmtu < start_mtu {
423            // Current PMTU is less than `start_mtu` and we know `PMTU_PLATEAUS`
424            // is sorted so this is the next best PMTU estimate.
425            return Some(pmtu);
426        }
427    }
428
429    None
430}
431
432#[cfg(test)]
433#[macro_use]
434pub(crate) mod testutil {
435    /// Implement the `PmtuHandler<$ip_version>` trait by just panicking.
436    macro_rules! impl_pmtu_handler {
437        ($ty:ty, $ctx:ty, $ip_version:ident) => {
438            impl PmtuHandler<net_types::ip::$ip_version, $ctx> for $ty {
439                fn update_pmtu_if_less(
440                    &mut self,
441                    _ctx: &mut $ctx,
442                    _src_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
443                    _dst_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
444                    _new_mtu: Mtu,
445                ) -> Option<Mtu> {
446                    unimplemented!()
447                }
448
449                fn update_pmtu_next_lower(
450                    &mut self,
451                    _ctx: &mut $ctx,
452                    _src_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
453                    _dst_ip: <net_types::ip::$ip_version as net_types::ip::Ip>::Addr,
454                    _from: Mtu,
455                ) -> Option<Mtu> {
456                    unimplemented!()
457                }
458            }
459        };
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    use ip_test_macro::ip_test;
468    use net_types::{SpecifiedAddr, Witness};
469    use netstack3_base::testutil::{
470        FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeTimerCtxExt, TestIpExt, assert_empty,
471    };
472    use netstack3_base::{CtxPair, InstantContext, IntoCoreTimerCtx};
473    use test_case::test_case;
474
475    struct FakePmtuContext<I: Ip> {
476        cache: PmtuCache<I, FakeBindingsCtxImpl<I>>,
477    }
478
479    type FakeCtxImpl<I> = CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>>;
480    type FakeCoreCtxImpl<I> = FakeCoreCtx<FakePmtuContext<I>, (), ()>;
481    type FakeBindingsCtxImpl<I> = FakeBindingsCtx<PmtuTimerId<I>, (), (), ()>;
482
483    impl<I: Ip> PmtuContext<I, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
484        fn with_state_mut<O, F: FnOnce(&mut PmtuCache<I, FakeBindingsCtxImpl<I>>) -> O>(
485            &mut self,
486            cb: F,
487        ) -> O {
488            cb(&mut self.state.cache)
489        }
490    }
491
492    fn new_context<I: Ip>() -> FakeCtxImpl<I> {
493        FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
494            FakeCoreCtxImpl::with_state(FakePmtuContext {
495                cache: PmtuCache::new::<IntoCoreTimerCtx>(bindings_ctx),
496            })
497        })
498    }
499
500    /// Get an IPv4 or IPv6 address within the same subnet as that of
501    /// `TEST_ADDRS_*`, but with the last octet set to `3`.
502    fn get_other_ip_address<I: TestIpExt>() -> SpecifiedAddr<I::Addr> {
503        I::get_other_ip_address(3)
504    }
505
506    impl<I: Ip, BT: PmtuBindingsTypes> PmtuCache<I, BT> {
507        /// Gets the last updated [`Instant`] when the PMTU between `src_ip` and
508        /// `dst_ip` was updated.
509        ///
510        /// [`Instant`]: Instant
511        fn get_last_updated(&mut self, src_ip: I::Addr, dst_ip: I::Addr) -> Option<BT::Instant> {
512            self.cache.get_mut(&PmtuCacheKey::new(src_ip, dst_ip)).map(|x| x.last_updated.clone())
513        }
514    }
515
516    #[test_case(Mtu::new(65536) => Some(Mtu::new(65535)))]
517    #[test_case(Mtu::new(65535) => Some(Mtu::new(32000)))]
518    #[test_case(Mtu::new(65534) => Some(Mtu::new(32000)))]
519    #[test_case(Mtu::new(32001) => Some(Mtu::new(32000)))]
520    #[test_case(Mtu::new(32000) => Some(Mtu::new(17914)))]
521    #[test_case(Mtu::new(31999) => Some(Mtu::new(17914)))]
522    #[test_case(Mtu::new(1281)  => Some(Mtu::new(1280)))]
523    #[test_case(Mtu::new(1280)  => Some(Mtu::new(1006)))]
524    #[test_case(Mtu::new(69)    => Some(Mtu::new(68)))]
525    #[test_case(Mtu::new(68)    => None)]
526    #[test_case(Mtu::new(67)    => None)]
527    #[test_case(Mtu::new(0)     => None)]
528    fn test_next_lower_pmtu_plateau(start: Mtu) -> Option<Mtu> {
529        next_lower_pmtu_plateau(start)
530    }
531
532    fn get_pmtu<I: Ip>(
533        core_ctx: &mut FakeCoreCtxImpl<I>,
534        src_ip: I::Addr,
535        dst_ip: I::Addr,
536    ) -> Option<Mtu> {
537        core_ctx.state.cache.get_pmtu(src_ip, dst_ip)
538    }
539
540    fn get_last_updated<I: Ip>(
541        core_ctx: &mut FakeCoreCtxImpl<I>,
542        src_ip: I::Addr,
543        dst_ip: I::Addr,
544    ) -> Option<FakeInstant> {
545        core_ctx.state.cache.get_last_updated(src_ip, dst_ip)
546    }
547
548    #[ip_test(I)]
549    fn test_ip_path_mtu_cache_ctx<I: TestIpExt>() {
550        let fake_config = I::TEST_ADDRS;
551        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
552
553        // Nothing in the cache yet
554        assert_eq!(
555            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
556            None
557        );
558        assert_eq!(
559            get_last_updated(
560                &mut core_ctx,
561                fake_config.local_ip.get(),
562                fake_config.remote_ip.get()
563            ),
564            None
565        );
566
567        let new_mtu1 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 50);
568        let start_time = bindings_ctx.now();
569        let duration = Duration::from_secs(1);
570
571        // Advance time to 1s.
572        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
573
574        // Update pmtu from local to remote. PMTU should be updated to
575        // `new_mtu1` and last updated instant should be updated to the start of
576        // the test + 1s.
577        assert_eq!(
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_mtu1,
584            ),
585            Some(new_mtu1)
586        );
587
588        // Advance time to 2s.
589        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
590
591        // Make sure the update worked. PMTU should be updated to `new_mtu1` and
592        // last updated instant should be updated to the start of the test + 1s
593        // (when the update occurred.
594        assert_eq!(
595            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
596                .unwrap(),
597            new_mtu1
598        );
599        assert_eq!(
600            get_last_updated(
601                &mut core_ctx,
602                fake_config.local_ip.get(),
603                fake_config.remote_ip.get()
604            )
605            .unwrap(),
606            start_time + duration
607        );
608
609        let new_mtu2 = Mtu::new(u32::from(new_mtu1) - 1);
610
611        // Advance time to 3s.
612        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
613
614        // Updating again should return the last pmtu PMTU should be updated to
615        // `new_mtu2` and last updated instant should be updated to the start of
616        // the test + 3s.
617        assert_eq!(
618            PmtuHandler::update_pmtu_if_less(
619                &mut core_ctx,
620                &mut bindings_ctx,
621                fake_config.local_ip.get(),
622                fake_config.remote_ip.get(),
623                new_mtu2,
624            ),
625            Some(new_mtu2)
626        );
627
628        // Advance time to 4s.
629        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
630
631        // Make sure the update worked. PMTU should be updated to `new_mtu2` and
632        // last updated instant should be updated to the start of the test + 3s
633        // (when the update occurred).
634        assert_eq!(
635            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
636                .unwrap(),
637            new_mtu2
638        );
639        assert_eq!(
640            get_last_updated(
641                &mut core_ctx,
642                fake_config.local_ip.get(),
643                fake_config.remote_ip.get()
644            )
645            .unwrap(),
646            start_time + (duration * 3)
647        );
648
649        let new_mtu3 = Mtu::new(u32::from(new_mtu2) - 1);
650
651        // Advance time to 5s.
652        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
653
654        // Make sure update only if new PMTU is less than current (it is). PMTU
655        // should be updated to `new_mtu3` and last updated instant should be
656        // updated to the start of the test + 5s.
657        assert_eq!(
658            PmtuHandler::update_pmtu_if_less(
659                &mut core_ctx,
660                &mut bindings_ctx,
661                fake_config.local_ip.get(),
662                fake_config.remote_ip.get(),
663                new_mtu3,
664            ),
665            Some(new_mtu3)
666        );
667
668        // Advance time to 6s.
669        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
670
671        // Make sure the update worked. PMTU should be updated to `new_mtu3` and
672        // last updated instant should be updated to the start of the test + 5s
673        // (when the update occurred).
674        assert_eq!(
675            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
676                .unwrap(),
677            new_mtu3
678        );
679        let last_updated = start_time + (duration * 5);
680        assert_eq!(
681            get_last_updated(
682                &mut core_ctx,
683                fake_config.local_ip.get(),
684                fake_config.remote_ip.get()
685            )
686            .unwrap(),
687            last_updated
688        );
689
690        let new_mtu4 = Mtu::new(u32::from(new_mtu3) + 50);
691
692        // Advance time to 7s.
693        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
694
695        // Make sure update only if new PMTU is less than current (it isn't)
696        assert_eq!(
697            PmtuHandler::update_pmtu_if_less(
698                &mut core_ctx,
699                &mut bindings_ctx,
700                fake_config.local_ip.get(),
701                fake_config.remote_ip.get(),
702                new_mtu4,
703            ),
704            Some(new_mtu3)
705        );
706
707        // Advance time to 8s.
708        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
709
710        // Make sure the update didn't work. PMTU and last updated should not
711        // have changed.
712        assert_eq!(
713            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
714                .unwrap(),
715            new_mtu3
716        );
717        assert_eq!(
718            get_last_updated(
719                &mut core_ctx,
720                fake_config.local_ip.get(),
721                fake_config.remote_ip.get()
722            )
723            .unwrap(),
724            last_updated
725        );
726
727        let low_mtu = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) - 1);
728
729        // Advance time to 9s.
730        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
731
732        // Updating with MTU value less than the minimum MTU should fail.
733        assert_eq!(
734            PmtuHandler::update_pmtu_if_less(
735                &mut core_ctx,
736                &mut bindings_ctx,
737                fake_config.local_ip.get(),
738                fake_config.remote_ip.get(),
739                low_mtu,
740            ),
741            Some(new_mtu3)
742        );
743
744        // Advance time to 10s.
745        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
746
747        // Make sure the update didn't work. PMTU and last updated should not
748        // have changed.
749        assert_eq!(
750            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
751                .unwrap(),
752            new_mtu3
753        );
754        assert_eq!(
755            get_last_updated(
756                &mut core_ctx,
757                fake_config.local_ip.get(),
758                fake_config.remote_ip.get()
759            )
760            .unwrap(),
761            last_updated
762        );
763    }
764
765    #[ip_test(I)]
766    fn test_ip_pmtu_task<I: TestIpExt>() {
767        let fake_config = I::TEST_ADDRS;
768        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
769
770        // Make sure there are no timers.
771        bindings_ctx.timers.assert_no_timers_installed();
772
773        let new_mtu1 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 50);
774        let start_time = bindings_ctx.now();
775        let duration = Duration::from_secs(1);
776
777        // Advance time to 1s.
778        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
779
780        // Update pmtu from local to remote. PMTU should be updated to
781        // `new_mtu1` and last updated instant should be updated to the start of
782        // the test + 1s.
783        assert_eq!(
784            PmtuHandler::update_pmtu_if_less(
785                &mut core_ctx,
786                &mut bindings_ctx,
787                fake_config.local_ip.get(),
788                fake_config.remote_ip.get(),
789                new_mtu1,
790            ),
791            Some(new_mtu1)
792        );
793
794        // Make sure a task got scheduled.
795        bindings_ctx.timers.assert_timers_installed([(
796            PmtuTimerId::default(),
797            FakeInstant::from(MAINTENANCE_PERIOD + Duration::from_secs(1)),
798        )]);
799
800        // Advance time to 2s.
801        assert_empty(bindings_ctx.trigger_timers_for(duration, &mut core_ctx));
802
803        // Make sure the update worked. PMTU should be updated to `new_mtu1` and
804        // last updated instant should be updated to the start of the test + 1s
805        // (when the update occurred.
806        assert_eq!(
807            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
808                .unwrap(),
809            new_mtu1
810        );
811        assert_eq!(
812            get_last_updated(
813                &mut core_ctx,
814                fake_config.local_ip.get(),
815                fake_config.remote_ip.get()
816            )
817            .unwrap(),
818            start_time + duration
819        );
820
821        // Advance time to 30mins.
822        assert_empty(bindings_ctx.trigger_timers_for(duration * 1798, &mut core_ctx));
823
824        // Update pmtu from local to another remote. PMTU should be updated to
825        // `new_mtu1` and last updated instant should be updated to the start of
826        // the test + 1s.
827        let other_ip = get_other_ip_address::<I>();
828        let new_mtu2 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 100);
829        assert_eq!(
830            PmtuHandler::update_pmtu_if_less(
831                &mut core_ctx,
832                &mut bindings_ctx,
833                fake_config.local_ip.get(),
834                other_ip.get(),
835                new_mtu2,
836            ),
837            Some(new_mtu2)
838        );
839
840        // Make sure there is still a task scheduled. (we know no timers got
841        // triggered because the `run_for` methods returned 0 so far).
842        bindings_ctx.timers.assert_timers_installed([(
843            PmtuTimerId::default(),
844            FakeInstant::from(MAINTENANCE_PERIOD + Duration::from_secs(1)),
845        )]);
846
847        // Make sure the update worked. PMTU should be updated to `new_mtu2` and
848        // last updated instant should be updated to the start of the test +
849        // 30mins + 2s (when the update occurred.
850        assert_eq!(
851            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
852            new_mtu2
853        );
854        assert_eq!(
855            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
856            start_time + (duration * 1800)
857        );
858        // Make sure first update is still in the cache.
859        assert_eq!(
860            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
861                .unwrap(),
862            new_mtu1
863        );
864        assert_eq!(
865            get_last_updated(
866                &mut core_ctx,
867                fake_config.local_ip.get(),
868                fake_config.remote_ip.get()
869            )
870            .unwrap(),
871            start_time + duration
872        );
873
874        // Advance time to 1hr + 1s. Should have triggered a timer.
875        bindings_ctx.trigger_timers_for_and_expect(
876            duration * 1801,
877            [PmtuTimerId::default()],
878            &mut core_ctx,
879        );
880        // Make sure none of the cache data has been marked as stale and
881        // removed.
882        assert_eq!(
883            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get())
884                .unwrap(),
885            new_mtu1
886        );
887        assert_eq!(
888            get_last_updated(
889                &mut core_ctx,
890                fake_config.local_ip.get(),
891                fake_config.remote_ip.get()
892            )
893            .unwrap(),
894            start_time + duration
895        );
896        assert_eq!(
897            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
898            new_mtu2
899        );
900        assert_eq!(
901            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
902            start_time + (duration * 1800)
903        );
904        // Should still have another task scheduled.
905        bindings_ctx.timers.assert_timers_installed([(
906            PmtuTimerId::default(),
907            FakeInstant::from(MAINTENANCE_PERIOD * 2 + Duration::from_secs(1)),
908        )]);
909
910        // Advance time to 3hr + 1s. Should have triggered 2 timers.
911        bindings_ctx.trigger_timers_for_and_expect(
912            duration * 7200,
913            [PmtuTimerId::default(), PmtuTimerId::default()],
914            &mut core_ctx,
915        );
916        // Make sure only the earlier PMTU data got marked as stale and removed.
917        assert_eq!(
918            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
919            None
920        );
921        assert_eq!(
922            get_last_updated(
923                &mut core_ctx,
924                fake_config.local_ip.get(),
925                fake_config.remote_ip.get()
926            ),
927            None
928        );
929        assert_eq!(
930            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
931            new_mtu2
932        );
933        assert_eq!(
934            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()).unwrap(),
935            start_time + (duration * 1800)
936        );
937        // Should still have another task scheduled.
938        bindings_ctx.timers.assert_timers_installed([(
939            PmtuTimerId::default(),
940            FakeInstant::from(MAINTENANCE_PERIOD * 4 + Duration::from_secs(1)),
941        )]);
942
943        // Advance time to 4hr + 1s. Should have triggered 1 timers.
944        bindings_ctx.trigger_timers_for_and_expect(
945            duration * 3600,
946            [PmtuTimerId::default()],
947            &mut core_ctx,
948        );
949        // Make sure both PMTU data got marked as stale and removed.
950        assert_eq!(
951            get_pmtu(&mut core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()),
952            None
953        );
954        assert_eq!(
955            get_last_updated(
956                &mut core_ctx,
957                fake_config.local_ip.get(),
958                fake_config.remote_ip.get()
959            ),
960            None
961        );
962        assert_eq!(get_pmtu(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()), None);
963        assert_eq!(
964            get_last_updated(&mut core_ctx, fake_config.local_ip.get(), other_ip.get()),
965            None
966        );
967        // Should not have a task scheduled since there is no more PMTU data.
968        bindings_ctx.timers.assert_no_timers_installed();
969    }
970
971    #[ip_test(I)]
972    fn discard_lru<I: TestIpExt>() {
973        let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_context::<I>();
974
975        // Fill the cache to capacity.
976        //
977        // If this assertion trips because we've increased `MAX_ENTRIES`, we'll need to
978        // update this test to use a different method than `get_other_ip_address` since
979        // it only allows us to choose a single byte of the address.
980        assert!(MAX_ENTRIES <= usize::from(u8::MAX) + 1);
981        for i in 0..MAX_ENTRIES {
982            let i = u8::try_from(i).unwrap();
983            assert_eq!(
984                PmtuHandler::update_pmtu_if_less(
985                    &mut core_ctx,
986                    &mut bindings_ctx,
987                    *I::TEST_ADDRS.local_ip,
988                    *I::get_other_ip_address(i),
989                    Mtu::max(),
990                ),
991                Some(Mtu::max())
992            );
993        }
994        assert_eq!(core_ctx.state.cache.cache.len(), MAX_ENTRIES);
995
996        // The next insertion should cause the LRU entry to be discarded.
997        assert_eq!(
998            PmtuHandler::update_pmtu_if_less(
999                &mut core_ctx,
1000                &mut bindings_ctx,
1001                *I::TEST_ADDRS.remote_ip,
1002                *I::TEST_ADDRS.local_ip,
1003                Mtu::max(),
1004            ),
1005            Some(Mtu::max())
1006        );
1007        assert_eq!(core_ctx.state.cache.cache.len(), MAX_ENTRIES);
1008        assert_eq!(
1009            core_ctx.state.cache.get_pmtu(*I::TEST_ADDRS.local_ip, *I::get_other_ip_address(0)),
1010            None
1011        );
1012        assert_eq!(
1013            core_ctx.state.cache.get_pmtu(*I::TEST_ADDRS.remote_ip, *I::TEST_ADDRS.local_ip),
1014            Some(Mtu::max())
1015        );
1016    }
1017}