Skip to main content

netstack3_ip/device/
nud.rs

1// Copyright 2022 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//! Neighbor unreachability detection.
6
7use alloc::collections::{BinaryHeap, VecDeque};
8use alloc::vec::Vec;
9use core::fmt::Debug;
10use core::hash::Hash;
11use core::marker::PhantomData;
12use core::num::{NonZeroU16, NonZeroU32};
13use core::time::Duration;
14
15use assert_matches::assert_matches;
16use derivative::Derivative;
17use log::{debug, error, warn};
18use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
19use net_types::{SpecifiedAddr, UnicastAddr};
20use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
21use netstack3_base::{
22    AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
23    ErrorAndSerializer, EventContext, HandleableTimer, Instant, InstantBindingsTypes, LinkDevice,
24    LinkDeviceAddress, LocalTimerHeap, NetworkSerializationContext, NetworkSerializer,
25    SendFrameError, SendFrameErrorReason, StrongDeviceIdentifier, TimerBindingsTypes, TimerContext,
26    TxMetadataBindingsTypes, WeakDeviceIdentifier,
27};
28use netstack3_hashmap::hash_map::{Entry, HashMap, OccupiedEntry};
29use packet::{
30    Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
31};
32use packet_formats::ip::IpPacket as _;
33use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
34use packet_formats::ipv6::Ipv6Packet;
35use packet_formats::utils::NonZeroDuration;
36use static_assertions::const_assert;
37use thiserror::Error;
38use zerocopy::SplitByteSlice;
39
40pub(crate) mod api;
41
42/// The default maximum number of multicast solicitations as defined in [RFC
43/// 4861 section 10].
44///
45/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
46pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
47
48/// The default maximum number of unicast solicitations as defined in [RFC 4861
49/// section 10].
50///
51/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
52const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
53
54/// The maximum amount of time between retransmissions of neighbor probe
55/// messages as defined in [RFC 7048 section 4].
56///
57/// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
58const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
59
60/// The default value for *RetransTimer* as defined in [RFC 4861 section 10].
61///
62/// Note that both ARP (IPv4) and NDP (IPv6) uses this default value to align
63/// behavior between IPv4 and IPv6 and simplify testing.
64///
65/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
66pub const DEFAULT_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
67
68/// The exponential backoff factor for retransmissions of multicast neighbor
69/// probe messages as defined in [RFC 7048 section 4].
70///
71/// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
72const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
73
74const MAX_PENDING_FRAMES: usize = 10;
75
76/// The time a neighbor is considered reachable after receiving a reachability
77/// confirmation, as defined in [RFC 4861 section 6.3.2].
78///
79/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
80const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
81
82/// The time after which a neighbor in the DELAY state transitions to PROBE, as
83/// defined in [RFC 4861 section 10].
84///
85/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
86const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
87
88/// The garbage collection threshold for the neighbor table for a given device.
89/// When the number of entries is above this number and an entry transitions
90/// into a discardable state, a garbage collection task will be scheduled to
91/// remove any entries that are not in use.
92pub const GC_THRESHOLD: usize = 512;
93
94/// The maximum number of neighbor entries in the neighbor table for a given
95/// device. When the number of entries reaches this number, new entries can no
96/// longer be inserted.
97pub const MAX_ENTRIES: usize = 1024;
98const_assert!(MAX_ENTRIES > GC_THRESHOLD);
99
100/// The minimum amount of time between garbage collection passes when the
101/// neighbor table grows beyond `GC_THRESHOLD`.
102const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
103
104/// NUD counters.
105#[derive(Default)]
106pub struct NudCountersInner {
107    /// Count of ICMP destination unreachable errors that could not be sent.
108    pub icmp_dest_unreachable_dropped: Counter,
109}
110
111/// NUD counters.
112pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
113
114/// Neighbor confirmation flags.
115#[derive(Debug, Copy, Clone)]
116pub struct ConfirmationFlags {
117    /// True if neighbor was explicitly solicited.
118    pub solicited_flag: bool,
119    /// True if must override neighbor entry.
120    pub override_flag: bool,
121}
122
123/// The type of message with a dynamic neighbor update.
124#[derive(Debug, Copy, Clone)]
125pub enum DynamicNeighborUpdateSource<A> {
126    /// Indicates an update from a neighbor probe message.
127    ///
128    /// E.g. NDP Neighbor Solicitation.
129    Probe {
130        /// The source link-layer address option.
131        link_address: UnicastAddr<A>,
132    },
133
134    /// Indicates an update from a neighbor confirmation message.
135    ///
136    /// E.g. NDP Neighbor Advertisement.
137    Confirmation {
138        /// The target link-layer address option.
139        link_address: Option<UnicastAddr<A>>,
140        /// The flags set on the neighbor confirmation.
141        flags: ConfirmationFlags,
142    },
143}
144
145/// A neighbor's state.
146#[derive(Derivative)]
147#[derivative(Debug(bound = ""))]
148#[cfg_attr(
149    any(test, feature = "testutils"),
150    derivative(
151        Clone(bound = "BT::TxMetadata: Clone"),
152        PartialEq(bound = "BT::TxMetadata: PartialEq"),
153        Eq(bound = "BT::TxMetadata: Eq")
154    )
155)]
156#[allow(missing_docs)]
157pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
158    Dynamic(DynamicNeighborState<D, BT>),
159    Static(UnicastAddr<D::Address>),
160}
161
162/// The state of a dynamic entry in the neighbor cache within the Neighbor
163/// Unreachability Detection state machine, defined in [RFC 4861 section 7.3.2]
164/// and [RFC 7048 section 3].
165///
166/// [RFC 4861 section 7.3.2]: https://tools.ietf.org/html/rfc4861#section-7.3.2
167/// [RFC 7048 section 3]: https://tools.ietf.org/html/rfc7048#section-3
168#[derive(Derivative)]
169#[derivative(Debug(bound = ""))]
170#[cfg_attr(
171    any(test, feature = "testutils"),
172    derivative(
173        Clone(bound = "BT::TxMetadata: Clone"),
174        PartialEq(bound = "BT::TxMetadata: PartialEq"),
175        Eq(bound = "BT::TxMetadata: Eq")
176    )
177)]
178pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
179    /// Address resolution is being performed on the entry.
180    ///
181    /// Specifically, a probe has been sent to the solicited-node multicast
182    /// address of the target, but the corresponding confirmation has not yet
183    /// been received.
184    Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
185
186    /// Positive confirmation was received within the last ReachableTime
187    /// milliseconds that the forward path to the neighbor was functioning
188    /// properly. While `Reachable`, no special action takes place as packets
189    /// are sent.
190    Reachable(Reachable<D, BT::Instant>),
191
192    /// More than ReachableTime milliseconds have elapsed since the last
193    /// positive confirmation was received that the forward path was functioning
194    /// properly. While stale, no action takes place until a packet is sent.
195    ///
196    /// The `Stale` state is entered upon receiving an unsolicited neighbor
197    /// message that updates the cached link-layer address. Receipt of such a
198    /// message does not confirm reachability, and entering the `Stale` state
199    /// ensures reachability is verified quickly if the entry is actually being
200    /// used. However, reachability is not actually verified until the entry is
201    /// actually used.
202    Stale(Stale<D>),
203
204    /// A packet has been recently sent to the neighbor, which has stale
205    /// reachability information (i.e. we have not received recent positive
206    /// confirmation that the forward path is functioning properly).
207    ///
208    /// The `Delay` state is an optimization that gives upper-layer protocols
209    /// additional time to provide reachability confirmation in those cases
210    /// where ReachableTime milliseconds have passed since the last confirmation
211    /// due to lack of recent traffic. Without this optimization, the opening of
212    /// a TCP connection after a traffic lull would initiate probes even though
213    /// the subsequent three-way handshake would provide a reachability
214    /// confirmation almost immediately.
215    Delay(Delay<D>),
216
217    /// A reachability confirmation is actively sought by retransmitting probes
218    /// every RetransTimer milliseconds until a reachability confirmation is
219    /// received.
220    Probe(Probe<D>),
221
222    /// Similarly to the `Probe` state, a reachability confirmation is actively
223    /// sought by retransmitting probes; however, probes are multicast to the
224    /// solicited-node multicast address, using a timeout with exponential
225    /// backoff, rather than unicast to the cached link address. Also, probes
226    /// are only transmitted as long as packets continue to be sent to the
227    /// neighbor.
228    Unreachable(Unreachable<D>),
229}
230
231/// The state of dynamic neighbor table entries as published via events.
232///
233/// Note that this is not how state is held in the neighbor table itself,
234/// see [`DynamicNeighborState`].
235///
236/// Modeled after RFC 4861 section 7.3.2. Descriptions are kept
237/// implementation-independent by using a set of generic terminology.
238///
239/// ,------------------------------------------------------------------.
240/// | Generic Term              | ARP Term    | NDP Term               |
241/// |---------------------------+-------------+------------------------|
242/// | Reachability Probe        | ARP Request | Neighbor Solicitation  |
243/// | Reachability Confirmation | ARP Reply   | Neighbor Advertisement |
244/// `---------------------------+-------------+------------------------'
245#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
246pub enum EventDynamicState<L: LinkDeviceAddress> {
247    /// Reachability is in the process of being confirmed for a newly
248    /// created entry.
249    Incomplete,
250    /// Forward reachability has been confirmed; the path to the neighbor
251    /// is functioning properly.
252    Reachable(UnicastAddr<L>),
253    /// Reachability is considered unknown.
254    ///
255    /// Occurs in one of two ways:
256    ///   1. Too much time has elapsed since the last positive reachability
257    ///      confirmation was received.
258    ///   2. Received a reachability confirmation from a neighbor with a
259    ///      different MAC address than the one cached.
260    Stale(UnicastAddr<L>),
261    /// A packet was recently sent while reachability was considered
262    /// unknown.
263    ///
264    /// This state is an optimization that gives non-Neighbor-Discovery
265    /// related protocols time to confirm reachability after the last
266    /// confirmation of reachability has expired due to lack of recent
267    /// traffic.
268    Delay(UnicastAddr<L>),
269    /// A reachability confirmation is actively sought by periodically
270    /// retransmitting unicast reachability probes until a reachability
271    /// confirmation is received, or until the maximum number of probes has
272    /// been sent.
273    Probe(UnicastAddr<L>),
274    /// Target is considered unreachable. A reachability confirmation was not
275    /// received after transmitting the maximum number of reachability
276    /// probes.
277    Unreachable(UnicastAddr<L>),
278}
279
280/// Neighbor state published via events.
281///
282/// Note that this is not how state is held in the neighbor table itself,
283/// see [`NeighborState`].
284///
285/// Either a dynamic state within the Neighbor Unreachability Detection (NUD)
286/// state machine, or a static entry that never expires.
287#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
288pub enum EventState<L: LinkDeviceAddress> {
289    /// Dynamic neighbor state.
290    Dynamic(EventDynamicState<L>),
291    /// Static neighbor state.
292    Static(UnicastAddr<L>),
293}
294
295/// Neighbor event kind.
296#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
297pub enum EventKind<L: LinkDeviceAddress> {
298    /// A neighbor entry was added.
299    Added(EventState<L>),
300    /// A neighbor entry has changed.
301    Changed(EventState<L>),
302    /// A neighbor entry was removed.
303    Removed,
304}
305
306/// Neighbor event.
307#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
308#[generic_over_ip(I, Ip)]
309pub struct Event<L: LinkDeviceAddress, DeviceId, I: Ip, Instant> {
310    /// The device.
311    pub device: DeviceId,
312    /// The neighbor's address.
313    pub addr: SpecifiedAddr<I::Addr>,
314    /// The kind of this neighbor event.
315    pub kind: EventKind<L>,
316    /// Time of this event.
317    pub at: Instant,
318}
319
320impl<L: LinkDeviceAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
321    /// Changes the device id type with `map`.
322    pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
323        let Self { device, kind, addr, at } = self;
324        Event { device: map(device), kind, addr, at }
325    }
326}
327
328impl<L: LinkDeviceAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
329    fn changed(
330        device: &DeviceId,
331        event_state: EventState<L>,
332        addr: SpecifiedAddr<I::Addr>,
333        at: Instant,
334    ) -> Self {
335        Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
336    }
337
338    fn added(
339        device: &DeviceId,
340        event_state: EventState<L>,
341        addr: SpecifiedAddr<I::Addr>,
342        at: Instant,
343    ) -> Self {
344        Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
345    }
346
347    fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
348        Self { device: device.clone(), kind: EventKind::Removed, addr, at }
349    }
350}
351
352fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
353    core_ctx: &mut CC,
354    bindings_ctx: &mut BC,
355    timers: &mut TimerHeap<I, BC>,
356    neighbor: SpecifiedAddr<I::Addr>,
357    event: NudEvent,
358    counter: &mut Option<NonZeroU16>,
359) -> bool
360where
361    I: Ip,
362    D: LinkDevice,
363    DeviceId: StrongDeviceIdentifier,
364    BC: NudBindingsContext<I, D, DeviceId>,
365    CC: NudConfigContext<I>,
366{
367    match counter {
368        Some(c) => {
369            *counter = NonZeroU16::new(c.get() - 1);
370            let retransmit_timeout = core_ctx.retransmit_timeout();
371            timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
372            true
373        }
374        None => false,
375    }
376}
377
378/// The state for an incomplete neighbor entry.
379#[derive(Debug, Derivative)]
380#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
381pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
382    transmit_counter: Option<NonZeroU16>,
383    pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
384    #[derivative(PartialEq = "ignore")]
385    notifiers: Vec<N>,
386    _marker: PhantomData<D>,
387}
388
389#[cfg(any(test, feature = "testutils"))]
390impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
391    fn clone(&self) -> Self {
392        // Do not clone `notifiers` since the LinkResolutionNotifier type is not
393        // required to implement `Clone` and notifiers are not used in equality
394        // checks in tests.
395        let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
396        Self {
397            transmit_counter: transmit_counter.clone(),
398            pending_frames: pending_frames.clone(),
399            notifiers: Vec::new(),
400            _marker: PhantomData,
401        }
402    }
403}
404
405impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
406    fn drop(&mut self) {
407        let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
408        for notifier in notifiers.drain(..) {
409            notifier.notify(Err(AddressResolutionFailed));
410        }
411    }
412}
413
414impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
415    /// Creates a new `Incomplete` entry with `pending_frames` and remaining
416    /// transmits `transmit_counter`.
417    #[cfg(any(test, feature = "testutils"))]
418    pub fn new_with_pending_frames_and_transmit_counter(
419        pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
420        transmit_counter: Option<NonZeroU16>,
421    ) -> Self {
422        Self {
423            transmit_counter,
424            pending_frames,
425            notifiers: Default::default(),
426            _marker: PhantomData,
427        }
428    }
429
430    fn new<I, CC, BC, DeviceId>(
431        core_ctx: &mut CC,
432        bindings_ctx: &mut BC,
433        timers: &mut TimerHeap<I, BC>,
434        neighbor: SpecifiedAddr<I::Addr>,
435    ) -> Self
436    where
437        I: Ip,
438        D: LinkDevice,
439        BC: NudBindingsContext<I, D, DeviceId>,
440        CC: NudConfigContext<I>,
441        DeviceId: StrongDeviceIdentifier,
442    {
443        let mut this = Incomplete {
444            transmit_counter: Some(core_ctx.max_multicast_solicit()),
445            pending_frames: VecDeque::new(),
446            notifiers: Vec::new(),
447            _marker: PhantomData,
448        };
449        // NB: transmission of a neighbor probe on entering INCOMPLETE (and subsequent
450        // retransmissions) is done by `handle_timer`, as it need not be done with the
451        // neighbor table lock held.
452        assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
453
454        this
455    }
456
457    fn new_with_notifier<I, CC, BC, DeviceId>(
458        core_ctx: &mut CC,
459        bindings_ctx: &mut BC,
460        timers: &mut TimerHeap<I, BC>,
461        neighbor: SpecifiedAddr<I::Addr>,
462        notifier: BC::Notifier,
463    ) -> Self
464    where
465        I: Ip,
466        D: LinkDevice,
467        BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
468        CC: NudConfigContext<I>,
469        DeviceId: StrongDeviceIdentifier,
470    {
471        let mut this = Incomplete {
472            transmit_counter: Some(core_ctx.max_multicast_solicit()),
473            pending_frames: VecDeque::new(),
474            notifiers: [notifier].into(),
475            _marker: PhantomData,
476        };
477        // NB: transmission of a neighbor probe on entering INCOMPLETE (and subsequent
478        // retransmissions) is done by `handle_timer`, as it need not be done with the
479        // neighbor table lock held.
480        assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
481
482        this
483    }
484
485    fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
486        &mut self,
487        core_ctx: &mut CC,
488        bindings_ctx: &mut BC,
489        timers: &mut TimerHeap<I, BC>,
490        neighbor: SpecifiedAddr<I::Addr>,
491    ) -> bool
492    where
493        I: Ip,
494        D: LinkDevice,
495        DeviceId: StrongDeviceIdentifier,
496        BC: NudBindingsContext<I, D, DeviceId>,
497        CC: NudConfigContext<I>,
498    {
499        let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
500        schedule_timer_if_should_retransmit(
501            core_ctx,
502            bindings_ctx,
503            timers,
504            neighbor,
505            NudEvent::RetransmitMulticastProbe,
506            transmit_counter,
507        )
508    }
509
510    fn queue_packet<B, S>(
511        &mut self,
512        body: S,
513        meta: M,
514    ) -> Result<(), ErrorAndSerializer<SerializeError<!>, S>>
515    where
516        B: BufferMut,
517        S: NetworkSerializer<Buffer = B>,
518    {
519        let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
520
521        // We don't accept new packets when the queue is full because earlier packets
522        // are more likely to initiate connections whereas later packets are more likely
523        // to carry data. E.g. A TCP SYN/SYN-ACK is likely to appear before a TCP
524        // segment with data and dropping the SYN/SYN-ACK may result in the TCP peer not
525        // processing the segment with data since the segment completing the handshake
526        // has not been received and handled yet.
527        if pending_frames.len() < MAX_PENDING_FRAMES {
528            pending_frames.push_back((
529                body.serialize_vec_outer(&mut NetworkSerializationContext::default())
530                    .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
531                    .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
532                    .into_inner(),
533                meta,
534            ));
535        }
536        Ok(())
537    }
538
539    /// Flush pending packets to the resolved link address and notify any observers
540    /// that link address resolution is complete.
541    fn complete_resolution<I, CC, BC>(
542        &mut self,
543        core_ctx: &mut CC,
544        bindings_ctx: &mut BC,
545        link_address: UnicastAddr<D::Address>,
546    ) where
547        I: Ip,
548        D: LinkDevice,
549        BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
550        CC: NudSenderContext<I, D, BC>,
551    {
552        let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
553
554        // Send out pending packets while holding the NUD lock to prevent a potential
555        // ordering violation.
556        //
557        // If we drop the NUD lock before sending out these queued packets, another
558        // thread could take the NUD lock, observe that neighbor resolution is complete,
559        // and send a packet *before* these pending packets are sent out, resulting in
560        // out-of-order transmission to the device.
561        for (body, meta) in pending_frames.drain(..) {
562            // Ignore any errors on sending the IP packet, because a failure at this point
563            // is not actionable for the caller: failing to send a previously-queued packet
564            // doesn't mean that updating the neighbor entry should fail.
565            core_ctx
566                .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
567                .unwrap_or_else(|err| {
568                    error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
569                })
570        }
571        for notifier in notifiers.drain(..) {
572            notifier.notify(Ok(link_address));
573        }
574    }
575}
576
577/// State associated with a reachable neighbor.
578#[derive(Debug, Derivative)]
579#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
580pub struct Reachable<D: LinkDevice, I: Instant> {
581    /// The resolved link address.
582    pub link_address: UnicastAddr<D::Address>,
583    /// The last confirmed instant.
584    pub last_confirmed_at: I,
585}
586
587/// State associated with a stale neighbor.
588#[derive(Debug, Derivative)]
589#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
590pub struct Stale<D: LinkDevice> {
591    /// The resolved link address.
592    pub link_address: UnicastAddr<D::Address>,
593}
594
595impl<D: LinkDevice> Stale<D> {
596    fn enter_delay<I, BC, DeviceId: Clone>(
597        &mut self,
598        bindings_ctx: &mut BC,
599        timers: &mut TimerHeap<I, BC>,
600        neighbor: SpecifiedAddr<I::Addr>,
601    ) -> Delay<D>
602    where
603        I: Ip,
604        BC: NudBindingsContext<I, D, DeviceId>,
605    {
606        let Self { link_address } = *self;
607
608        // Start a timer to transition into PROBE after DELAY_FIRST_PROBE seconds if no
609        // packets are sent to this neighbor.
610        timers.schedule_neighbor(
611            bindings_ctx,
612            DELAY_FIRST_PROBE_TIME,
613            neighbor,
614            NudEvent::DelayFirstProbe,
615        );
616
617        Delay { link_address }
618    }
619}
620
621/// State associated with a neighbor in delay state.
622#[derive(Debug, Derivative)]
623#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
624pub struct Delay<D: LinkDevice> {
625    /// The resolved link address.
626    pub link_address: UnicastAddr<D::Address>,
627}
628
629impl<D: LinkDevice> Delay<D> {
630    fn enter_probe<I, DeviceId, CC, BC>(
631        &mut self,
632        core_ctx: &mut CC,
633        bindings_ctx: &mut BC,
634        timers: &mut TimerHeap<I, BC>,
635        neighbor: SpecifiedAddr<I::Addr>,
636    ) -> Probe<D>
637    where
638        I: Ip,
639        DeviceId: StrongDeviceIdentifier,
640        BC: NudBindingsContext<I, D, DeviceId>,
641        CC: NudConfigContext<I>,
642    {
643        let Self { link_address } = *self;
644
645        // NB: transmission of a neighbor probe on entering PROBE (and subsequent
646        // retransmissions) is done by `handle_timer`, as it need not be done with the
647        // neighbor table lock held.
648        let retransmit_timeout = core_ctx.retransmit_timeout();
649        timers.schedule_neighbor(
650            bindings_ctx,
651            retransmit_timeout,
652            neighbor,
653            NudEvent::RetransmitUnicastProbe,
654        );
655
656        Probe {
657            link_address,
658            transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
659        }
660    }
661}
662
663#[derive(Debug, Derivative)]
664#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
665pub struct Probe<D: LinkDevice> {
666    link_address: UnicastAddr<D::Address>,
667    transmit_counter: Option<NonZeroU16>,
668}
669
670impl<D: LinkDevice> Probe<D> {
671    fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
672        &mut self,
673        core_ctx: &mut CC,
674        bindings_ctx: &mut BC,
675        timers: &mut TimerHeap<I, BC>,
676        neighbor: SpecifiedAddr<I::Addr>,
677    ) -> bool
678    where
679        I: Ip,
680        DeviceId: StrongDeviceIdentifier,
681        BC: NudBindingsContext<I, D, DeviceId>,
682        CC: NudConfigContext<I>,
683    {
684        let Self { link_address: _, transmit_counter } = self;
685        schedule_timer_if_should_retransmit(
686            core_ctx,
687            bindings_ctx,
688            timers,
689            neighbor,
690            NudEvent::RetransmitUnicastProbe,
691            transmit_counter,
692        )
693    }
694
695    fn enter_unreachable<I, BC, DeviceId>(
696        &mut self,
697        bindings_ctx: &mut BC,
698        timers: &mut TimerHeap<I, BC>,
699        num_entries: usize,
700        gc_state: &mut GarbageCollectionState<BC::Instant>,
701    ) -> Unreachable<D>
702    where
703        I: Ip,
704        BC: NudBindingsContext<I, D, DeviceId>,
705        DeviceId: Clone,
706    {
707        // This entry is deemed discardable now that it is not in active use; schedule
708        // garbage collection for the neighbor table if we are currently over the
709        // maximum amount of entries.
710        timers.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
711
712        let Self { link_address, transmit_counter: _ } = self;
713        Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
714    }
715}
716
717#[derive(Debug, Derivative)]
718#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
719pub struct Unreachable<D: LinkDevice> {
720    link_address: UnicastAddr<D::Address>,
721    mode: UnreachableMode,
722}
723
724/// The dynamic neighbor state specific to the UNREACHABLE state as defined in
725/// [RFC 7048].
726///
727/// When a neighbor entry transitions to UNREACHABLE, the netstack will stop
728/// actively retransmitting probes if no packets are being sent to the neighbor.
729///
730/// If packets are sent through the neighbor, the netstack will continue to
731/// retransmit multicast probes, but with exponential backoff on the timer,
732/// based on the `BACKOFF_MULTIPLE` and clamped at `MAX_RETRANS_TIMER`.
733///
734/// [RFC 7048]: https://tools.ietf.org/html/rfc7048
735#[derive(Debug, Clone, Copy, Derivative)]
736#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
737pub(crate) enum UnreachableMode {
738    WaitingForPacketSend,
739    Backoff { probes_sent: NonZeroU32, packet_sent: bool },
740}
741
742impl UnreachableMode {
743    /// The amount of time to wait before transmitting another multicast probe
744    /// to the cached link address, based on how many probes we have transmitted
745    /// so far, as defined in [RFC 7048 section 4].
746    ///
747    /// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
748    fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
749    where
750        I: Ip,
751        CC: NudConfigContext<I>,
752    {
753        let probes_sent = match self {
754            UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
755            UnreachableMode::WaitingForPacketSend => {
756                panic!("cannot calculate exponential backoff in state {self:?}")
757            }
758        };
759        // TODO(https://fxbug.dev/42083368): vary this retransmit timeout by some random
760        // "jitter factor" to avoid synchronization of transmissions from different
761        // hosts.
762        (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
763            .min(MAX_RETRANS_TIMER)
764    }
765}
766
767impl<D: LinkDevice> Unreachable<D> {
768    fn handle_timer<I, DeviceId, CC, BC>(
769        &mut self,
770        core_ctx: &mut CC,
771        bindings_ctx: &mut BC,
772        timers: &mut TimerHeap<I, BC>,
773        device_id: &DeviceId,
774        neighbor: SpecifiedAddr<I::Addr>,
775    ) -> Option<TransmitProbe<UnicastAddr<D::Address>>>
776    where
777        I: Ip,
778        DeviceId: StrongDeviceIdentifier,
779        BC: NudBindingsContext<I, D, DeviceId>,
780        CC: NudConfigContext<I>,
781    {
782        let Self { link_address: _, mode } = self;
783        match mode {
784            UnreachableMode::WaitingForPacketSend => {
785                panic!(
786                    "timer should not have fired in UNREACHABLE while waiting for packet send; got \
787                    a retransmit multicast probe event for {neighbor} on {device_id:?}",
788                );
789            }
790            UnreachableMode::Backoff { probes_sent, packet_sent } => {
791                if *packet_sent {
792                    // It is all but guaranteed that we will never end up transmitting u32::MAX
793                    // probes, given the retransmit timeout backs off to MAX_RETRANS_TIMER (1 minute
794                    // by default), and u32::MAX minutes is over 8,000 years. By then we almost
795                    // certainly would have garbage-collected the neighbor entry.
796                    //
797                    // But we do a saturating add just to be safe.
798                    *probes_sent = probes_sent.saturating_add(1);
799                    *packet_sent = false;
800
801                    let duration = mode.next_backoff_retransmit_timeout(core_ctx);
802                    timers.schedule_neighbor(
803                        bindings_ctx,
804                        duration,
805                        neighbor,
806                        NudEvent::RetransmitMulticastProbe,
807                    );
808
809                    Some(TransmitProbe::Multicast)
810                } else {
811                    *mode = UnreachableMode::WaitingForPacketSend;
812
813                    None
814                }
815            }
816        }
817    }
818
819    /// Advance the UNREACHABLE state machine based on a packet being queued for
820    /// transmission.
821    ///
822    /// Returns whether a multicast neighbor probe should be sent as a result.
823    fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
824        &mut self,
825        core_ctx: &mut CC,
826        bindings_ctx: &mut BC,
827        timers: &mut TimerHeap<I, BC>,
828        neighbor: SpecifiedAddr<I::Addr>,
829    ) -> bool
830    where
831        I: Ip,
832        DeviceId: StrongDeviceIdentifier,
833        BC: NudBindingsContext<I, D, DeviceId>,
834        CC: NudConfigContext<I>,
835    {
836        let Self { link_address: _, mode } = self;
837        match mode {
838            UnreachableMode::WaitingForPacketSend => {
839                // We already transmitted MAX_MULTICAST_SOLICIT probes to the neighbor
840                // without confirmation, but now a packet is being sent to that neighbor, so
841                // we are resuming transmission of probes for as long as packets continue to
842                // be sent to the neighbor. Instead of retransmitting on a fixed timeout,
843                // use exponential backoff per [RFC 7048 section 4]:
844                //
845                //   If an implementation transmits more than MAX_UNICAST_SOLICIT/
846                //   MAX_MULTICAST_SOLICIT packets, then it SHOULD use the exponential
847                //   backoff of the retransmit timer.  This is to avoid any significant
848                //   load due to a steady background level of retransmissions from
849                //   implementations that retransmit a large number of Neighbor
850                //   Solicitations (NS) before discarding the NCE.
851                //
852                // [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
853                let probes_sent = NonZeroU32::new(1).unwrap();
854                *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
855
856                let duration = mode.next_backoff_retransmit_timeout(core_ctx);
857                timers.schedule_neighbor(
858                    bindings_ctx,
859                    duration,
860                    neighbor,
861                    NudEvent::RetransmitMulticastProbe,
862                );
863
864                // Transmit a multicast probe.
865                true
866            }
867            UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
868                // We are in the exponential backoff phase of sending probes. Make a note
869                // that a packet was sent since the last transmission so that we will send
870                // another when the timer fires.
871                *packet_sent = true;
872
873                false
874            }
875        }
876    }
877}
878
879#[derive(Debug, PartialEq, Eq, Error)]
880pub(crate) enum EnterProbeError {
881    #[error("link address is unknown")]
882    LinkAddressUnknown,
883}
884
885impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
886    fn to_event_state(&self) -> EventState<D::Address> {
887        match self {
888            NeighborState::Dynamic(dynamic_state) => {
889                EventState::Dynamic(dynamic_state.to_event_dynamic_state())
890            }
891            NeighborState::Static(addr) => EventState::Static(*addr),
892        }
893    }
894
895    /// Transitions this neighbor to the Probe state. If a state change
896    /// resulted, returns the link address that should be probed by the caller
897    /// once the neighbor table lock is released.
898    ///
899    /// NB: if `neighbor` is a static entry, this will cause it to become and
900    /// remain a dynamic entry.
901    ///
902    /// Returns an error if this neighbor is in the Incomplete state because the
903    /// link address must be known in order to trigger a unicast probe.
904    pub(crate) fn enter_probe<I, DeviceId, CC>(
905        &mut self,
906        core_ctx: &mut CC,
907        bindings_ctx: &mut BT,
908        timers: &mut TimerHeap<I, BT>,
909        neighbor: SpecifiedAddr<I::Addr>,
910        device_id: &DeviceId,
911    ) -> Result<Option<UnicastAddr<D::Address>>, EnterProbeError>
912    where
913        I: Ip,
914        DeviceId: StrongDeviceIdentifier,
915        BT: NudBindingsContext<I, D, DeviceId>,
916        CC: NudConfigContext<I>,
917    {
918        let link_addr = match self {
919            NeighborState::Dynamic(dynamic_state) => {
920                let link_addr = match dynamic_state {
921                    DynamicNeighborState::Reachable(Reachable { link_address, .. }) => {
922                        *link_address
923                    }
924                    DynamicNeighborState::Stale(Stale { link_address }) => *link_address,
925                    DynamicNeighborState::Delay(Delay { link_address }) => *link_address,
926                    DynamicNeighborState::Unreachable(Unreachable { link_address, .. }) => {
927                        *link_address
928                    }
929                    // The neighbor cannot be probed because its link address is
930                    // unknown.
931                    DynamicNeighborState::Incomplete(_) => {
932                        return Err(EnterProbeError::LinkAddressUnknown);
933                    }
934                    // The neighbor probe is already in progress; do not send
935                    // another probe.
936                    DynamicNeighborState::Probe(_) => return Ok(None),
937                };
938
939                // Cancel any existing timers in preparation for scheduling the
940                // retransmit timer.
941                dynamic_state.cancel_timer(bindings_ctx, timers, neighbor);
942                link_addr
943            }
944            NeighborState::Static(link_address) => *link_address,
945        };
946
947        // Schedule retransmit.
948        let retransmit_timeout = core_ctx.retransmit_timeout();
949        timers.schedule_neighbor(
950            bindings_ctx,
951            retransmit_timeout,
952            neighbor,
953            NudEvent::RetransmitUnicastProbe,
954        );
955
956        // Transition state.
957        *self = NeighborState::Dynamic(DynamicNeighborState::Probe(Probe {
958            link_address: link_addr,
959            transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
960        }));
961        let event_state = self.to_event_state();
962        bindings_ctx.on_event(Event::changed(
963            &device_id,
964            event_state,
965            neighbor,
966            bindings_ctx.now(),
967        ));
968        Ok(Some(link_addr))
969    }
970}
971
972impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
973    fn cancel_timer<I, DeviceId>(
974        &mut self,
975        bindings_ctx: &mut BC,
976        timers: &mut TimerHeap<I, BC>,
977        neighbor: SpecifiedAddr<I::Addr>,
978    ) where
979        I: Ip,
980        DeviceId: StrongDeviceIdentifier,
981        BC: NudBindingsContext<I, D, DeviceId>,
982    {
983        let expected_event = match self {
984            DynamicNeighborState::Incomplete(Incomplete {
985                transmit_counter: _,
986                pending_frames: _,
987                notifiers: _,
988                _marker,
989            }) => Some(NudEvent::RetransmitMulticastProbe),
990            DynamicNeighborState::Reachable(Reachable {
991                link_address: _,
992                last_confirmed_at: _,
993            }) => Some(NudEvent::ReachableTime),
994            DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
995            DynamicNeighborState::Delay(Delay { link_address: _ }) => {
996                Some(NudEvent::DelayFirstProbe)
997            }
998            DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
999                Some(NudEvent::RetransmitUnicastProbe)
1000            }
1001            DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
1002                // A timer should be scheduled iff a packet was recently sent to the neighbor
1003                // and we are retransmitting probes with exponential backoff.
1004                match mode {
1005                    UnreachableMode::WaitingForPacketSend => None,
1006                    UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
1007                        Some(NudEvent::RetransmitMulticastProbe)
1008                    }
1009                }
1010            }
1011        };
1012        assert_eq!(
1013            timers.cancel_neighbor(bindings_ctx, neighbor),
1014            expected_event,
1015            "neighbor {neighbor} ({self:?}) had unexpected timer installed"
1016        );
1017    }
1018
1019    fn cancel_timer_and_complete_resolution<I, CC>(
1020        mut self,
1021        core_ctx: &mut CC,
1022        bindings_ctx: &mut BC,
1023        timers: &mut TimerHeap<I, BC>,
1024        neighbor: SpecifiedAddr<I::Addr>,
1025        link_address: UnicastAddr<D::Address>,
1026    ) where
1027        I: Ip,
1028        BC: NudBindingsContext<I, D, CC::DeviceId>,
1029        CC: NudSenderContext<I, D, BC>,
1030    {
1031        self.cancel_timer(bindings_ctx, timers, neighbor);
1032
1033        match self {
1034            DynamicNeighborState::Incomplete(mut incomplete) => {
1035                incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
1036            }
1037            DynamicNeighborState::Reachable(_)
1038            | DynamicNeighborState::Stale(_)
1039            | DynamicNeighborState::Delay(_)
1040            | DynamicNeighborState::Probe(_)
1041            | DynamicNeighborState::Unreachable(_) => {}
1042        }
1043    }
1044
1045    fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
1046        match self {
1047            Self::Incomplete(_) => EventDynamicState::Incomplete,
1048            Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
1049                EventDynamicState::Reachable(*link_address)
1050            }
1051            Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
1052            Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
1053            Self::Probe(Probe { link_address, transmit_counter: _ }) => {
1054                EventDynamicState::Probe(*link_address)
1055            }
1056            Self::Unreachable(Unreachable { link_address, mode: _ }) => {
1057                EventDynamicState::Unreachable(*link_address)
1058            }
1059        }
1060    }
1061
1062    // Enters reachable state.
1063    fn enter_reachable<I, CC>(
1064        &mut self,
1065        core_ctx: &mut CC,
1066        bindings_ctx: &mut BC,
1067        timers: &mut TimerHeap<I, BC>,
1068        device_id: &CC::DeviceId,
1069        neighbor: SpecifiedAddr<I::Addr>,
1070        link_address: UnicastAddr<D::Address>,
1071    ) where
1072        I: Ip,
1073        BC: NudBindingsContext<I, D, CC::DeviceId>,
1074        CC: NudSenderContext<I, D, BC>,
1075    {
1076        // TODO(https://fxbug.dev/42075782): if the new state matches the current state,
1077        // update the link address as necessary, but do not cancel + reschedule timers.
1078        let now = bindings_ctx.now();
1079        match self {
1080            // If this neighbor entry is already in REACHABLE, rather than proactively
1081            // rescheduling the timer (which can be a relatively expensive operation
1082            // especially in the hot path), simply update `last_confirmed_at` so that when
1083            // the timer does eventually fire, we can reschedule it accordingly.
1084            DynamicNeighborState::Reachable(Reachable {
1085                link_address: current,
1086                last_confirmed_at,
1087            }) if *current == link_address => {
1088                *last_confirmed_at = now;
1089                return;
1090            }
1091            DynamicNeighborState::Incomplete(_)
1092            | DynamicNeighborState::Reachable(_)
1093            | DynamicNeighborState::Stale(_)
1094            | DynamicNeighborState::Delay(_)
1095            | DynamicNeighborState::Probe(_)
1096            | DynamicNeighborState::Unreachable(_) => {}
1097        }
1098        let previous = core::mem::replace(
1099            self,
1100            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1101        );
1102        let event_dynamic_state = self.to_event_dynamic_state();
1103        debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1104        let event_state = EventState::Dynamic(event_dynamic_state);
1105        bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1106        previous.cancel_timer_and_complete_resolution(
1107            core_ctx,
1108            bindings_ctx,
1109            timers,
1110            neighbor,
1111            link_address,
1112        );
1113        timers.schedule_neighbor(
1114            bindings_ctx,
1115            core_ctx.base_reachable_time(),
1116            neighbor,
1117            NudEvent::ReachableTime,
1118        );
1119    }
1120
1121    // Enters the Stale state.
1122    //
1123    // # Panics
1124    //
1125    // Panics if `self` is already in Stale with a link address equal to
1126    // `link_address`, i.e. this function should only be called when state
1127    // actually changes.
1128    fn enter_stale<I, CC>(
1129        &mut self,
1130        core_ctx: &mut CC,
1131        bindings_ctx: &mut BC,
1132        timers: &mut TimerHeap<I, BC>,
1133        device_id: &CC::DeviceId,
1134        neighbor: SpecifiedAddr<I::Addr>,
1135        link_address: UnicastAddr<D::Address>,
1136        num_entries: usize,
1137        gc_state: &mut GarbageCollectionState<BC::Instant>,
1138    ) where
1139        I: Ip,
1140        BC: NudBindingsContext<I, D, CC::DeviceId>,
1141        CC: NudSenderContext<I, D, BC>,
1142    {
1143        // TODO(https://fxbug.dev/42075782): if the new state matches the current state,
1144        // update the link address as necessary, but do not cancel + reschedule timers.
1145        let previous =
1146            core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1147        let event_dynamic_state = self.to_event_dynamic_state();
1148        debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1149        let event_state = EventState::Dynamic(event_dynamic_state);
1150        bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1151        previous.cancel_timer_and_complete_resolution(
1152            core_ctx,
1153            bindings_ctx,
1154            timers,
1155            neighbor,
1156            link_address,
1157        );
1158
1159        // This entry is deemed discardable now that it is not in active use; schedule
1160        // garbage collection for the neighbor table if we are currently over the
1161        // maximum amount of entries.
1162        timers.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
1163
1164        // Stale entries don't do anything until an outgoing packet is queued for
1165        // transmission.
1166    }
1167
1168    /// Resolve the cached link address for this neighbor entry, or return an
1169    /// observer for an unresolved neighbor, and advance the NUD state machine
1170    /// accordingly (as if a packet had been sent to the neighbor).
1171    ///
1172    /// Also returns whether a multicast neighbor probe should be sent as a result.
1173    fn resolve_link_addr<I, DeviceId, CC>(
1174        &mut self,
1175        core_ctx: &mut CC,
1176        bindings_ctx: &mut BC,
1177        timers: &mut TimerHeap<I, BC>,
1178        device_id: &DeviceId,
1179        neighbor: SpecifiedAddr<I::Addr>,
1180    ) -> (
1181        LinkResolutionResult<
1182            UnicastAddr<D::Address>,
1183            <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1184        >,
1185        bool,
1186    )
1187    where
1188        I: Ip,
1189        DeviceId: StrongDeviceIdentifier,
1190        BC: NudBindingsContext<I, D, DeviceId>,
1191        CC: NudConfigContext<I>,
1192    {
1193        match self {
1194            DynamicNeighborState::Incomplete(Incomplete {
1195                notifiers,
1196                transmit_counter: _,
1197                pending_frames: _,
1198                _marker,
1199            }) => {
1200                let (notifier, observer) = BC::Notifier::new();
1201                notifiers.push(notifier);
1202
1203                (LinkResolutionResult::Pending(observer), false)
1204            }
1205            DynamicNeighborState::Stale(entry) => {
1206                // Advance the state machine as if a packet had been sent to this neighbor.
1207                //
1208                // This is not required by the RFC, and it may result in neighbor probes going
1209                // out for this neighbor that would not have otherwise (the only other way a
1210                // STALE entry moves to DELAY is due to a packet being sent to it). However,
1211                // sending neighbor probes to confirm reachability is likely to be useful given
1212                // a client is attempting to resolve this neighbor. Additionally, this maintains
1213                // consistency with Netstack2's behavior.
1214                let delay @ Delay { link_address } =
1215                    entry.enter_delay(bindings_ctx, timers, neighbor);
1216                *self = DynamicNeighborState::Delay(delay);
1217                let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1218                bindings_ctx.on_event(Event::changed(
1219                    device_id,
1220                    event_state,
1221                    neighbor,
1222                    bindings_ctx.now(),
1223                ));
1224
1225                (LinkResolutionResult::Resolved(link_address), false)
1226            }
1227            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1228            | DynamicNeighborState::Delay(Delay { link_address })
1229            | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1230                (LinkResolutionResult::Resolved(*link_address), false)
1231            }
1232            DynamicNeighborState::Unreachable(unreachable) => {
1233                let Unreachable { link_address, mode: _ } = unreachable;
1234                let link_address = *link_address;
1235
1236                // Advance the state machine as if a packet had been sent to this neighbor.
1237                let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1238                    core_ctx,
1239                    bindings_ctx,
1240                    timers,
1241                    neighbor,
1242                );
1243                (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1244            }
1245        }
1246    }
1247
1248    /// Handle a packet being queued for transmission: either queue it as a
1249    /// pending packet for an unresolved neighbor, or send it to the cached link
1250    /// address, and advance the NUD state machine accordingly.
1251    ///
1252    /// Returns whether a multicast neighbor probe should be sent as a result.
1253    fn handle_packet_queued_to_send<I, CC, S>(
1254        &mut self,
1255        core_ctx: &mut CC,
1256        bindings_ctx: &mut BC,
1257        timers: &mut TimerHeap<I, BC>,
1258        device_id: &CC::DeviceId,
1259        neighbor: SpecifiedAddr<I::Addr>,
1260        body: S,
1261        meta: BC::TxMetadata,
1262    ) -> Result<bool, SendFrameError<S>>
1263    where
1264        I: Ip,
1265        BC: NudBindingsContext<I, D, CC::DeviceId>,
1266        CC: NudSenderContext<I, D, BC>,
1267        S: NetworkSerializer,
1268        S::Buffer: BufferMut,
1269    {
1270        match self {
1271            DynamicNeighborState::Incomplete(incomplete) => {
1272                incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1273            }
1274            // Send the IP packet while holding the NUD lock to prevent a potential
1275            // ordering violation.
1276            //
1277            // If we drop the NUD lock before sending out this packet, another thread
1278            // could take the NUD lock and send a packet *before* this packet is sent
1279            // out, resulting in out-of-order transmission to the device.
1280            DynamicNeighborState::Stale(entry) => {
1281                // Per [RFC 4861 section 7.3.3]:
1282                //
1283                //   The first time a node sends a packet to a neighbor whose entry is
1284                //   STALE, the sender changes the state to DELAY and sets a timer to
1285                //   expire in DELAY_FIRST_PROBE_TIME seconds.
1286                //
1287                // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
1288                let delay @ Delay { link_address } =
1289                    entry.enter_delay(bindings_ctx, timers, neighbor);
1290                *self = DynamicNeighborState::Delay(delay);
1291                let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1292                bindings_ctx.on_event(Event::changed(
1293                    device_id,
1294                    event_state,
1295                    neighbor,
1296                    bindings_ctx.now(),
1297                ));
1298
1299                core_ctx.send_ip_packet_to_neighbor_link_addr(
1300                    bindings_ctx,
1301                    link_address,
1302                    body,
1303                    meta,
1304                )?;
1305
1306                Ok(false)
1307            }
1308            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1309            | DynamicNeighborState::Delay(Delay { link_address })
1310            | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1311                core_ctx.send_ip_packet_to_neighbor_link_addr(
1312                    bindings_ctx,
1313                    *link_address,
1314                    body,
1315                    meta,
1316                )?;
1317
1318                Ok(false)
1319            }
1320            DynamicNeighborState::Unreachable(unreachable) => {
1321                let Unreachable { link_address, mode: _ } = unreachable;
1322                core_ctx.send_ip_packet_to_neighbor_link_addr(
1323                    bindings_ctx,
1324                    *link_address,
1325                    body,
1326                    meta,
1327                )?;
1328
1329                let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1330                    core_ctx,
1331                    bindings_ctx,
1332                    timers,
1333                    neighbor,
1334                );
1335                Ok(do_multicast_solicit)
1336            }
1337        }
1338    }
1339
1340    fn handle_probe<I, CC>(
1341        &mut self,
1342        core_ctx: &mut CC,
1343        bindings_ctx: &mut BC,
1344        timers: &mut TimerHeap<I, BC>,
1345        device_id: &CC::DeviceId,
1346        neighbor: SpecifiedAddr<I::Addr>,
1347        incoming_link_address: UnicastAddr<D::Address>,
1348        num_entries: usize,
1349        gc_state: &mut GarbageCollectionState<BC::Instant>,
1350    ) where
1351        I: Ip,
1352        BC: NudBindingsContext<I, D, CC::DeviceId>,
1353        CC: NudSenderContext<I, D, BC>,
1354    {
1355        // Per [RFC 4861 section 7.2.3] ("Receipt of Neighbor Solicitations"):
1356        //
1357        //   If an entry already exists, and the cached link-layer address
1358        //   differs from the one in the received Source Link-Layer option, the
1359        //   cached address should be replaced by the received address, and the
1360        //   entry's reachability state MUST be set to STALE.
1361        //
1362        // [RFC 4861 section 7.2.3]: https://tools.ietf.org/html/rfc4861#section-7.2.3
1363        let transition_to_stale = match self {
1364            DynamicNeighborState::Incomplete(_) => true,
1365            DynamicNeighborState::Reachable(Reachable {
1366                link_address: current,
1367                last_confirmed_at: _,
1368            })
1369            | DynamicNeighborState::Stale(Stale { link_address: current })
1370            | DynamicNeighborState::Delay(Delay { link_address: current })
1371            | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1372            | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1373                current != &incoming_link_address
1374            }
1375        };
1376        if transition_to_stale {
1377            self.enter_stale(
1378                core_ctx,
1379                bindings_ctx,
1380                timers,
1381                device_id,
1382                neighbor,
1383                incoming_link_address,
1384                num_entries,
1385                gc_state,
1386            );
1387        }
1388    }
1389
1390    fn handle_confirmation<I, CC>(
1391        &mut self,
1392        core_ctx: &mut CC,
1393        bindings_ctx: &mut BC,
1394        timers: &mut TimerHeap<I, BC>,
1395        device_id: &CC::DeviceId,
1396        neighbor: SpecifiedAddr<I::Addr>,
1397        incoming_link_address: Option<UnicastAddr<D::Address>>,
1398        flags: ConfirmationFlags,
1399        num_entries: usize,
1400        gc_state: &mut GarbageCollectionState<BC::Instant>,
1401    ) where
1402        I: Ip,
1403        BC: NudBindingsContext<I, D, CC::DeviceId>,
1404        CC: NudSenderContext<I, D, BC>,
1405    {
1406        let ConfirmationFlags { solicited_flag, override_flag } = flags;
1407        enum NewState<A> {
1408            Reachable { link_address: A },
1409            Stale { link_address: A },
1410        }
1411
1412        let new_state = match self {
1413            DynamicNeighborState::Incomplete(Incomplete {
1414                transmit_counter: _,
1415                pending_frames: _,
1416                notifiers: _,
1417                _marker,
1418            }) => {
1419                // Per RFC 4861 section 7.2.5:
1420                //
1421                //   If the advertisement's Solicited flag is set, the state of the
1422                //   entry is set to REACHABLE; otherwise, it is set to STALE.
1423                //
1424                //   Note that the Override flag is ignored if the entry is in the
1425                //   INCOMPLETE state.
1426                //
1427                // Also note that if the target link-layer address was not specified in this
1428                // neighbor confirmation, we ignore the confirmation: there is nothing we can do
1429                // since we don't have a cached link-layer address.
1430                incoming_link_address.map(|link_address| {
1431                    if solicited_flag {
1432                        NewState::Reachable { link_address }
1433                    } else {
1434                        NewState::Stale { link_address }
1435                    }
1436                })
1437            }
1438
1439            // Ignore duplicate neighbor confirmations that arrive less than
1440            // `override_lock_time()` from the previous one. This matches Linux
1441            // behavior. See https://fxbug.dev/490161899 .
1442            DynamicNeighborState::Reachable(Reachable {
1443                link_address: current,
1444                last_confirmed_at,
1445            }) if !core_ctx.override_lock_time().is_zero()
1446                && solicited_flag
1447                && bindings_ctx.now().saturating_duration_since(*last_confirmed_at)
1448                    < core_ctx.override_lock_time() =>
1449            {
1450                // If the link address matches the cached address (or is omitted, as
1451                // permitted in unicast NDP advertisements per RFC 4861 section 4.4),
1452                // this is a harmless duplicate confirmation and is logged at debug level.
1453                // If the link address differs, it may indicate a conflict or spoofing attempt,
1454                // so log at warn level.
1455                if incoming_link_address.as_ref().is_some_and(|addr| addr != current) {
1456                    warn!(
1457                        "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}. \
1458                        New address {:?}",
1459                        neighbor, current, incoming_link_address
1460                    );
1461                } else {
1462                    debug!(
1463                        "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}. \
1464                        New address {:?}",
1465                        neighbor, current, incoming_link_address
1466                    );
1467                }
1468                None
1469            }
1470
1471            DynamicNeighborState::Reachable(Reachable {
1472                link_address: current,
1473                last_confirmed_at: _,
1474            })
1475            | DynamicNeighborState::Stale(Stale { link_address: current })
1476            | DynamicNeighborState::Delay(Delay { link_address: current })
1477            | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1478            | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1479                // Per RFC 4861 section 4.4:
1480                //
1481                //    The link-layer address for the target, i.e., the
1482                //    sender of the advertisement ... MUST be
1483                //    included on link layers that have addresses when
1484                //    responding to multicast solicitations.  When
1485                //    responding to a unicast Neighbor Solicitation this
1486                //    option SHOULD be included.
1487                //
1488                //    ... When responding to unicast
1489                //    solicitations, the option can be omitted since the
1490                //    sender of the solicitation has the correct link-
1491                //    layer address; otherwise, it would not be able to
1492                //    send the unicast solicitation in the first place.
1493                //
1494                // Because neighbors may choose to omit the target link-layer address option
1495                // from neighbor confirmations, we must be tolerant of its absence. In the case
1496                // of absence, we use the cached link-layer address if one is available.
1497                let updated_link_address = incoming_link_address
1498                    .and_then(|link_address| (*current != link_address).then_some(link_address));
1499
1500                match (solicited_flag, updated_link_address, override_flag) {
1501                    // Per RFC 4861 section 7.2.5:
1502                    //
1503                    //   If [either] the Override flag is set, or the supplied link-layer address is
1504                    //   the same as that in the cache, [and] ... the Solicited flag is set, the
1505                    //   entry MUST be set to REACHABLE.
1506                    (true, _, true) | (true, None, _) => Some(NewState::Reachable {
1507                        link_address: incoming_link_address.unwrap_or(*current),
1508                    }),
1509                    // Per RFC 4861 section 7.2.5:
1510                    //
1511                    //   If the Override flag is clear and the supplied link-layer address differs
1512                    //   from that in the cache, then one of two actions takes place:
1513                    //
1514                    //    a. If the state of the entry is REACHABLE, set it to STALE, but do not
1515                    //       update the entry in any other way.
1516                    //    b. Otherwise, the received advertisement should be ignored and MUST NOT
1517                    //       update the cache.
1518                    (_, Some(_), false) => match self {
1519                        // NB: do not update the link address.
1520                        DynamicNeighborState::Reachable(Reachable {
1521                            link_address: current,
1522                            last_confirmed_at: _,
1523                        }) => Some(NewState::Stale { link_address: *current }),
1524                        // Ignore the advertisement and do not update the cache.
1525                        DynamicNeighborState::Stale(_)
1526                        | DynamicNeighborState::Delay(_)
1527                        | DynamicNeighborState::Probe(_)
1528                        | DynamicNeighborState::Unreachable(_) => None,
1529                        // The INCOMPLETE state was already handled in the outer match.
1530                        DynamicNeighborState::Incomplete(_) => unreachable!(),
1531                    },
1532                    // Per RFC 4861 section 7.2.5:
1533                    //
1534                    //   If the Override flag is set [and] ... the Solicited flag is zero and the
1535                    //   link-layer address was updated with a different address, the state MUST be
1536                    //   set to STALE.
1537                    (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1538                    // Per RFC 4861 section 7.2.5:
1539                    //
1540                    //   There is no need to update the state for unsolicited advertisements that do
1541                    //   not change the contents of the cache.
1542                    (false, None, _) => None,
1543                }
1544            }
1545        };
1546        match new_state {
1547            Some(NewState::Reachable { link_address }) => self.enter_reachable(
1548                core_ctx,
1549                bindings_ctx,
1550                timers,
1551                device_id,
1552                neighbor,
1553                link_address,
1554            ),
1555            Some(NewState::Stale { link_address }) => self.enter_stale(
1556                core_ctx,
1557                bindings_ctx,
1558                timers,
1559                device_id,
1560                neighbor,
1561                link_address,
1562                num_entries,
1563                gc_state,
1564            ),
1565            None => {}
1566        }
1567    }
1568}
1569
1570#[cfg(any(test, feature = "testutils"))]
1571pub(crate) mod testutil {
1572    use super::*;
1573
1574    use alloc::sync::Arc;
1575
1576    use netstack3_base::sync::Mutex;
1577    use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1578
1579    /// Asserts that `device_id`'s `neighbor` resolved to `expected_link_addr`.
1580    pub fn assert_dynamic_neighbor_with_addr<
1581        I: Ip,
1582        D: LinkDevice,
1583        BC: NudBindingsContext<I, D, CC::DeviceId>,
1584        CC: NudContext<I, D, BC>,
1585    >(
1586        core_ctx: &mut CC,
1587        device_id: CC::DeviceId,
1588        neighbor: SpecifiedAddr<I::Addr>,
1589        expected_link_addr: UnicastAddr<D::Address>,
1590    ) {
1591        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1592            assert_matches!(
1593                neighbors.get(&neighbor),
1594                Some(NeighborState::Dynamic(
1595                    DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1596                    | DynamicNeighborState::Stale(Stale{ link_address })
1597                )) => {
1598                    assert_eq!(link_address, &expected_link_addr)
1599                }
1600            )
1601        })
1602    }
1603
1604    /// Asserts that the `device_id`'s `neighbor` is at `expected_state`.
1605    pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1606        core_ctx: &mut CC,
1607        device_id: CC::DeviceId,
1608        neighbor: SpecifiedAddr<I::Addr>,
1609        expected_state: DynamicNeighborState<D, BC>,
1610    ) where
1611        I: Ip,
1612        D: LinkDevice + PartialEq,
1613        BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1614        CC: NudContext<I, D, BC>,
1615    {
1616        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1617            assert_matches!(
1618                neighbors.get(&neighbor),
1619                Some(NeighborState::Dynamic(state)) => {
1620                    assert_eq!(state, &expected_state)
1621                }
1622            )
1623        })
1624    }
1625
1626    /// Asserts that `device_id`'s `neighbor` doesn't exist.
1627    pub fn assert_neighbor_unknown<
1628        I: Ip,
1629        D: LinkDevice,
1630        BC: NudBindingsContext<I, D, CC::DeviceId>,
1631        CC: NudContext<I, D, BC>,
1632    >(
1633        core_ctx: &mut CC,
1634        device_id: CC::DeviceId,
1635        neighbor: SpecifiedAddr<I::Addr>,
1636    ) {
1637        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1638            assert_matches!(neighbors.get(&neighbor), None)
1639        })
1640    }
1641
1642    impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1643        for FakeBindingsCtx<Id, Event, State, FrameMeta>
1644    {
1645        type Notifier = FakeLinkResolutionNotifier<D>;
1646    }
1647
1648    /// A fake implementation of [`LinkResolutionNotifier`].
1649    #[derive(Debug)]
1650    pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1651        Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>,
1652    );
1653
1654    impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1655        type Observer =
1656            Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>;
1657
1658        fn new() -> (Self, Self::Observer) {
1659            let inner = Arc::new(Mutex::new(None));
1660            (Self(inner.clone()), inner)
1661        }
1662
1663        fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>) {
1664            let Self(inner) = self;
1665            let mut inner = inner.lock();
1666            assert_eq!(*inner, None, "resolved link address was set more than once");
1667            *inner = Some(result);
1668        }
1669    }
1670
1671    impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1672        S: UseDelegateNudContext
1673    {
1674    }
1675    impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1676    where
1677        S: DelegateNudContext<I>,
1678    {
1679        type Delegate<T> = S::Delegate<T>;
1680    }
1681}
1682
1683#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1684enum NudEvent {
1685    RetransmitMulticastProbe,
1686    ReachableTime,
1687    DelayFirstProbe,
1688    RetransmitUnicastProbe,
1689}
1690
1691/// The timer ID for the NUD module.
1692#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1693#[generic_over_ip(I, Ip)]
1694pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1695    device_id: D,
1696    timer_type: NudTimerType,
1697    _marker: PhantomData<(I, L)>,
1698}
1699
1700#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1701enum NudTimerType {
1702    Neighbor,
1703    GarbageCollection,
1704}
1705
1706/// A wrapper for [`LocalTimerHeap`] that we can attach NUD helpers to.
1707#[derive(Debug)]
1708pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1709    gc: BT::Timer,
1710    neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1711}
1712
1713impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1714    fn new<
1715        DeviceId: WeakDeviceIdentifier,
1716        L: LinkDevice,
1717        CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1718    >(
1719        bindings_ctx: &mut BC,
1720        device_id: DeviceId,
1721    ) -> Self {
1722        Self {
1723            neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1724                bindings_ctx,
1725                NudTimerId {
1726                    device_id: device_id.clone(),
1727                    timer_type: NudTimerType::Neighbor,
1728                    _marker: PhantomData,
1729                },
1730            ),
1731            gc: CC::new_timer(
1732                bindings_ctx,
1733                NudTimerId {
1734                    device_id,
1735                    timer_type: NudTimerType::GarbageCollection,
1736                    _marker: PhantomData,
1737                },
1738            ),
1739        }
1740    }
1741
1742    fn schedule_neighbor(
1743        &mut self,
1744        bindings_ctx: &mut BC,
1745        after: NonZeroDuration,
1746        neighbor: SpecifiedAddr<I::Addr>,
1747        event: NudEvent,
1748    ) {
1749        let Self { neighbor: heap, gc: _ } = self;
1750        assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1751    }
1752
1753    fn schedule_neighbor_at(
1754        &mut self,
1755        bindings_ctx: &mut BC,
1756        at: BC::Instant,
1757        neighbor: SpecifiedAddr<I::Addr>,
1758        event: NudEvent,
1759    ) {
1760        let Self { neighbor: heap, gc: _ } = self;
1761        assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1762    }
1763
1764    /// Cancels a neighbor timer.
1765    fn cancel_neighbor(
1766        &mut self,
1767        bindings_ctx: &mut BC,
1768        neighbor: SpecifiedAddr<I::Addr>,
1769    ) -> Option<NudEvent> {
1770        let Self { neighbor: heap, gc: _ } = self;
1771        heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1772    }
1773
1774    fn pop_neighbor(
1775        &mut self,
1776        bindings_ctx: &mut BC,
1777    ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1778        let Self { neighbor: heap, gc: _ } = self;
1779        heap.pop(bindings_ctx)
1780    }
1781
1782    /// Schedules a garbage collection IFF we hit the entries threshold and it's
1783    /// not already scheduled.
1784    fn maybe_schedule_gc(
1785        &mut self,
1786        bindings_ctx: &mut BC,
1787        num_entries: usize,
1788        gc_state: &mut GarbageCollectionState<BC::Instant>,
1789    ) {
1790        let GarbageCollectionState { is_dirty, last_gc } = gc_state;
1791        *is_dirty = true;
1792        let Self { gc, neighbor: _ } = self;
1793        if num_entries > GC_THRESHOLD && bindings_ctx.scheduled_instant(gc).is_none() {
1794            let instant = if let Some(last_gc) = last_gc {
1795                last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1796            } else {
1797                bindings_ctx.now()
1798            };
1799            // Scheduling a timer requires a mutable borrow and we're
1800            // currently holding it exclusively. We just checked that the timer
1801            // is not scheduled, so this assertion always holds.
1802            assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1803        }
1804    }
1805
1806    fn cancel_gc(&mut self, bindings_ctx: &mut BC) {
1807        let Self { gc, neighbor: _ } = self;
1808        let _: Option<BC::Instant> = bindings_ctx.cancel_timer(gc);
1809    }
1810}
1811
1812/// State related to neighbor table garbage collection.
1813#[derive(Debug)]
1814pub struct GarbageCollectionState<Instant> {
1815    /// The last time garbage collection was run.
1816    last_gc: Option<Instant>,
1817    /// Whether the table contains potentially discardable entries (e.g. STALE
1818    /// or UNREACHABLE).
1819    is_dirty: bool,
1820}
1821
1822/// NUD module per-device state.
1823#[derive(Debug)]
1824pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1825    // TODO(https://fxbug.dev/42076887): Key neighbors by `UnicastAddr`.
1826    neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1827    gc_state: GarbageCollectionState<BT::Instant>,
1828    timer_heap: TimerHeap<I, BT>,
1829}
1830
1831impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1832    /// Returns current neighbors.
1833    #[cfg(any(test, feature = "testutils"))]
1834    pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1835        &self.neighbors
1836    }
1837}
1838
1839impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1840    /// Constructs a new `NudState` for `device_id`.
1841    pub fn new<
1842        DeviceId: WeakDeviceIdentifier,
1843        CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1844    >(
1845        bindings_ctx: &mut BC,
1846        device_id: DeviceId,
1847    ) -> Self {
1848        Self {
1849            neighbors: Default::default(),
1850            gc_state: GarbageCollectionState { last_gc: None, is_dirty: false },
1851            timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1852        }
1853    }
1854}
1855
1856/// The bindings context for NUD.
1857pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1858    TimerContext
1859    + LinkResolutionContext<D>
1860    + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1861    + NudBindingsTypes<D>
1862{
1863}
1864
1865impl<
1866    I: Ip,
1867    D: LinkDevice,
1868    DeviceId,
1869    BC: TimerContext
1870        + LinkResolutionContext<D>
1871        + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1872        + NudBindingsTypes<D>,
1873> NudBindingsContext<I, D, DeviceId> for BC
1874{
1875}
1876
1877/// A marker trait for types provided by bindings to NUD.
1878pub trait NudBindingsTypes<D: LinkDevice>:
1879    LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1880{
1881}
1882
1883impl<BT, D> NudBindingsTypes<D> for BT
1884where
1885    D: LinkDevice,
1886    BT: LinkResolutionContext<D>
1887        + InstantBindingsTypes
1888        + TimerBindingsTypes
1889        + TxMetadataBindingsTypes,
1890{
1891}
1892
1893/// An execution context that allows creating link resolution notifiers.
1894pub trait LinkResolutionContext<D: LinkDevice> {
1895    /// A notifier held by core that can be used to inform interested parties of
1896    /// the result of link address resolution.
1897    type Notifier: LinkResolutionNotifier<D>;
1898}
1899
1900/// A notifier held by core that can be used to inform interested parties of the
1901/// result of link address resolution.
1902pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1903    /// The corresponding observer that can be used to observe the result of
1904    /// link address resolution.
1905    type Observer;
1906
1907    /// Create a connected (notifier, observer) pair.
1908    fn new() -> (Self, Self::Observer);
1909
1910    /// Signal to Bindings that link address resolution has completed for a
1911    /// neighbor.
1912    fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>);
1913}
1914
1915/// The execution context for NUD for a link device.
1916pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1917    /// The inner configuration context.
1918    type ConfigCtx<'a>: NudConfigContext<I>;
1919    /// The inner send context.
1920    type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1921
1922    /// Calls the function with a mutable reference to the NUD state and the
1923    /// core sender context.
1924    fn with_nud_state_mut_and_sender_ctx<
1925        O,
1926        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1927    >(
1928        &mut self,
1929        device_id: &Self::DeviceId,
1930        cb: F,
1931    ) -> O;
1932
1933    /// Calls the function with a mutable reference to the NUD state and NUD
1934    /// configuration for the device.
1935    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1936        &mut self,
1937        device_id: &Self::DeviceId,
1938        cb: F,
1939    ) -> O;
1940
1941    /// Calls the function with an immutable reference to the NUD state.
1942    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1943        &mut self,
1944        device_id: &Self::DeviceId,
1945        cb: F,
1946    ) -> O;
1947
1948    /// Sends a neighbor probe/solicitation message.
1949    ///
1950    /// If `remote_link_addr` is provided, the message will be unicasted to that
1951    /// address; if it is `None`, the message will be multicast.
1952    fn send_neighbor_solicitation(
1953        &mut self,
1954        bindings_ctx: &mut BC,
1955        device_id: &Self::DeviceId,
1956        lookup_addr: SpecifiedAddr<I::Addr>,
1957        remote_link_addr: Option<UnicastAddr<D::Address>>,
1958    );
1959}
1960
1961/// A marker trait to enable the blanket impl of [`NudContext`] for types
1962/// implementing [`DelegateNudContext`].
1963pub trait UseDelegateNudContext {}
1964
1965/// Enables a blanket implementation of [`NudContext`] via delegate that can
1966/// wrap a mutable reference of `Self`.
1967///
1968/// The `UseDelegateNudContext` requirement here is steering users to to the
1969/// right thing to enable the blanket implementation.
1970pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1971    /// The delegate that implements [`NudContext`].
1972    type Delegate<T>: ref_cast::RefCast<From = T>;
1973    /// Wraps self into a mutable delegate reference.
1974    fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1975        <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1976    }
1977}
1978
1979impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1980where
1981    I: Ip,
1982    D: LinkDevice,
1983    BC: NudBindingsTypes<D>,
1984    CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1985        // This seems redundant with `DelegateNudContext` but it is required to
1986        // get the compiler happy.
1987        + UseDelegateNudContext
1988        + DeviceIdContext<D>,
1989{
1990    type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1991    type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1992    fn with_nud_state_mut_and_sender_ctx<
1993        O,
1994        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1995    >(
1996        &mut self,
1997        device_id: &Self::DeviceId,
1998        cb: F,
1999    ) -> O {
2000        self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
2001    }
2002
2003    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
2004        &mut self,
2005        device_id: &Self::DeviceId,
2006        cb: F,
2007    ) -> O {
2008        self.wrap().with_nud_state_mut(device_id, cb)
2009    }
2010    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
2011        &mut self,
2012        device_id: &Self::DeviceId,
2013        cb: F,
2014    ) -> O {
2015        self.wrap().with_nud_state(device_id, cb)
2016    }
2017    fn send_neighbor_solicitation(
2018        &mut self,
2019        bindings_ctx: &mut BC,
2020        device_id: &Self::DeviceId,
2021        lookup_addr: SpecifiedAddr<I::Addr>,
2022        remote_link_addr: Option<UnicastAddr<D::Address>>,
2023    ) {
2024        self.wrap().send_neighbor_solicitation(
2025            bindings_ctx,
2026            device_id,
2027            lookup_addr,
2028            remote_link_addr,
2029        )
2030    }
2031}
2032
2033/// IP extension trait to support [`NudIcmpContext`].
2034pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2035    /// IP packet metadata needed when sending ICMP destination unreachable
2036    /// errors as a result of link-layer address resolution failure.
2037    type Metadata;
2038
2039    /// Extracts IP-version specific metadata from `packet`.
2040    fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2041}
2042
2043impl NudIcmpIpExt for Ipv4 {
2044    type Metadata = Ipv4FragmentType;
2045
2046    fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2047        packet.fragment_type()
2048    }
2049}
2050
2051impl NudIcmpIpExt for Ipv6 {
2052    type Metadata = ();
2053
2054    fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2055        ()
2056    }
2057}
2058
2059/// The execution context which allows sending ICMP destination unreachable
2060/// errors, which needs to happen when address resolution fails.
2061pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2062    /// Send an ICMP destination unreachable error to `original_src_ip` as
2063    /// a result of `frame` being unable to be sent/forwarded due to link
2064    /// layer address resolution failure.
2065    ///
2066    /// `original_src_ip`, `original_dst_ip`, and `header_len` are all IP
2067    /// header fields from `frame`.
2068    fn send_icmp_dest_unreachable(
2069        &mut self,
2070        bindings_ctx: &mut BC,
2071        frame: Buf<Vec<u8>>,
2072        device_id: Option<&Self::DeviceId>,
2073        original_src_ip: SocketIpAddr<I::Addr>,
2074        original_dst_ip: SocketIpAddr<I::Addr>,
2075        header_len: usize,
2076        proto: I::Proto,
2077        metadata: I::Metadata,
2078    );
2079}
2080
2081/// NUD configurations.
2082#[derive(Clone, Debug)]
2083pub struct NudUserConfig {
2084    /// The maximum number of unicast solicitations as defined in [RFC 4861
2085    /// section 10].
2086    ///
2087    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2088    pub max_unicast_solicitations: NonZeroU16,
2089    /// The maximum number of multicast solicitations as defined in [RFC 4861
2090    /// section 10].
2091    ///
2092    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2093    pub max_multicast_solicitations: NonZeroU16,
2094    /// The base value used for computing the duration a neighbor is considered
2095    /// reachable after receiving a reachability confirmation as defined in
2096    /// [RFC 4861 section 6.3.2].
2097    ///
2098    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2099    pub base_reachable_time: NonZeroDuration,
2100    /// The time between retransmissions of neighbor probe messages to a neighbor
2101    /// when resolving the address or when probing the reachability of a neighbor
2102    /// as defined in [RFC 4861 section 6.3.2].
2103    ///
2104    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2105    pub retrans_timer: NonZeroDuration,
2106}
2107
2108impl Default for NudUserConfig {
2109    fn default() -> Self {
2110        NudUserConfig {
2111            max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2112            max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2113            base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2114            retrans_timer: DEFAULT_RETRANS_TIMER,
2115        }
2116    }
2117}
2118
2119/// An update structure for [`NudUserConfig`].
2120///
2121/// Only fields with variant `Some` are updated.
2122#[allow(missing_docs)]
2123#[derive(Clone, Debug, Eq, PartialEq, Default)]
2124pub struct NudUserConfigUpdate {
2125    pub max_unicast_solicitations: Option<NonZeroU16>,
2126    pub max_multicast_solicitations: Option<NonZeroU16>,
2127    pub base_reachable_time: Option<NonZeroDuration>,
2128    pub retrans_timer: Option<NonZeroDuration>,
2129}
2130
2131impl NudUserConfigUpdate {
2132    /// Applies the configuration returning a [`NudUserConfigUpdate`] with the
2133    /// changed fields populated.
2134    pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2135        fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2136            if let Some(opt) = opt.as_mut() {
2137                core::mem::swap(opt, target)
2138            }
2139        }
2140        let Self {
2141            max_unicast_solicitations,
2142            max_multicast_solicitations,
2143            base_reachable_time,
2144            retrans_timer,
2145        } = &mut self;
2146        swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2147        swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2148        swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2149        swap_if_set(retrans_timer, &mut config.retrans_timer);
2150
2151        self
2152    }
2153}
2154
2155/// The execution context for NUD that allows accessing NUD configuration (such
2156/// as timer durations) for a particular device.
2157pub trait NudConfigContext<I: Ip> {
2158    /// The amount of time between retransmissions of neighbor probe messages.
2159    ///
2160    /// This corresponds to the configurable per-interface `RetransTimer` value
2161    /// used in NUD as defined in [RFC 4861 section 6.3.2].
2162    ///
2163    /// [RFC 4861 section 6.3.2]: https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.2
2164    fn retransmit_timeout(&mut self) -> NonZeroDuration;
2165
2166    /// Calls the callback with an immutable reference to NUD configurations.
2167    fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2168
2169    /// Returns the maximum number of unicast solicitations.
2170    fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2171        self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2172            *max_unicast_solicitations
2173        })
2174    }
2175
2176    /// Returns the maximum number of multicast solicitations.
2177    fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2178        self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2179            *max_multicast_solicitations
2180        })
2181    }
2182
2183    /// Returns the base reachable time, the duration a neighbor is considered
2184    /// reachable after receiving a reachability confirmation.
2185    fn base_reachable_time(&mut self) -> NonZeroDuration {
2186        self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2187    }
2188
2189    /// Amount of time from the moment a host becomes reachable before the
2190    /// entry can overridden.
2191    fn override_lock_time(&mut self) -> Duration;
2192}
2193
2194/// The execution context for NUD for a link device that allows sending IP
2195/// packets to specific neighbors.
2196pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2197    NudConfigContext<I> + DeviceIdContext<D>
2198{
2199    /// Send an IP frame to the neighbor with the specified link address.
2200    fn send_ip_packet_to_neighbor_link_addr<S>(
2201        &mut self,
2202        bindings_ctx: &mut BC,
2203        neighbor_link_addr: UnicastAddr<D::Address>,
2204        body: S,
2205        meta: BC::TxMetadata,
2206    ) -> Result<(), SendFrameError<S>>
2207    where
2208        S: NetworkSerializer,
2209        S::Buffer: BufferMut;
2210}
2211
2212/// An implementation of NUD for the IP layer.
2213pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2214    /// Handles an incoming neighbor probe message.
2215    ///
2216    /// For IPv6, this can be an NDP Neighbor Solicitation or an NDP Router
2217    /// Advertisement message.
2218    fn handle_neighbor_probe(
2219        &mut self,
2220        bindings_ctx: &mut BC,
2221        device_id: &Self::DeviceId,
2222        neighbor: SpecifiedAddr<I::Addr>,
2223        link_addr: &[u8],
2224    );
2225
2226    /// Handles an incoming neighbor confirmation message.
2227    ///
2228    /// For IPv6, this can be an NDP Neighbor Advertisement.
2229    fn handle_neighbor_confirmation(
2230        &mut self,
2231        bindings_ctx: &mut BC,
2232        device_id: &Self::DeviceId,
2233        neighbor: SpecifiedAddr<I::Addr>,
2234        link_addr: Option<&[u8]>,
2235        flags: ConfirmationFlags,
2236    );
2237
2238    /// Clears the neighbor table.
2239    fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2240}
2241
2242/// Specifies the link-layer address of a neighbor.
2243#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2244pub enum LinkResolutionResult<A, Observer> {
2245    /// The destination is a known neighbor with the given link-layer address.
2246    Resolved(A),
2247    /// The destination is pending neighbor resolution.
2248    Pending(Observer),
2249}
2250
2251/// An implementation of NUD for a link device.
2252pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2253    /// Sets a dynamic neighbor's entry state to the specified values in
2254    /// response to the source packet.
2255    fn handle_neighbor_update(
2256        &mut self,
2257        bindings_ctx: &mut BC,
2258        device_id: &Self::DeviceId,
2259        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
2260        // disallow the address with all host bits equal to 0, and the
2261        // subnet broadcast addresses with all host bits equal to 1.
2262        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
2263        neighbor: SpecifiedAddr<I::Addr>,
2264        source: DynamicNeighborUpdateSource<D::Address>,
2265    );
2266
2267    /// Clears the neighbor table.
2268    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2269
2270    /// Send an IP packet to the neighbor.
2271    ///
2272    /// If the neighbor's link address is not known, link address resolution
2273    /// is performed.
2274    fn send_ip_packet_to_neighbor<S>(
2275        &mut self,
2276        bindings_ctx: &mut BC,
2277        device_id: &Self::DeviceId,
2278        neighbor: SpecifiedAddr<I::Addr>,
2279        body: S,
2280        meta: BC::TxMetadata,
2281    ) -> Result<(), SendFrameError<S>>
2282    where
2283        S: NetworkSerializer,
2284        S::Buffer: BufferMut;
2285}
2286
2287enum TransmitProbe<A> {
2288    Multicast,
2289    Unicast(A),
2290}
2291
2292impl<
2293    I: NudIcmpIpExt,
2294    D: LinkDevice,
2295    BC: NudBindingsContext<I, D, CC::DeviceId>,
2296    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2297> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2298{
2299    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2300        let Self { device_id, timer_type, _marker: PhantomData } = self;
2301        let Some(device_id) = device_id.upgrade() else {
2302            return;
2303        };
2304        match timer_type {
2305            NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2306            NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2307        }
2308    }
2309}
2310
2311fn handle_neighbor_timer<I, D, CC, BC>(
2312    core_ctx: &mut CC,
2313    bindings_ctx: &mut BC,
2314    device_id: CC::DeviceId,
2315) where
2316    I: NudIcmpIpExt,
2317    D: LinkDevice,
2318    BC: NudBindingsContext<I, D, CC::DeviceId>,
2319    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2320{
2321    enum Action<L, A, M> {
2322        TransmitProbe { probe: TransmitProbe<L>, to: A },
2323        SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2324    }
2325    let action = core_ctx.with_nud_state_mut(
2326        &device_id,
2327        |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2328            let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2329            let num_entries = neighbors.len();
2330            let mut entry = match neighbors.entry(lookup_addr) {
2331                Entry::Occupied(entry) => entry,
2332                Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2333            };
2334
2335            match entry.get_mut() {
2336                NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2337                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2338
2339                    if incomplete.schedule_timer_if_should_retransmit(
2340                        core_ctx,
2341                        bindings_ctx,
2342                        timer_heap,
2343                        lookup_addr,
2344                    ) {
2345                        Some(Action::TransmitProbe {
2346                            probe: TransmitProbe::Multicast,
2347                            to: lookup_addr,
2348                        })
2349                    } else {
2350                        // Failed to complete neighbor resolution and no more probes to send.
2351                        // Subsequent traffic to this neighbor will recreate the entry and restart
2352                        // address resolution.
2353                        //
2354                        // TODO(https://fxbug.dev/42082448): consider retaining this neighbor entry in
2355                        // a sentinel `Failed` state, equivalent to its having been discarded except
2356                        // for debugging/observability purposes.
2357                        debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2358                        let Incomplete {
2359                            transmit_counter: _,
2360                            ref mut pending_frames,
2361                            notifiers: _,
2362                            _marker,
2363                        } = assert_matches!(
2364                            entry.remove(),
2365                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2366                                => incomplete
2367                        );
2368                        let pending_frames = core::mem::take(pending_frames);
2369                        bindings_ctx.on_event(Event::removed(
2370                            &device_id,
2371                            lookup_addr,
2372                            bindings_ctx.now(),
2373                        ));
2374                        Some(Action::SendIcmpDestUnreachable(pending_frames))
2375                    }
2376                }
2377                NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2378                    assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2379
2380                    let Probe { link_address, transmit_counter: _ } = probe;
2381                    let link_address = *link_address;
2382                    if probe.schedule_timer_if_should_retransmit(
2383                        core_ctx,
2384                        bindings_ctx,
2385                        timer_heap,
2386                        lookup_addr,
2387                    ) {
2388                        Some(Action::TransmitProbe {
2389                            probe: TransmitProbe::Unicast(link_address),
2390                            to: lookup_addr,
2391                        })
2392                    } else {
2393                        let unreachable = probe.enter_unreachable(
2394                            bindings_ctx,
2395                            timer_heap,
2396                            num_entries,
2397                            gc_state,
2398                        );
2399                        *entry.get_mut() =
2400                            NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2401                        let event_state = entry.get_mut().to_event_state();
2402                        let event = Event::changed(
2403                            &device_id,
2404                            event_state,
2405                            lookup_addr,
2406                            bindings_ctx.now(),
2407                        );
2408                        bindings_ctx.on_event(event);
2409                        None
2410                    }
2411                }
2412                NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2413                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2414                    unreachable
2415                        .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2416                        .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2417                }
2418                NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2419                    link_address,
2420                    last_confirmed_at,
2421                })) => {
2422                    assert_eq!(event, NudEvent::ReachableTime);
2423                    let link_address = *link_address;
2424
2425                    let expiration =
2426                        last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2427                    if expiration > bindings_ctx.now() {
2428                        timer_heap.schedule_neighbor_at(
2429                            bindings_ctx,
2430                            expiration,
2431                            lookup_addr,
2432                            NudEvent::ReachableTime,
2433                        );
2434                    } else {
2435                        // Per [RFC 4861 section 7.3.3]:
2436                        //
2437                        //   When ReachableTime milliseconds have passed since receipt of the last
2438                        //   reachability confirmation for a neighbor, the Neighbor Cache entry's
2439                        //   state changes from REACHABLE to STALE.
2440                        //
2441                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2442                        *entry.get_mut() =
2443                            NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2444                                link_address,
2445                            }));
2446                        let event_state = entry.get_mut().to_event_state();
2447                        let event = Event::changed(
2448                            &device_id,
2449                            event_state,
2450                            lookup_addr,
2451                            bindings_ctx.now(),
2452                        );
2453                        bindings_ctx.on_event(event);
2454
2455                        // This entry is deemed discardable now that it is not in active use;
2456                        // schedule garbage collection for the neighbor table if we are currently
2457                        // over the maximum amount of entries.
2458                        timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
2459                    }
2460
2461                    None
2462                }
2463                NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2464                    assert_eq!(event, NudEvent::DelayFirstProbe);
2465
2466                    // Per [RFC 4861 section 7.3.3]:
2467                    //
2468                    //   If the entry is still in the DELAY state when the timer expires, the
2469                    //   entry's state changes to PROBE.
2470                    //
2471                    // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2472                    let probe @ Probe { link_address, transmit_counter: _ } =
2473                        delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2474                    *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2475                    let event_state = entry.get_mut().to_event_state();
2476                    bindings_ctx.on_event(Event::changed(
2477                        &device_id,
2478                        event_state,
2479                        lookup_addr,
2480                        bindings_ctx.now(),
2481                    ));
2482
2483                    Some(Action::TransmitProbe {
2484                        probe: TransmitProbe::Unicast(link_address),
2485                        to: lookup_addr,
2486                    })
2487                }
2488                state @ (NeighborState::Static(_)
2489                | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2490                    panic!("timer unexpectedly fired in state {state:?}")
2491                }
2492            }
2493        },
2494    );
2495
2496    match action {
2497        Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2498            for (mut frame, meta) in pending_frames.drain(..) {
2499                // This frame is being dropped from the pending NUD queue, we
2500                // can release its tx metadata.
2501                core::mem::drop(meta);
2502
2503                // TODO(https://fxbug.dev/323585811): Avoid needing to parse the packet to get
2504                // IP header fields by extracting them from the serializer passed into the NUD
2505                // layer and storing them alongside the pending frames instead.
2506                let Some((packet, original_src_ip, original_dst_ip)) = frame
2507                    .parse_mut::<I::Packet<_>>()
2508                    .map_err(|e| {
2509                        warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2510                    })
2511                    .ok()
2512                    .and_then(|packet| {
2513                        let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2514                        let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2515                        Some((packet, original_src_ip, original_dst_ip))
2516                    })
2517                    .or_else(|| {
2518                        core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2519                        None
2520                    })
2521                else {
2522                    continue;
2523                };
2524                let header_metadata = I::extract_metadata(&packet);
2525                let header_len = packet.parse_metadata().header_len();
2526                let proto = packet.proto();
2527                let metadata = packet.parse_metadata();
2528                core::mem::drop(packet);
2529                frame.undo_parse(metadata);
2530                core_ctx.send_icmp_dest_unreachable(
2531                    bindings_ctx,
2532                    frame,
2533                    // Provide the device ID if `original_src_ip`, the address the ICMP error
2534                    // is destined for, is link-local. Note that if this address is link-local,
2535                    // it should be an address assigned to one of our own interfaces, because the
2536                    // link-local subnet should always be on-link according to RFC 5942 Section 3:
2537                    //
2538                    //   The link-local prefix is effectively considered a permanent entry on the
2539                    //   Prefix List.
2540                    //
2541                    // Even if the link-local subnet is off-link, passing the device ID is never
2542                    // incorrect because link-local traffic will never be forwarded, and
2543                    // there is only ever one link and thus interface involved.
2544                    original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2545                    original_src_ip,
2546                    original_dst_ip,
2547                    header_len,
2548                    proto,
2549                    header_metadata,
2550                );
2551            }
2552        }
2553        Some(Action::TransmitProbe { probe, to }) => {
2554            let remote_link_addr = match probe {
2555                TransmitProbe::Multicast => None,
2556                TransmitProbe::Unicast(link_addr) => Some(link_addr),
2557            };
2558            core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2559        }
2560        None => {}
2561    }
2562}
2563
2564impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2565    NudHandler<I, D, BC> for CC
2566{
2567    fn handle_neighbor_update(
2568        &mut self,
2569        bindings_ctx: &mut BC,
2570        device_id: &CC::DeviceId,
2571        neighbor: SpecifiedAddr<I::Addr>,
2572        source: DynamicNeighborUpdateSource<D::Address>,
2573    ) {
2574        debug!("received neighbor {:?} from {}", source, neighbor);
2575        self.with_nud_state_mut_and_sender_ctx(
2576            device_id,
2577            |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2578                let num_entries = neighbors.len();
2579                match neighbors.get_mut(&neighbor) {
2580                    None => match source {
2581                        DynamicNeighborUpdateSource::Probe { link_address } => {
2582                            // Per [RFC 4861 section 7.2.3] ("Receipt of Neighbor Solicitations"):
2583                            //
2584                            //   If an entry does not already exist, the node SHOULD create a new
2585                            //   one and set its reachability state to STALE as specified in Section
2586                            //   7.3.3.
2587                            //
2588                            // [RFC 4861 section 7.2.3]: https://tools.ietf.org/html/rfc4861#section-7.2.3
2589                            let result = insert_new_entry(
2590                                neighbors,
2591                                gc_state,
2592                                timer_heap,
2593                                bindings_ctx,
2594                                device_id,
2595                                neighbor,
2596                                NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2597                                    link_address,
2598                                })),
2599                            );
2600                            match result {
2601                                Ok(_entry) => {}
2602                                Err(TableFullError { entry }) => {
2603                                    debug!("Neighbor table full; failed to insert {entry:?}");
2604                                    return;
2605                                }
2606                            }
2607
2608                            // This entry is not currently in active use; if we are currently over
2609                            // the maximum amount of entries, schedule garbage collection.
2610                            timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), gc_state);
2611                        }
2612                        // Per [RFC 4861 section 7.2.5] ("Receipt of Neighbor Advertisements"):
2613                        //
2614                        //   If no entry exists, the advertisement SHOULD be silently discarded.
2615                        //   There is no need to create an entry if none exists, since the
2616                        //   recipient has apparently not initiated any communication with the
2617                        //   target.
2618                        //
2619                        // [RFC 4861 section 7.2.5]: https://tools.ietf.org/html/rfc4861#section-7.2.5
2620                        DynamicNeighborUpdateSource::Confirmation { .. } => {}
2621                    },
2622                    Some(entry) => match entry {
2623                        NeighborState::Dynamic(e) => match source {
2624                            DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2625                                core_ctx,
2626                                bindings_ctx,
2627                                timer_heap,
2628                                device_id,
2629                                neighbor,
2630                                link_address,
2631                                num_entries,
2632                                gc_state,
2633                            ),
2634                            DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2635                                .handle_confirmation(
2636                                    core_ctx,
2637                                    bindings_ctx,
2638                                    timer_heap,
2639                                    device_id,
2640                                    neighbor,
2641                                    link_address,
2642                                    flags,
2643                                    num_entries,
2644                                    gc_state,
2645                                ),
2646                        },
2647                        NeighborState::Static(_) => {}
2648                    },
2649                }
2650            },
2651        );
2652    }
2653
2654    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2655        self.with_nud_state_mut(
2656            device_id,
2657            |NudState { neighbors, gc_state: _, timer_heap }, _config| {
2658                neighbors.drain().for_each(|(neighbor, state)| {
2659                    match state {
2660                        NeighborState::Dynamic(mut entry) => {
2661                            entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2662                        }
2663                        NeighborState::Static(_) => {}
2664                    }
2665                    bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2666                });
2667            },
2668        );
2669    }
2670
2671    fn send_ip_packet_to_neighbor<S>(
2672        &mut self,
2673        bindings_ctx: &mut BC,
2674        device_id: &Self::DeviceId,
2675        lookup_addr: SpecifiedAddr<I::Addr>,
2676        body: S,
2677        meta: BC::TxMetadata,
2678    ) -> Result<(), SendFrameError<S>>
2679    where
2680        S: NetworkSerializer,
2681        S::Buffer: BufferMut,
2682    {
2683        let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2684            device_id,
2685            |NudState { neighbors, gc_state, timer_heap },
2686             core_ctx|
2687             -> Result<_, SendFrameError<S>> {
2688                match neighbors.get_mut(&lookup_addr) {
2689                    None => {
2690                        let incomplete =
2691                            Incomplete::new(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2692                        let result = insert_new_entry(
2693                            neighbors,
2694                            gc_state,
2695                            timer_heap,
2696                            bindings_ctx,
2697                            device_id,
2698                            lookup_addr,
2699                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2700                        );
2701                        match result {
2702                            Err(TableFullError { entry }) => {
2703                                debug!("Neighbor table full; failed to insert {entry:?}");
2704                                return Err(ErrorAndSerializer {
2705                                    serializer: body,
2706                                    error: SendFrameErrorReason::AddressResolutionFailed,
2707                                });
2708                            }
2709                            Ok(mut entry) => {
2710                                let dynamic = assert_matches!(
2711                                    entry.get_mut(),
2712                                    NeighborState::Dynamic(d) => d,
2713                                    "newly inserted entry must still be dynamic"
2714                                );
2715                                let incomplete = assert_matches!(
2716                                    dynamic,
2717                                    DynamicNeighborState::Incomplete(i) => i,
2718                                    "newly inserted entry must still be incomplete"
2719                                );
2720                                // Queue the packet and unwind on failure.
2721                                match incomplete.queue_packet(body, meta) {
2722                                    Ok(()) => Ok(true),
2723                                    Err(e) => {
2724                                        dynamic.cancel_timer(bindings_ctx, timer_heap, lookup_addr);
2725                                        let _entry = entry.remove();
2726                                        Err(e.err_into())
2727                                    }
2728                                }
2729                            }
2730                        }
2731                    }
2732                    Some(entry) => {
2733                        match entry {
2734                            NeighborState::Static(link_address) => {
2735                                // Send the IP packet while holding the NUD lock to prevent a
2736                                // potential ordering violation.
2737                                //
2738                                // If we drop the NUD lock before sending out this packet, another
2739                                // thread could take the NUD lock and send a packet *before* this
2740                                // packet is sent out, resulting in out-of-order transmission to the
2741                                // device.
2742                                core_ctx.send_ip_packet_to_neighbor_link_addr(
2743                                    bindings_ctx,
2744                                    *link_address,
2745                                    body,
2746                                    meta,
2747                                )?;
2748
2749                                Ok(false)
2750                            }
2751                            NeighborState::Dynamic(e) => {
2752                                let do_multicast_solicit = e.handle_packet_queued_to_send(
2753                                    core_ctx,
2754                                    bindings_ctx,
2755                                    timer_heap,
2756                                    device_id,
2757                                    lookup_addr,
2758                                    body,
2759                                    meta,
2760                                )?;
2761
2762                                Ok(do_multicast_solicit)
2763                            }
2764                        }
2765                    }
2766                }
2767            },
2768        )?;
2769
2770        if do_multicast_solicit {
2771            self.send_neighbor_solicitation(
2772                bindings_ctx,
2773                &device_id,
2774                lookup_addr,
2775                /* multicast */ None,
2776            );
2777        }
2778
2779        Ok(())
2780    }
2781}
2782
2783pub(crate) struct TableFullError<E> {
2784    entry: E,
2785}
2786
2787/// Attempts to insert a new entry into the neighbor table.
2788///
2789/// If the table is full, the garbage collector will be run synchronously in
2790/// an attempt to free up space. If space becomes available, the entry will be
2791/// inserted, otherwise a `TableFullError` is returned.
2792///
2793/// Upon successful insertion, the `Added` event is emitted to bindings and a
2794/// the newly inserted entry is returned.
2795///
2796/// # Panics
2797///
2798/// May panic if the entry already exists (depending on whether the garbage
2799/// collector needs to run, and whether the existing entry can be discarded).
2800pub(crate) fn insert_new_entry<
2801    'a,
2802    I: Ip,
2803    D: LinkDevice,
2804    DeviceId: StrongDeviceIdentifier,
2805    BC: NudBindingsContext<I, D, DeviceId>,
2806>(
2807    neighbors: &'a mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2808    gc_state: &mut GarbageCollectionState<BC::Instant>,
2809    timer_heap: &mut TimerHeap<I, BC>,
2810    bindings_ctx: &mut BC,
2811    device_id: &DeviceId,
2812    ip: SpecifiedAddr<I::Addr>,
2813    entry: NeighborState<D, BC>,
2814) -> Result<
2815    OccupiedEntry<'a, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2816    TableFullError<NeighborState<D, BC>>,
2817> {
2818    if neighbors.len() >= MAX_ENTRIES {
2819        // If the garbage collector is already scheduled, cancel it on a best
2820        // effort basis. This may race with the timer firing, but there's no
2821        // real harm of that happening (e.g. it would result in a single
2822        // spurious GC run).
2823        timer_heap.cancel_gc(bindings_ctx);
2824        collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, device_id);
2825    }
2826
2827    if neighbors.len() >= MAX_ENTRIES {
2828        return Err(TableFullError { entry });
2829    }
2830
2831    match neighbors.entry(ip) {
2832        Entry::Occupied(_) => panic!("neighbor entry unexpectedly existed"),
2833        Entry::Vacant(e) => {
2834            let event_state = entry.to_event_state();
2835            let entry = e.insert_entry(entry);
2836            let event = Event::added(device_id, event_state, ip, bindings_ctx.now());
2837            bindings_ctx.on_event(event);
2838            Ok(entry)
2839        }
2840    }
2841}
2842
2843/// Confirm upper-layer forward reachability to the specified neighbor through
2844/// the specified device.
2845pub fn confirm_reachable<I, D, CC, BC>(
2846    core_ctx: &mut CC,
2847    bindings_ctx: &mut BC,
2848    device_id: &CC::DeviceId,
2849    neighbor: SpecifiedAddr<I::Addr>,
2850) where
2851    I: Ip,
2852    D: LinkDevice,
2853    BC: NudBindingsContext<I, D, CC::DeviceId>,
2854    CC: NudContext<I, D, BC>,
2855{
2856    core_ctx.with_nud_state_mut_and_sender_ctx(
2857        device_id,
2858        |NudState { neighbors, timer_heap, .. }, core_ctx| {
2859            match neighbors.entry(neighbor) {
2860                Entry::Vacant(_) => {
2861                    debug!(
2862                        "got an upper-layer confirmation for non-existent neighbor entry {}",
2863                        neighbor
2864                    );
2865                }
2866                Entry::Occupied(e) => match e.into_mut() {
2867                    NeighborState::Static(_) => {}
2868                    NeighborState::Dynamic(e) => {
2869                        // Per [RFC 4861 section 7.3.3]:
2870                        //
2871                        //   When a reachability confirmation is received (either through upper-
2872                        //   layer advice or a solicited Neighbor Advertisement), an entry's state
2873                        //   changes to REACHABLE.  The one exception is that upper-layer advice has
2874                        //   no effect on entries in the INCOMPLETE state (e.g., for which no link-
2875                        //   layer address is cached).
2876                        //
2877                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2878                        let link_address = match e {
2879                            DynamicNeighborState::Incomplete(_) => return,
2880                            DynamicNeighborState::Reachable(Reachable {
2881                                link_address,
2882                                last_confirmed_at: _,
2883                            })
2884                            | DynamicNeighborState::Stale(Stale { link_address })
2885                            | DynamicNeighborState::Delay(Delay { link_address })
2886                            | DynamicNeighborState::Probe(Probe {
2887                                link_address,
2888                                transmit_counter: _,
2889                            })
2890                            | DynamicNeighborState::Unreachable(Unreachable {
2891                                link_address,
2892                                mode: _,
2893                            }) => *link_address,
2894                        };
2895                        e.enter_reachable(
2896                            core_ctx,
2897                            bindings_ctx,
2898                            timer_heap,
2899                            device_id,
2900                            neighbor,
2901                            link_address,
2902                        );
2903                    }
2904                },
2905            }
2906        },
2907    );
2908}
2909
2910/// Performs a linear scan of the neighbor table, discarding enough entries to
2911/// bring the total size under `GC_THRESHOLD` if possible.
2912///
2913/// Static neighbor entries are never discarded, nor are any entries that are
2914/// considered to be in use, which is defined as an entry in REACHABLE,
2915/// INCOMPLETE, DELAY, or PROBE. In other words, the only entries eligible to be
2916/// discarded are those in STALE or UNREACHABLE. This is reasonable because all
2917/// other states represent entries to which we have either recently sent packets
2918/// (REACHABLE, DELAY, PROBE), or which we are actively trying to resolve and
2919/// for which we have recently queued outgoing packets (INCOMPLETE).
2920fn collect_garbage_inner<I, D, DeviceId, BC>(
2921    neighbors: &mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2922    gc_state: &mut GarbageCollectionState<BC::Instant>,
2923    timer_heap: &mut TimerHeap<I, BC>,
2924    bindings_ctx: &mut BC,
2925    device_id: &DeviceId,
2926) where
2927    I: Ip,
2928    D: LinkDevice,
2929    DeviceId: StrongDeviceIdentifier,
2930    BC: NudBindingsContext<I, D, DeviceId>,
2931{
2932    let GarbageCollectionState { last_gc, is_dirty } = gc_state;
2933    // Short circuit if we know there are no discardable entries in the table.
2934    if !*is_dirty {
2935        return;
2936    }
2937
2938    let max_to_remove = neighbors.len().saturating_sub(GC_THRESHOLD);
2939    if max_to_remove == 0 {
2940        return;
2941    }
2942
2943    let mut is_still_dirty = false;
2944
2945    // Define an ordering by priority for garbage collection, such that lower
2946    // numbers correspond to higher usefulness and therefore lower likelihood of
2947    // being discarded.
2948    //
2949    // TODO(https://fxbug.dev/42075782): once neighbor entries hold a timestamp
2950    // tracking when they were last updated, consider using this timestamp to break
2951    // ties between entries in the same state, so that we discard less recently
2952    // updated entries before more recently updated ones.
2953    fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2954        state: &DynamicNeighborState<D, BT>,
2955    ) -> usize {
2956        match state {
2957            DynamicNeighborState::Incomplete(_)
2958            | DynamicNeighborState::Reachable(_)
2959            | DynamicNeighborState::Delay(_)
2960            | DynamicNeighborState::Probe(_) => unreachable!(
2961                "the netstack should only ever discard STALE or UNREACHABLE entries; \
2962                    found {:?}",
2963                state,
2964            ),
2965            DynamicNeighborState::Stale(_) => 0,
2966            DynamicNeighborState::Unreachable(Unreachable {
2967                link_address: _,
2968                mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2969            }) => 1,
2970            DynamicNeighborState::Unreachable(Unreachable {
2971                link_address: _,
2972                mode: UnreachableMode::WaitingForPacketSend,
2973            }) => 2,
2974        }
2975    }
2976
2977    struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2978        key: K,
2979        state: &'a mut DynamicNeighborState<D, BT>,
2980    }
2981
2982    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2983        fn eq(&self, other: &Self) -> bool {
2984            self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2985        }
2986    }
2987    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2988    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2989        fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2990            // Sort in reverse order so `BinaryHeap` will function as a min-heap rather than
2991            // a max-heap. This means it will maintain the minimum (i.e. most useful) entry
2992            // at the top of the heap.
2993            gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2994        }
2995    }
2996    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2997        fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2998            Some(self.cmp(&other))
2999        }
3000    }
3001
3002    let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
3003    for (ip, neighbor) in neighbors.iter_mut() {
3004        match neighbor {
3005            NeighborState::Static(_) => {
3006                // Don't discard static entries.
3007                continue;
3008            }
3009            NeighborState::Dynamic(state) => {
3010                match state {
3011                    DynamicNeighborState::Incomplete(_)
3012                    | DynamicNeighborState::Reachable(_)
3013                    | DynamicNeighborState::Delay(_)
3014                    | DynamicNeighborState::Probe(_) => {
3015                        // Don't discard in-use entries.
3016                        continue;
3017                    }
3018                    DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
3019                        // Unconditionally insert the first `max_to_remove` entries.
3020                        if entries_to_remove.len() < max_to_remove {
3021                            entries_to_remove.push(SortEntry { key: ip, state });
3022                            continue;
3023                        }
3024                        // If we exceed `max_to_remove`, the table will still
3025                        // have discardable entries after this run. Prioritize
3026                        // the removal of neighbors that are less useful (based
3027                        // on their ordering).
3028                        is_still_dirty = true;
3029                        let minimum =
3030                            entries_to_remove.peek().expect("heap should have at least 1 entry");
3031                        let candidate = SortEntry { key: ip, state };
3032                        if &candidate > minimum {
3033                            let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
3034                            entries_to_remove.push(candidate);
3035                        }
3036                    }
3037                }
3038            }
3039        }
3040    }
3041
3042    let entries_to_remove = entries_to_remove
3043        .into_iter()
3044        .map(|SortEntry { key: neighbor, state }| {
3045            state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
3046            *neighbor
3047        })
3048        .collect::<Vec<_>>();
3049
3050    for neighbor in entries_to_remove {
3051        assert_matches!(neighbors.remove(&neighbor), Some(_));
3052        bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
3053    }
3054
3055    *last_gc = Some(bindings_ctx.now());
3056    *is_dirty = is_still_dirty;
3057}
3058
3059fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
3060where
3061    I: Ip,
3062    D: LinkDevice,
3063    BC: NudBindingsContext<I, D, CC::DeviceId>,
3064    CC: NudContext<I, D, BC>,
3065{
3066    core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, gc_state, timer_heap }, _| {
3067        collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, &device_id);
3068    })
3069}
3070
3071#[cfg(test)]
3072mod tests {
3073    use alloc::vec;
3074
3075    use ip_test_macro::ip_test;
3076    use net_declare::{net_ip_v4, net_ip_v6};
3077    use net_types::UnicastAddr;
3078    use net_types::ip::{Ipv4Addr, Ipv6Addr};
3079    use netstack3_base::testutil::{
3080        FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
3081        FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
3082    };
3083    use netstack3_base::{
3084        CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
3085    };
3086    use netstack3_hashmap::HashSet;
3087    use test_case::test_case;
3088
3089    use super::*;
3090    use crate::internal::device::nud::api::{NeighborApi, StaticNeighborInsertionError};
3091    use packet::NestableSerializer as _;
3092
3093    struct FakeNudContext<I: Ip, D: LinkDevice> {
3094        state: NudState<I, D, FakeBindingsCtxImpl<I>>,
3095        counters: NudCounters<I>,
3096    }
3097
3098    struct FakeConfigContext {
3099        retrans_timer: NonZeroDuration,
3100        nud_config: NudUserConfig,
3101    }
3102
3103    struct FakeCoreCtxImpl<I: Ip> {
3104        nud: FakeNudContext<I, FakeLinkDevice>,
3105        inner: FakeInnerCtxImpl<I>,
3106    }
3107
3108    type FakeInnerCtxImpl<I> =
3109        FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
3110
3111    #[derive(Debug, PartialEq, Eq)]
3112    enum FakeNudMessageMeta<I: Ip> {
3113        NeighborSolicitation {
3114            lookup_addr: SpecifiedAddr<I::Addr>,
3115            remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3116        },
3117        IpFrame {
3118            dst_link_address: UnicastAddr<FakeLinkAddress>,
3119        },
3120        IcmpDestUnreachable,
3121    }
3122
3123    type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3124        NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3125        Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3126        (),
3127        (),
3128    >;
3129
3130    impl<I: Ip> FakeCoreCtxImpl<I> {
3131        fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3132            Self {
3133                nud: {
3134                    FakeNudContext {
3135                        state: NudState::new::<_, IntoCoreTimerCtx>(
3136                            bindings_ctx,
3137                            FakeWeakDeviceId(FakeLinkDeviceId),
3138                        ),
3139                        counters: Default::default(),
3140                    }
3141                },
3142                inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3143                    retrans_timer: ONE_SECOND,
3144                    // Use different values from the defaults in tests so we get
3145                    // coverage that the config is used everywhere and not the
3146                    // defaults.
3147                    nud_config: NudUserConfig {
3148                        max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3149                        max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3150                        base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3151                        retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3152                    },
3153                }),
3154            }
3155        }
3156    }
3157
3158    fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3159        CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3160    }
3161
3162    impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3163        type DeviceId = FakeLinkDeviceId;
3164        type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3165    }
3166
3167    impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3168        type ConfigCtx<'a> = FakeConfigContext;
3169
3170        type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3171
3172        fn with_nud_state_mut_and_sender_ctx<
3173            O,
3174            F: FnOnce(
3175                &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3176                &mut Self::SenderCtx<'_>,
3177            ) -> O,
3178        >(
3179            &mut self,
3180            _device_id: &Self::DeviceId,
3181            cb: F,
3182        ) -> O {
3183            cb(&mut self.nud.state, &mut self.inner)
3184        }
3185
3186        fn with_nud_state_mut<
3187            O,
3188            F: FnOnce(
3189                &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3190                &mut Self::ConfigCtx<'_>,
3191            ) -> O,
3192        >(
3193            &mut self,
3194            &FakeLinkDeviceId: &FakeLinkDeviceId,
3195            cb: F,
3196        ) -> O {
3197            cb(&mut self.nud.state, &mut self.inner.state)
3198        }
3199
3200        fn with_nud_state<
3201            O,
3202            F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3203        >(
3204            &mut self,
3205            &FakeLinkDeviceId: &FakeLinkDeviceId,
3206            cb: F,
3207        ) -> O {
3208            cb(&self.nud.state)
3209        }
3210
3211        fn send_neighbor_solicitation(
3212            &mut self,
3213            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3214            &FakeLinkDeviceId: &FakeLinkDeviceId,
3215            lookup_addr: SpecifiedAddr<I::Addr>,
3216            remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3217        ) {
3218            self.inner
3219                .send_frame(
3220                    bindings_ctx,
3221                    FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3222                    Buf::new(Vec::new(), ..),
3223                )
3224                .unwrap()
3225        }
3226    }
3227
3228    impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3229        for FakeCoreCtxImpl<I>
3230    {
3231        fn send_icmp_dest_unreachable(
3232            &mut self,
3233            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3234            frame: Buf<Vec<u8>>,
3235            _device_id: Option<&Self::DeviceId>,
3236            _original_src_ip: SocketIpAddr<I::Addr>,
3237            _original_dst_ip: SocketIpAddr<I::Addr>,
3238            _header_len: usize,
3239            _proto: I::Proto,
3240            _metadata: I::Metadata,
3241        ) {
3242            self.inner
3243                .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3244                .unwrap()
3245        }
3246    }
3247
3248    impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3249        fn counters(&self) -> &NudCounters<I> {
3250            &self.nud.counters
3251        }
3252    }
3253
3254    impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3255        fn retransmit_timeout(&mut self) -> NonZeroDuration {
3256            self.retrans_timer
3257        }
3258
3259        fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3260            cb(&self.nud_config)
3261        }
3262
3263        fn override_lock_time(&mut self) -> Duration {
3264            Duration::ZERO
3265        }
3266    }
3267
3268    impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3269        fn send_ip_packet_to_neighbor_link_addr<S>(
3270            &mut self,
3271            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3272            dst_link_address: UnicastAddr<FakeLinkAddress>,
3273            body: S,
3274            _tx_meta: FakeTxMetadata,
3275        ) -> Result<(), SendFrameError<S>>
3276        where
3277            S: NetworkSerializer,
3278            S::Buffer: BufferMut,
3279        {
3280            self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3281        }
3282    }
3283
3284    impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3285        fn retransmit_timeout(&mut self) -> NonZeroDuration {
3286            <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3287        }
3288
3289        fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3290            <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3291        }
3292
3293        fn override_lock_time(&mut self) -> Duration {
3294            <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3295        }
3296    }
3297
3298    const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3299
3300    #[track_caller]
3301    fn check_lookup_has<I: Ip>(
3302        core_ctx: &mut FakeCoreCtxImpl<I>,
3303        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3304        lookup_addr: SpecifiedAddr<I::Addr>,
3305        expected_link_addr: UnicastAddr<FakeLinkAddress>,
3306    ) {
3307        let entry = assert_matches!(
3308            core_ctx.nud.state.neighbors.get(&lookup_addr),
3309            Some(entry @ (
3310                NeighborState::Dynamic(
3311                    DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3312                    | DynamicNeighborState::Stale (Stale { link_address })
3313                    | DynamicNeighborState::Delay (Delay { link_address })
3314                    | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3315                    | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3316                )
3317                | NeighborState::Static(link_address)
3318            )) => {
3319                assert_eq!(link_address, &expected_link_addr);
3320                entry
3321            }
3322        );
3323        match entry {
3324            NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3325                unreachable!("entry must be static, REACHABLE, or STALE")
3326            }
3327            NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3328                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3329                    bindings_ctx,
3330                    [(
3331                        lookup_addr,
3332                        NudEvent::ReachableTime,
3333                        core_ctx.inner.base_reachable_time().get(),
3334                    )],
3335                );
3336            }
3337            NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3338                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3339                    bindings_ctx,
3340                    [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3341                );
3342            }
3343            NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3344                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3345                    bindings_ctx,
3346                    [(
3347                        lookup_addr,
3348                        NudEvent::RetransmitUnicastProbe,
3349                        core_ctx.inner.state.retrans_timer.get(),
3350                    )],
3351                );
3352            }
3353            NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3354                link_address: _,
3355                mode,
3356            })) => {
3357                let instant = match mode {
3358                    UnreachableMode::WaitingForPacketSend => None,
3359                    mode @ UnreachableMode::Backoff { .. } => {
3360                        let duration =
3361                            mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3362                        Some(bindings_ctx.now() + duration.get())
3363                    }
3364                };
3365                if let Some(instant) = instant {
3366                    core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3367                        lookup_addr,
3368                        NudEvent::RetransmitUnicastProbe,
3369                        instant,
3370                    )]);
3371                }
3372            }
3373            NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3374            | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3375        }
3376    }
3377
3378    trait TestIpExt: NudIcmpIpExt {
3379        const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3380        const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3381        const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3382    }
3383
3384    impl TestIpExt for Ipv4 {
3385        // Safe because the address is non-zero.
3386        const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3387            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3388        const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3389            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3390        const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3391            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3392    }
3393
3394    impl TestIpExt for Ipv6 {
3395        // Safe because the address is non-zero.
3396        const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3397            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3398        const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3399            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3400        const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3401            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3402    }
3403
3404    const LINK_ADDR1: UnicastAddr<FakeLinkAddress> =
3405        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([2])) };
3406    const LINK_ADDR2: UnicastAddr<FakeLinkAddress> =
3407        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([4])) };
3408    const LINK_ADDR3: UnicastAddr<FakeLinkAddress> =
3409        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([6])) };
3410
3411    impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3412        fn neighbor() -> Self {
3413            Self {
3414                device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3415                timer_type: NudTimerType::Neighbor,
3416                _marker: PhantomData,
3417            }
3418        }
3419
3420        fn garbage_collection() -> Self {
3421            Self {
3422                device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3423                timer_type: NudTimerType::GarbageCollection,
3424                _marker: PhantomData,
3425            }
3426        }
3427    }
3428
3429    fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3430        core_ctx: &mut FakeCoreCtxImpl<I>,
3431        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3432        neighbor: SpecifiedAddr<I::Addr>,
3433        pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3434        body: u8,
3435        expect_event: bool,
3436    ) {
3437        let body = [body];
3438        assert_eq!(
3439            NudHandler::send_ip_packet_to_neighbor(
3440                core_ctx,
3441                bindings_ctx,
3442                &FakeLinkDeviceId,
3443                neighbor,
3444                Buf::new(body, ..),
3445                FakeTxMetadata::default(),
3446            ),
3447            Ok(())
3448        );
3449
3450        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3451
3452        pending_frames.push_back(Buf::new(body.to_vec(), ..));
3453
3454        assert_neighbor_state_with_ip(
3455            core_ctx,
3456            bindings_ctx,
3457            neighbor,
3458            DynamicNeighborState::Incomplete(Incomplete {
3459                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3460                pending_frames: pending_frames
3461                    .iter()
3462                    .cloned()
3463                    .map(|buf| (buf, FakeTxMetadata::default()))
3464                    .collect(),
3465                notifiers: Vec::new(),
3466                _marker: PhantomData,
3467            }),
3468            expect_event.then_some(ExpectedEvent::Added),
3469        );
3470
3471        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3472            bindings_ctx,
3473            [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3474        );
3475    }
3476
3477    fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3478        core_ctx: &mut FakeCoreCtxImpl<I>,
3479        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3480        ip_address: SpecifiedAddr<I::Addr>,
3481        take_probe: bool,
3482    ) -> VecDeque<Buf<Vec<u8>>> {
3483        let mut pending_frames = VecDeque::new();
3484        queue_ip_packet_to_unresolved_neighbor(
3485            core_ctx,
3486            bindings_ctx,
3487            ip_address,
3488            &mut pending_frames,
3489            1,
3490            true, /* expect_event */
3491        );
3492        if take_probe {
3493            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3494        }
3495        pending_frames
3496    }
3497
3498    fn init_incomplete_neighbor<I: TestIpExt>(
3499        core_ctx: &mut FakeCoreCtxImpl<I>,
3500        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3501        take_probe: bool,
3502    ) -> VecDeque<Buf<Vec<u8>>> {
3503        init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3504    }
3505
3506    fn init_stale_neighbor_with_ip<I: TestIpExt>(
3507        core_ctx: &mut FakeCoreCtxImpl<I>,
3508        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3509        ip_address: SpecifiedAddr<I::Addr>,
3510        link_address: UnicastAddr<FakeLinkAddress>,
3511    ) {
3512        NudHandler::handle_neighbor_update(
3513            core_ctx,
3514            bindings_ctx,
3515            &FakeLinkDeviceId,
3516            ip_address,
3517            DynamicNeighborUpdateSource::Probe { link_address },
3518        );
3519        assert_neighbor_state_with_ip(
3520            core_ctx,
3521            bindings_ctx,
3522            ip_address,
3523            DynamicNeighborState::Stale(Stale { link_address }),
3524            Some(ExpectedEvent::Added),
3525        );
3526    }
3527
3528    fn init_stale_neighbor<I: TestIpExt>(
3529        core_ctx: &mut FakeCoreCtxImpl<I>,
3530        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3531        link_address: UnicastAddr<FakeLinkAddress>,
3532    ) {
3533        init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3534    }
3535
3536    fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3537        core_ctx: &mut FakeCoreCtxImpl<I>,
3538        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3539        ip_address: SpecifiedAddr<I::Addr>,
3540        link_address: UnicastAddr<FakeLinkAddress>,
3541    ) {
3542        let queued_frame =
3543            init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3544        NudHandler::handle_neighbor_update(
3545            core_ctx,
3546            bindings_ctx,
3547            &FakeLinkDeviceId,
3548            ip_address,
3549            DynamicNeighborUpdateSource::Confirmation {
3550                link_address: Some(link_address),
3551                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3552            },
3553        );
3554        assert_neighbor_state_with_ip(
3555            core_ctx,
3556            bindings_ctx,
3557            ip_address,
3558            DynamicNeighborState::Reachable(Reachable {
3559                link_address,
3560                last_confirmed_at: bindings_ctx.now(),
3561            }),
3562            Some(ExpectedEvent::Changed),
3563        );
3564        assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3565    }
3566
3567    fn init_reachable_neighbor<I: TestIpExt>(
3568        core_ctx: &mut FakeCoreCtxImpl<I>,
3569        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3570        link_address: UnicastAddr<FakeLinkAddress>,
3571    ) {
3572        init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3573    }
3574
3575    fn init_delay_neighbor_with_ip<I: TestIpExt>(
3576        core_ctx: &mut FakeCoreCtxImpl<I>,
3577        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3578        ip_address: SpecifiedAddr<I::Addr>,
3579        link_address: UnicastAddr<FakeLinkAddress>,
3580    ) {
3581        init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3582        assert_eq!(
3583            NudHandler::send_ip_packet_to_neighbor(
3584                core_ctx,
3585                bindings_ctx,
3586                &FakeLinkDeviceId,
3587                ip_address,
3588                Buf::new([1], ..),
3589                FakeTxMetadata::default(),
3590            ),
3591            Ok(())
3592        );
3593        assert_neighbor_state_with_ip(
3594            core_ctx,
3595            bindings_ctx,
3596            ip_address,
3597            DynamicNeighborState::Delay(Delay { link_address }),
3598            Some(ExpectedEvent::Changed),
3599        );
3600        assert_eq!(
3601            core_ctx.inner.take_frames(),
3602            vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3603        );
3604    }
3605
3606    fn init_delay_neighbor<I: TestIpExt>(
3607        core_ctx: &mut FakeCoreCtxImpl<I>,
3608        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3609        link_address: UnicastAddr<FakeLinkAddress>,
3610    ) {
3611        init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3612    }
3613
3614    fn init_probe_neighbor_with_ip<I: TestIpExt>(
3615        core_ctx: &mut FakeCoreCtxImpl<I>,
3616        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3617        ip_address: SpecifiedAddr<I::Addr>,
3618        link_address: UnicastAddr<FakeLinkAddress>,
3619        take_probe: bool,
3620    ) {
3621        init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3622        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3623        core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3624        assert_eq!(
3625            bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3626            [NudTimerId::neighbor()]
3627        );
3628        assert_neighbor_state_with_ip(
3629            core_ctx,
3630            bindings_ctx,
3631            ip_address,
3632            DynamicNeighborState::Probe(Probe {
3633                link_address,
3634                transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3635            }),
3636            Some(ExpectedEvent::Changed),
3637        );
3638        if take_probe {
3639            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3640        }
3641    }
3642
3643    fn init_probe_neighbor<I: TestIpExt>(
3644        core_ctx: &mut FakeCoreCtxImpl<I>,
3645        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3646        link_address: UnicastAddr<FakeLinkAddress>,
3647        take_probe: bool,
3648    ) {
3649        init_probe_neighbor_with_ip(
3650            core_ctx,
3651            bindings_ctx,
3652            I::LOOKUP_ADDR1,
3653            link_address,
3654            take_probe,
3655        );
3656    }
3657
3658    fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3659        core_ctx: &mut FakeCoreCtxImpl<I>,
3660        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3661        ip_address: SpecifiedAddr<I::Addr>,
3662        link_address: UnicastAddr<FakeLinkAddress>,
3663    ) {
3664        init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3665        let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3666        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3667        for _ in 0..max_unicast_solicit {
3668            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3669            assert_eq!(
3670                bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3671                [NudTimerId::neighbor()]
3672            );
3673        }
3674        assert_neighbor_state_with_ip(
3675            core_ctx,
3676            bindings_ctx,
3677            ip_address,
3678            DynamicNeighborState::Unreachable(Unreachable {
3679                link_address,
3680                mode: UnreachableMode::WaitingForPacketSend,
3681            }),
3682            Some(ExpectedEvent::Changed),
3683        );
3684    }
3685
3686    fn init_unreachable_neighbor<I: TestIpExt>(
3687        core_ctx: &mut FakeCoreCtxImpl<I>,
3688        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3689        link_address: UnicastAddr<FakeLinkAddress>,
3690    ) {
3691        init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3692    }
3693
3694    #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3695    enum InitialState {
3696        Incomplete,
3697        Stale,
3698        Reachable,
3699        Delay,
3700        Probe,
3701        Unreachable,
3702    }
3703
3704    fn init_neighbor_in_state<I: TestIpExt>(
3705        core_ctx: &mut FakeCoreCtxImpl<I>,
3706        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3707        state: InitialState,
3708    ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3709        match state {
3710            InitialState::Incomplete => {
3711                let _: VecDeque<Buf<Vec<u8>>> =
3712                    init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3713            }
3714            InitialState::Reachable => {
3715                init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3716            }
3717            InitialState::Stale => {
3718                init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3719            }
3720            InitialState::Delay => {
3721                init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3722            }
3723            InitialState::Probe => {
3724                init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3725            }
3726            InitialState::Unreachable => {
3727                init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3728            }
3729        }
3730        assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3731            Some(NeighborState::Dynamic(state)) => state.clone()
3732        )
3733    }
3734
3735    #[track_caller]
3736    fn init_static_neighbor_with_ip<I: TestIpExt>(
3737        core_ctx: &mut FakeCoreCtxImpl<I>,
3738        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3739        ip_address: SpecifiedAddr<I::Addr>,
3740        link_address: UnicastAddr<FakeLinkAddress>,
3741        expected_event: ExpectedEvent,
3742    ) {
3743        let mut ctx = CtxPair { core_ctx, bindings_ctx };
3744        NeighborApi::new(&mut ctx)
3745            .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3746            .unwrap();
3747        assert_eq!(
3748            ctx.bindings_ctx.take_events(),
3749            [Event {
3750                device: FakeLinkDeviceId,
3751                addr: ip_address,
3752                kind: match expected_event {
3753                    ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3754                    ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3755                },
3756                at: ctx.bindings_ctx.now(),
3757            }],
3758        );
3759    }
3760
3761    #[track_caller]
3762    fn init_static_neighbor<I: TestIpExt>(
3763        core_ctx: &mut FakeCoreCtxImpl<I>,
3764        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3765        link_address: UnicastAddr<FakeLinkAddress>,
3766        expected_event: ExpectedEvent,
3767    ) {
3768        init_static_neighbor_with_ip(
3769            core_ctx,
3770            bindings_ctx,
3771            I::LOOKUP_ADDR1,
3772            link_address,
3773            expected_event,
3774        );
3775    }
3776
3777    #[track_caller]
3778    fn delete_neighbor<I: TestIpExt>(
3779        core_ctx: &mut FakeCoreCtxImpl<I>,
3780        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3781    ) {
3782        let mut ctx = CtxPair { core_ctx, bindings_ctx };
3783        NeighborApi::new(&mut ctx)
3784            .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3785            .expect("neighbor entry should exist");
3786        assert_eq!(
3787            ctx.bindings_ctx.take_events(),
3788            [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3789        );
3790    }
3791
3792    #[track_caller]
3793    fn assert_neighbor_state<I: TestIpExt>(
3794        core_ctx: &FakeCoreCtxImpl<I>,
3795        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3796        state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3797        event_kind: Option<ExpectedEvent>,
3798    ) {
3799        assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3800    }
3801
3802    #[derive(Clone, Copy, Debug)]
3803    enum ExpectedEvent {
3804        Added,
3805        Changed,
3806    }
3807
3808    #[track_caller]
3809    fn assert_neighbor_state_with_ip<I: TestIpExt>(
3810        core_ctx: &FakeCoreCtxImpl<I>,
3811        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3812        neighbor: SpecifiedAddr<I::Addr>,
3813        state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3814        expected_event: Option<ExpectedEvent>,
3815    ) {
3816        if let Some(expected_event) = expected_event {
3817            let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3818            assert_eq!(
3819                bindings_ctx.take_events(),
3820                [Event {
3821                    device: FakeLinkDeviceId,
3822                    addr: neighbor,
3823                    kind: match expected_event {
3824                        ExpectedEvent::Added => EventKind::Added(event_state),
3825                        ExpectedEvent::Changed => EventKind::Changed(event_state),
3826                    },
3827                    at: bindings_ctx.now(),
3828                }],
3829            );
3830        }
3831
3832        assert_eq!(
3833            core_ctx.nud.state.neighbors.get(&neighbor),
3834            Some(&NeighborState::Dynamic(state))
3835        );
3836    }
3837
3838    #[track_caller]
3839    fn assert_pending_frame_sent<I: TestIpExt>(
3840        core_ctx: &mut FakeCoreCtxImpl<I>,
3841        pending_frames: VecDeque<Buf<Vec<u8>>>,
3842        link_address: UnicastAddr<FakeLinkAddress>,
3843    ) {
3844        assert_eq!(
3845            core_ctx.inner.take_frames(),
3846            pending_frames
3847                .into_iter()
3848                .map(|f| (
3849                    FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3850                    f.as_ref().to_vec(),
3851                ))
3852                .collect::<Vec<_>>()
3853        );
3854    }
3855
3856    #[track_caller]
3857    fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3858        core_ctx: &mut FakeCoreCtxImpl<I>,
3859        ip_address: SpecifiedAddr<I::Addr>,
3860        link_address: Option<UnicastAddr<FakeLinkAddress>>,
3861    ) {
3862        assert_eq!(
3863            core_ctx.inner.take_frames(),
3864            [(
3865                FakeNudMessageMeta::NeighborSolicitation {
3866                    lookup_addr: ip_address,
3867                    remote_link_addr: link_address,
3868                },
3869                Vec::new()
3870            )]
3871        );
3872    }
3873
3874    #[track_caller]
3875    fn assert_neighbor_probe_sent<I: TestIpExt>(
3876        core_ctx: &mut FakeCoreCtxImpl<I>,
3877        link_address: Option<UnicastAddr<FakeLinkAddress>>,
3878    ) {
3879        assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3880    }
3881
3882    #[track_caller]
3883    fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3884        core_ctx: &mut FakeCoreCtxImpl<I>,
3885        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3886        neighbor: SpecifiedAddr<I::Addr>,
3887    ) {
3888        super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3889        assert_eq!(
3890            bindings_ctx.take_events(),
3891            [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3892        );
3893    }
3894
3895    #[ip_test(I)]
3896    fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3897        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3898
3899        // Try to send a packet for which serialization will fail due to a size
3900        // constraint.
3901        let packet = Buf::new([0; 2], ..).with_size_limit(1);
3902
3903        let err = assert_matches!(
3904            NudHandler::send_ip_packet_to_neighbor(
3905                &mut core_ctx,
3906                &mut bindings_ctx,
3907                &FakeLinkDeviceId,
3908                I::LOOKUP_ADDR1,
3909                packet,
3910                FakeTxMetadata::default(),
3911            ),
3912            Err(ErrorAndSerializer { error, serializer: _ }) => error
3913        );
3914        assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3915
3916        // The neighbor should not be inserted in the table, a probe should not be sent,
3917        // and no retransmission timer should be scheduled.
3918        super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3919        assert_eq!(core_ctx.inner.take_frames(), []);
3920        bindings_ctx.timers.assert_no_timers_installed();
3921    }
3922
3923    #[ip_test(I)]
3924    fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3925        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3926
3927        // Initialize a neighbor in INCOMPLETE.
3928        let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3929
3930        // Handle an incoming probe from that neighbor.
3931        NudHandler::handle_neighbor_update(
3932            &mut core_ctx,
3933            &mut bindings_ctx,
3934            &FakeLinkDeviceId,
3935            I::LOOKUP_ADDR1,
3936            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3937        );
3938
3939        // Neighbor should now be in STALE, per RFC 4861 section 7.2.3.
3940        assert_neighbor_state(
3941            &core_ctx,
3942            &mut bindings_ctx,
3943            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3944            Some(ExpectedEvent::Changed),
3945        );
3946        assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3947    }
3948
3949    #[ip_test(I)]
3950    #[test_case(true, true; "solicited override")]
3951    #[test_case(true, false; "solicited non-override")]
3952    #[test_case(false, true; "unsolicited override")]
3953    #[test_case(false, false; "unsolicited non-override")]
3954    fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3955        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3956
3957        // Initialize a neighbor in INCOMPLETE.
3958        let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3959
3960        // Handle an incoming confirmation from that neighbor.
3961        NudHandler::handle_neighbor_update(
3962            &mut core_ctx,
3963            &mut bindings_ctx,
3964            &FakeLinkDeviceId,
3965            I::LOOKUP_ADDR1,
3966            DynamicNeighborUpdateSource::Confirmation {
3967                link_address: Some(LINK_ADDR1),
3968                flags: ConfirmationFlags { solicited_flag, override_flag },
3969            },
3970        );
3971
3972        let expected_state = if solicited_flag {
3973            DynamicNeighborState::Reachable(Reachable {
3974                link_address: LINK_ADDR1,
3975                last_confirmed_at: bindings_ctx.now(),
3976            })
3977        } else {
3978            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3979        };
3980        assert_neighbor_state(
3981            &core_ctx,
3982            &mut bindings_ctx,
3983            expected_state,
3984            Some(ExpectedEvent::Changed),
3985        );
3986        assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3987    }
3988
3989    #[ip_test(I)]
3990    fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3991        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3992
3993        // Initialize a neighbor in REACHABLE.
3994        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3995
3996        // After reachable time, neighbor should transition to STALE.
3997        assert_eq!(
3998            bindings_ctx
3999                .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
4000            [NudTimerId::neighbor()]
4001        );
4002        assert_neighbor_state(
4003            &core_ctx,
4004            &mut bindings_ctx,
4005            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4006            Some(ExpectedEvent::Changed),
4007        );
4008    }
4009
4010    #[ip_test(I)]
4011    #[test_case(InitialState::Reachable, true; "reachable with different address")]
4012    #[test_case(InitialState::Reachable, false; "reachable with same address")]
4013    #[test_case(InitialState::Stale, true; "stale with different address")]
4014    #[test_case(InitialState::Stale, false; "stale with same address")]
4015    #[test_case(InitialState::Delay, true; "delay with different address")]
4016    #[test_case(InitialState::Delay, false; "delay with same address")]
4017    #[test_case(InitialState::Probe, true; "probe with different address")]
4018    #[test_case(InitialState::Probe, false; "probe with same address")]
4019    #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
4020    #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
4021    fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
4022        initial_state: InitialState,
4023        update_link_address: bool,
4024    ) {
4025        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4026
4027        // Initialize a neighbor.
4028        let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4029
4030        // Handle an incoming probe, possibly with an updated link address.
4031        NudHandler::handle_neighbor_update(
4032            &mut core_ctx,
4033            &mut bindings_ctx,
4034            &FakeLinkDeviceId,
4035            I::LOOKUP_ADDR1,
4036            DynamicNeighborUpdateSource::Probe {
4037                link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
4038            },
4039        );
4040
4041        // If the link address was updated, the neighbor should now be in STALE with the
4042        // new link address, per RFC 4861 section 7.2.3.
4043        //
4044        // If the link address is the same, the entry should remain in its initial
4045        // state.
4046        let expected_state = if update_link_address {
4047            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
4048        } else {
4049            initial_state
4050        };
4051        assert_neighbor_state(
4052            &core_ctx,
4053            &mut bindings_ctx,
4054            expected_state,
4055            update_link_address.then_some(ExpectedEvent::Changed),
4056        );
4057    }
4058
4059    #[ip_test(I)]
4060    #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4061    #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4062    #[test_case(InitialState::Stale, true; "stale with override flag set")]
4063    #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4064    #[test_case(InitialState::Delay, true; "delay with override flag set")]
4065    #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4066    #[test_case(InitialState::Probe, true; "probe with override flag set")]
4067    #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4068    #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4069    #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4070    fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
4071        initial_state: InitialState,
4072        override_flag: bool,
4073    ) {
4074        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4075
4076        // Initialize a neighbor.
4077        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4078
4079        // Handle an incoming solicited confirmation.
4080        NudHandler::handle_neighbor_update(
4081            &mut core_ctx,
4082            &mut bindings_ctx,
4083            &FakeLinkDeviceId,
4084            I::LOOKUP_ADDR1,
4085            DynamicNeighborUpdateSource::Confirmation {
4086                link_address: Some(LINK_ADDR1),
4087                flags: ConfirmationFlags { solicited_flag: true, override_flag },
4088            },
4089        );
4090
4091        // Neighbor should now be in REACHABLE, per RFC 4861 section 7.2.5.
4092        let now = bindings_ctx.now();
4093        assert_neighbor_state(
4094            &core_ctx,
4095            &mut bindings_ctx,
4096            DynamicNeighborState::Reachable(Reachable {
4097                link_address: LINK_ADDR1,
4098                last_confirmed_at: now,
4099            }),
4100            (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4101        );
4102    }
4103
4104    #[ip_test(I)]
4105    #[test_case(InitialState::Reachable; "reachable")]
4106    #[test_case(InitialState::Stale; "stale")]
4107    #[test_case(InitialState::Delay; "delay")]
4108    #[test_case(InitialState::Probe; "probe")]
4109    #[test_case(InitialState::Unreachable; "unreachable")]
4110    fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
4111        I: TestIpExt,
4112    >(
4113        initial_state: InitialState,
4114    ) {
4115        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4116
4117        // Initialize a neighbor.
4118        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4119
4120        // Handle an incoming unsolicited override confirmation with a different link address.
4121        NudHandler::handle_neighbor_update(
4122            &mut core_ctx,
4123            &mut bindings_ctx,
4124            &FakeLinkDeviceId,
4125            I::LOOKUP_ADDR1,
4126            DynamicNeighborUpdateSource::Confirmation {
4127                link_address: Some(LINK_ADDR2),
4128                flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4129            },
4130        );
4131
4132        // Neighbor should now be in STALE, per RFC 4861 section 7.2.5.
4133        assert_neighbor_state(
4134            &core_ctx,
4135            &mut bindings_ctx,
4136            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4137            Some(ExpectedEvent::Changed),
4138        );
4139    }
4140
4141    #[ip_test(I)]
4142    #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4143    #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4144    #[test_case(InitialState::Stale, true; "stale with override flag set")]
4145    #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4146    #[test_case(InitialState::Delay, true; "delay with override flag set")]
4147    #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4148    #[test_case(InitialState::Probe, true; "probe with override flag set")]
4149    #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4150    #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4151    #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4152    fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4153        initial_state: InitialState,
4154        override_flag: bool,
4155    ) {
4156        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4157
4158        // Initialize a neighbor.
4159        let expected_state =
4160            init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4161
4162        // Handle an incoming unsolicited confirmation with the same link address.
4163        NudHandler::handle_neighbor_update(
4164            &mut core_ctx,
4165            &mut bindings_ctx,
4166            &FakeLinkDeviceId,
4167            I::LOOKUP_ADDR1,
4168            DynamicNeighborUpdateSource::Confirmation {
4169                link_address: Some(LINK_ADDR1),
4170                flags: ConfirmationFlags { solicited_flag: false, override_flag },
4171            },
4172        );
4173
4174        // Neighbor should not have been updated.
4175        assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4176    }
4177
4178    #[ip_test(I)]
4179    #[test_case(InitialState::Reachable; "reachable")]
4180    #[test_case(InitialState::Stale; "stale")]
4181    #[test_case(InitialState::Delay; "delay")]
4182    #[test_case(InitialState::Probe; "probe")]
4183    #[test_case(InitialState::Unreachable; "unreachable")]
4184    fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4185        I: TestIpExt,
4186    >(
4187        initial_state: InitialState,
4188    ) {
4189        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4190
4191        // Initialize a neighbor.
4192        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4193
4194        // Handle an incoming solicited override confirmation with a different link address.
4195        NudHandler::handle_neighbor_update(
4196            &mut core_ctx,
4197            &mut bindings_ctx,
4198            &FakeLinkDeviceId,
4199            I::LOOKUP_ADDR1,
4200            DynamicNeighborUpdateSource::Confirmation {
4201                link_address: Some(LINK_ADDR2),
4202                flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4203            },
4204        );
4205
4206        // Neighbor should now be in REACHABLE, per RFC 4861 section 7.2.5.
4207        let now = bindings_ctx.now();
4208        assert_neighbor_state(
4209            &core_ctx,
4210            &mut bindings_ctx,
4211            DynamicNeighborState::Reachable(Reachable {
4212                link_address: LINK_ADDR2,
4213                last_confirmed_at: now,
4214            }),
4215            Some(ExpectedEvent::Changed),
4216        );
4217    }
4218
4219    #[ip_test(I)]
4220    fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4221        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4222
4223        // Initialize a neighbor in REACHABLE.
4224        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4225
4226        // Handle an incoming probe with the same link address.
4227        NudHandler::handle_neighbor_update(
4228            &mut core_ctx,
4229            &mut bindings_ctx,
4230            &FakeLinkDeviceId,
4231            I::LOOKUP_ADDR1,
4232            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4233        );
4234
4235        // Neighbor should still be in REACHABLE with the same link address.
4236        let now = bindings_ctx.now();
4237        assert_neighbor_state(
4238            &core_ctx,
4239            &mut bindings_ctx,
4240            DynamicNeighborState::Reachable(Reachable {
4241                link_address: LINK_ADDR1,
4242                last_confirmed_at: now,
4243            }),
4244            None,
4245        );
4246    }
4247
4248    #[ip_test(I)]
4249    #[test_case(true; "solicited")]
4250    #[test_case(false; "unsolicited")]
4251    fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4252        solicited_flag: bool,
4253    ) {
4254        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4255
4256        // Initialize a neighbor in REACHABLE.
4257        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4258
4259        // Handle an incoming non-override confirmation with a different link address.
4260        NudHandler::handle_neighbor_update(
4261            &mut core_ctx,
4262            &mut bindings_ctx,
4263            &FakeLinkDeviceId,
4264            I::LOOKUP_ADDR1,
4265            DynamicNeighborUpdateSource::Confirmation {
4266                link_address: Some(LINK_ADDR2),
4267                flags: ConfirmationFlags { override_flag: false, solicited_flag },
4268            },
4269        );
4270
4271        // Neighbor should now be in STALE, with the *same* link address as was
4272        // previously cached, per RFC 4861 section 7.2.5.
4273        assert_neighbor_state(
4274            &core_ctx,
4275            &mut bindings_ctx,
4276            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4277            Some(ExpectedEvent::Changed),
4278        );
4279    }
4280
4281    #[ip_test(I)]
4282    #[test_case(InitialState::Stale, true; "stale solicited")]
4283    #[test_case(InitialState::Stale, false; "stale unsolicited")]
4284    #[test_case(InitialState::Delay, true; "delay solicited")]
4285    #[test_case(InitialState::Delay, false; "delay unsolicited")]
4286    #[test_case(InitialState::Probe, true; "probe solicited")]
4287    #[test_case(InitialState::Probe, false; "probe unsolicited")]
4288    #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4289    #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4290    fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4291        initial_state: InitialState,
4292        solicited_flag: bool,
4293    ) {
4294        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4295
4296        // Initialize a neighbor.
4297        let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4298
4299        // Handle an incoming non-override confirmation with a different link address.
4300        NudHandler::handle_neighbor_update(
4301            &mut core_ctx,
4302            &mut bindings_ctx,
4303            &FakeLinkDeviceId,
4304            I::LOOKUP_ADDR1,
4305            DynamicNeighborUpdateSource::Confirmation {
4306                link_address: Some(LINK_ADDR2),
4307                flags: ConfirmationFlags { override_flag: false, solicited_flag },
4308            },
4309        );
4310
4311        // Neighbor should still be in the original state; the link address should *not*
4312        // have been updated.
4313        assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4314    }
4315
4316    #[ip_test(I)]
4317    fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4318        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4319
4320        // Initialize a neighbor in STALE.
4321        init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4322
4323        // Send a packet to the neighbor.
4324        let body = 1;
4325        assert_eq!(
4326            NudHandler::send_ip_packet_to_neighbor(
4327                &mut core_ctx,
4328                &mut bindings_ctx,
4329                &FakeLinkDeviceId,
4330                I::LOOKUP_ADDR1,
4331                Buf::new([body], ..),
4332                FakeTxMetadata::default(),
4333            ),
4334            Ok(())
4335        );
4336
4337        // Neighbor should be in DELAY.
4338        assert_neighbor_state(
4339            &core_ctx,
4340            &mut bindings_ctx,
4341            DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4342            Some(ExpectedEvent::Changed),
4343        );
4344        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4345            &mut bindings_ctx,
4346            [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4347        );
4348        assert_pending_frame_sent(
4349            &mut core_ctx,
4350            VecDeque::from([Buf::new(vec![body], ..)]),
4351            LINK_ADDR1,
4352        );
4353    }
4354
4355    #[ip_test(I)]
4356    #[test_case(InitialState::Delay,
4357                NudEvent::DelayFirstProbe;
4358                "delay to probe")]
4359    #[test_case(InitialState::Probe,
4360                NudEvent::RetransmitUnicastProbe;
4361                "probe retransmit unicast probe")]
4362    fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4363        initial_state: InitialState,
4364        expected_initial_event: NudEvent,
4365    ) {
4366        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4367
4368        // Initialize a neighbor.
4369        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4370
4371        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4372
4373        // If the neighbor started in DELAY, then after DELAY_FIRST_PROBE_TIME, the
4374        // neighbor should transition to PROBE and send out a unicast probe.
4375        //
4376        // If the neighbor started in PROBE, then after RetransTimer expires, the
4377        // neighbor should remain in PROBE and retransmit a unicast probe.
4378        let (time, transmit_counter) = match initial_state {
4379            InitialState::Delay => {
4380                (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4381            }
4382            InitialState::Probe => {
4383                (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4384            }
4385            other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4386        };
4387        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4388            &mut bindings_ctx,
4389            [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4390        );
4391        assert_eq!(
4392            bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4393            [NudTimerId::neighbor()]
4394        );
4395        assert_neighbor_state(
4396            &core_ctx,
4397            &mut bindings_ctx,
4398            DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4399            (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4400        );
4401        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4402            &mut bindings_ctx,
4403            [(
4404                I::LOOKUP_ADDR1,
4405                NudEvent::RetransmitUnicastProbe,
4406                core_ctx.inner.state.retrans_timer.get(),
4407            )],
4408        );
4409        assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4410    }
4411
4412    #[ip_test(I)]
4413    fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4414        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4415
4416        init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4417
4418        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4419        let timer_id = NudTimerId::neighbor();
4420
4421        // No multicast probes should be transmitted even after the retransmit timeout.
4422        assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4423        assert_eq!(core_ctx.inner.take_frames(), []);
4424
4425        // Send a packet and ensure that we also transmit a multicast probe.
4426        const BODY: u8 = 0x33;
4427        assert_eq!(
4428            NudHandler::send_ip_packet_to_neighbor(
4429                &mut core_ctx,
4430                &mut bindings_ctx,
4431                &FakeLinkDeviceId,
4432                I::LOOKUP_ADDR1,
4433                Buf::new([BODY], ..),
4434                FakeTxMetadata::default(),
4435            ),
4436            Ok(())
4437        );
4438        assert_eq!(
4439            core_ctx.inner.take_frames(),
4440            [
4441                (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4442                (
4443                    FakeNudMessageMeta::NeighborSolicitation {
4444                        lookup_addr: I::LOOKUP_ADDR1,
4445                        remote_link_addr: /* multicast */ None,
4446                    },
4447                    Vec::new()
4448                )
4449            ]
4450        );
4451
4452        let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4453            UnreachableMode::Backoff {
4454                probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4455                packet_sent: /* unused */ false,
4456            }
4457            .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4458            .get()
4459        };
4460
4461        const ITERATIONS: u8 = 2;
4462        for i in 1..ITERATIONS {
4463            let probes_sent = u32::from(i);
4464
4465            // Send another packet before the retransmit timer expires: only the packet
4466            // should be sent (not a probe), and the `packet_sent` flag should be set.
4467            assert_eq!(
4468                NudHandler::send_ip_packet_to_neighbor(
4469                    &mut core_ctx,
4470                    &mut bindings_ctx,
4471                    &FakeLinkDeviceId,
4472                    I::LOOKUP_ADDR1,
4473                    Buf::new([BODY + i], ..),
4474                    FakeTxMetadata::default(),
4475                ),
4476                Ok(())
4477            );
4478            assert_eq!(
4479                core_ctx.inner.take_frames(),
4480                [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4481            );
4482
4483            // Fast forward until the current retransmit timer should fire, taking
4484            // exponential backoff into account. Another multicast probe should be
4485            // transmitted and a new timer should be scheduled (backing off further) because
4486            // a packet was recently sent.
4487            assert_eq!(
4488                bindings_ctx.trigger_timers_for(
4489                    next_backoff_timer(&mut core_ctx, probes_sent),
4490                    &mut core_ctx,
4491                ),
4492                [timer_id]
4493            );
4494            assert_neighbor_probe_sent(&mut core_ctx, /* multicast */ None);
4495            bindings_ctx.timers.assert_timers_installed([(
4496                timer_id,
4497                bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4498            )]);
4499        }
4500
4501        // If no more packets are sent, no multicast probes should be transmitted even
4502        // after the next backoff timer expires.
4503        let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4504        assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4505        assert_eq!(core_ctx.inner.take_frames(), []);
4506        bindings_ctx.timers.assert_no_timers_installed();
4507
4508        // Finally, if another packet is sent, we resume transmitting multicast probes
4509        // and "reset" the exponential backoff.
4510        assert_eq!(
4511            NudHandler::send_ip_packet_to_neighbor(
4512                &mut core_ctx,
4513                &mut bindings_ctx,
4514                &FakeLinkDeviceId,
4515                I::LOOKUP_ADDR1,
4516                Buf::new([BODY], ..),
4517                FakeTxMetadata::default(),
4518            ),
4519            Ok(())
4520        );
4521        assert_eq!(
4522            core_ctx.inner.take_frames(),
4523            [
4524                (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4525                (
4526                    FakeNudMessageMeta::NeighborSolicitation {
4527                        lookup_addr: I::LOOKUP_ADDR1,
4528                        remote_link_addr: /* multicast */ None,
4529                    },
4530                    Vec::new()
4531                )
4532            ]
4533        );
4534        bindings_ctx.timers.assert_timers_installed([(
4535            timer_id,
4536            bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4537        )]);
4538    }
4539
4540    #[ip_test(I)]
4541    #[test_case(true; "solicited confirmation")]
4542    #[test_case(false; "unsolicited confirmation")]
4543    fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4544        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4545
4546        let link_address = Some(LINK_ADDR1);
4547        NudHandler::handle_neighbor_update(
4548            &mut core_ctx,
4549            &mut bindings_ctx,
4550            &FakeLinkDeviceId,
4551            I::LOOKUP_ADDR1,
4552            DynamicNeighborUpdateSource::Confirmation {
4553                link_address,
4554                flags: ConfirmationFlags { solicited_flag, override_flag: false },
4555            },
4556        );
4557        assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4558    }
4559
4560    #[ip_test(I)]
4561    #[test_case(true; "set_with_dynamic")]
4562    #[test_case(false; "set_with_static")]
4563    fn pending_frames<I: TestIpExt>(dynamic: bool) {
4564        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4565        assert_eq!(core_ctx.inner.take_frames(), []);
4566
4567        // Send up to the maximum number of pending frames to some neighbor
4568        // which requires resolution. This should cause all frames to be queued
4569        // pending resolution completion.
4570        const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4571        let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4572            .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4573            .collect::<VecDeque<_>>();
4574
4575        for (body, meta) in expected_pending_frames.iter() {
4576            assert_eq!(
4577                NudHandler::send_ip_packet_to_neighbor(
4578                    &mut core_ctx,
4579                    &mut bindings_ctx,
4580                    &FakeLinkDeviceId,
4581                    I::LOOKUP_ADDR1,
4582                    body.clone(),
4583                    meta.clone(),
4584                ),
4585                Ok(())
4586            );
4587        }
4588        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4589        // Should have only sent out a single neighbor probe message.
4590        assert_neighbor_probe_sent(&mut core_ctx, None);
4591        assert_neighbor_state(
4592            &core_ctx,
4593            &mut bindings_ctx,
4594            DynamicNeighborState::Incomplete(Incomplete {
4595                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4596                pending_frames: expected_pending_frames.clone(),
4597                notifiers: Vec::new(),
4598                _marker: PhantomData,
4599            }),
4600            Some(ExpectedEvent::Added),
4601        );
4602
4603        // The next frame should be dropped.
4604        assert_eq!(
4605            NudHandler::send_ip_packet_to_neighbor(
4606                &mut core_ctx,
4607                &mut bindings_ctx,
4608                &FakeLinkDeviceId,
4609                I::LOOKUP_ADDR1,
4610                Buf::new([123], ..),
4611                FakeTxMetadata::default(),
4612            ),
4613            Ok(())
4614        );
4615        assert_eq!(core_ctx.inner.take_frames(), []);
4616        assert_neighbor_state(
4617            &core_ctx,
4618            &mut bindings_ctx,
4619            DynamicNeighborState::Incomplete(Incomplete {
4620                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4621                pending_frames: expected_pending_frames.clone(),
4622                notifiers: Vec::new(),
4623                _marker: PhantomData,
4624            }),
4625            None,
4626        );
4627
4628        // Completing resolution should result in all queued packets being sent.
4629        if dynamic {
4630            NudHandler::handle_neighbor_update(
4631                &mut core_ctx,
4632                &mut bindings_ctx,
4633                &FakeLinkDeviceId,
4634                I::LOOKUP_ADDR1,
4635                DynamicNeighborUpdateSource::Confirmation {
4636                    link_address: Some(LINK_ADDR1),
4637                    flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4638                },
4639            );
4640            core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4641                &mut bindings_ctx,
4642                [(
4643                    I::LOOKUP_ADDR1,
4644                    NudEvent::ReachableTime,
4645                    core_ctx.inner.base_reachable_time().get(),
4646                )],
4647            );
4648            let last_confirmed_at = bindings_ctx.now();
4649            assert_neighbor_state(
4650                &core_ctx,
4651                &mut bindings_ctx,
4652                DynamicNeighborState::Reachable(Reachable {
4653                    link_address: LINK_ADDR1,
4654                    last_confirmed_at,
4655                }),
4656                Some(ExpectedEvent::Changed),
4657            );
4658        } else {
4659            init_static_neighbor(
4660                &mut core_ctx,
4661                &mut bindings_ctx,
4662                LINK_ADDR1,
4663                ExpectedEvent::Changed,
4664            );
4665            bindings_ctx.timers.assert_no_timers_installed();
4666        }
4667        assert_eq!(
4668            core_ctx.inner.take_frames(),
4669            expected_pending_frames
4670                .into_iter()
4671                .map(|(p, FakeTxMetadata)| (
4672                    FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4673                    p.as_ref().to_vec()
4674                ))
4675                .collect::<Vec<_>>()
4676        );
4677    }
4678
4679    #[ip_test(I)]
4680    fn static_neighbor<I: TestIpExt>() {
4681        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4682
4683        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4684        bindings_ctx.timers.assert_no_timers_installed();
4685        assert_eq!(core_ctx.inner.take_frames(), []);
4686        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4687
4688        // Dynamic entries should not overwrite static entries.
4689        NudHandler::handle_neighbor_update(
4690            &mut core_ctx,
4691            &mut bindings_ctx,
4692            &FakeLinkDeviceId,
4693            I::LOOKUP_ADDR1,
4694            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4695        );
4696        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4697
4698        delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4699
4700        let neighbors = &core_ctx.nud.state.neighbors;
4701        assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4702    }
4703
4704    #[ip_test(I)]
4705    fn dynamic_neighbor<I: TestIpExt>() {
4706        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4707
4708        init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4709        bindings_ctx.timers.assert_no_timers_installed();
4710        assert_eq!(core_ctx.inner.take_frames(), []);
4711        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4712
4713        // Dynamic entries may be overwritten by new dynamic entries.
4714        NudHandler::handle_neighbor_update(
4715            &mut core_ctx,
4716            &mut bindings_ctx,
4717            &FakeLinkDeviceId,
4718            I::LOOKUP_ADDR1,
4719            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4720        );
4721        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4722        assert_eq!(core_ctx.inner.take_frames(), []);
4723        assert_neighbor_state(
4724            &core_ctx,
4725            &mut bindings_ctx,
4726            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4727            Some(ExpectedEvent::Changed),
4728        );
4729
4730        // A static entry may overwrite a dynamic entry.
4731        init_static_neighbor_with_ip(
4732            &mut core_ctx,
4733            &mut bindings_ctx,
4734            I::LOOKUP_ADDR1,
4735            LINK_ADDR3,
4736            ExpectedEvent::Changed,
4737        );
4738        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4739        assert_eq!(core_ctx.inner.take_frames(), []);
4740    }
4741
4742    #[ip_test(I)]
4743    fn send_solicitation_on_lookup<I: TestIpExt>() {
4744        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4745        bindings_ctx.timers.assert_no_timers_installed();
4746        assert_eq!(core_ctx.inner.take_frames(), []);
4747
4748        let mut pending_frames = VecDeque::new();
4749
4750        queue_ip_packet_to_unresolved_neighbor(
4751            &mut core_ctx,
4752            &mut bindings_ctx,
4753            I::LOOKUP_ADDR1,
4754            &mut pending_frames,
4755            1,
4756            true, /* expect_event */
4757        );
4758        assert_neighbor_probe_sent(&mut core_ctx, None);
4759
4760        queue_ip_packet_to_unresolved_neighbor(
4761            &mut core_ctx,
4762            &mut bindings_ctx,
4763            I::LOOKUP_ADDR1,
4764            &mut pending_frames,
4765            2,
4766            false, /* expect_event */
4767        );
4768        assert_eq!(core_ctx.inner.take_frames(), []);
4769
4770        // Complete link resolution.
4771        NudHandler::handle_neighbor_update(
4772            &mut core_ctx,
4773            &mut bindings_ctx,
4774            &FakeLinkDeviceId,
4775            I::LOOKUP_ADDR1,
4776            DynamicNeighborUpdateSource::Confirmation {
4777                link_address: Some(LINK_ADDR1),
4778                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4779            },
4780        );
4781        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4782
4783        let now = bindings_ctx.now();
4784        assert_neighbor_state(
4785            &core_ctx,
4786            &mut bindings_ctx,
4787            DynamicNeighborState::Reachable(Reachable {
4788                link_address: LINK_ADDR1,
4789                last_confirmed_at: now,
4790            }),
4791            Some(ExpectedEvent::Changed),
4792        );
4793        assert_eq!(
4794            core_ctx.inner.take_frames(),
4795            pending_frames
4796                .into_iter()
4797                .map(|f| (
4798                    FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4799                    f.as_ref().to_vec(),
4800                ))
4801                .collect::<Vec<_>>()
4802        );
4803    }
4804
4805    #[ip_test(I)]
4806    fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4807        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4808        bindings_ctx.timers.assert_no_timers_installed();
4809        assert_eq!(core_ctx.inner.take_frames(), []);
4810
4811        let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4812
4813        let timer_id = NudTimerId::neighbor();
4814
4815        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4816        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4817
4818        for i in 1..=max_multicast_solicit {
4819            assert_neighbor_state(
4820                &core_ctx,
4821                &mut bindings_ctx,
4822                DynamicNeighborState::Incomplete(Incomplete {
4823                    transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4824                    pending_frames: pending_frames
4825                        .iter()
4826                        .cloned()
4827                        .map(|b| (b, FakeTxMetadata::default()))
4828                        .collect(),
4829                    notifiers: Vec::new(),
4830                    _marker: PhantomData,
4831                }),
4832                None,
4833            );
4834
4835            bindings_ctx
4836                .timers
4837                .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4838            assert_neighbor_probe_sent(&mut core_ctx, /* multicast */ None);
4839
4840            assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4841        }
4842
4843        // The neighbor entry should have been removed.
4844        assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4845        bindings_ctx.timers.assert_no_timers_installed();
4846
4847        // The ICMP destination unreachable error sent as a result of solicitation failure
4848        // will be dropped because the packets pending address resolution in this test
4849        // is not a valid IP packet.
4850        assert_eq!(core_ctx.inner.take_frames(), []);
4851        assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4852    }
4853
4854    #[ip_test(I)]
4855    fn solicitation_failure_in_probe<I: TestIpExt>() {
4856        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4857        bindings_ctx.timers.assert_no_timers_installed();
4858        assert_eq!(core_ctx.inner.take_frames(), []);
4859
4860        init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4861
4862        let timer_id = NudTimerId::neighbor();
4863        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4864        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4865        for i in 1..=max_unicast_solicit {
4866            assert_neighbor_state(
4867                &core_ctx,
4868                &mut bindings_ctx,
4869                DynamicNeighborState::Probe(Probe {
4870                    transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4871                    link_address: LINK_ADDR1,
4872                }),
4873                None,
4874            );
4875
4876            bindings_ctx
4877                .timers
4878                .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4879            assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4880
4881            assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4882        }
4883
4884        assert_neighbor_state(
4885            &core_ctx,
4886            &mut bindings_ctx,
4887            DynamicNeighborState::Unreachable(Unreachable {
4888                link_address: LINK_ADDR1,
4889                mode: UnreachableMode::WaitingForPacketSend,
4890            }),
4891            Some(ExpectedEvent::Changed),
4892        );
4893        bindings_ctx.timers.assert_no_timers_installed();
4894        assert_eq!(core_ctx.inner.take_frames(), []);
4895    }
4896
4897    #[ip_test(I)]
4898    fn flush_entries<I: TestIpExt>() {
4899        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4900        bindings_ctx.timers.assert_no_timers_installed();
4901        assert_eq!(core_ctx.inner.take_frames(), []);
4902
4903        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4904        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4905        let pending_frames = init_incomplete_neighbor_with_ip(
4906            &mut core_ctx,
4907            &mut bindings_ctx,
4908            I::LOOKUP_ADDR3,
4909            true,
4910        );
4911        let pending_frames =
4912            pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4913
4914        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4915        assert_eq!(
4916            core_ctx.nud.state.neighbors,
4917            HashMap::from([
4918                (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4919                (
4920                    I::LOOKUP_ADDR2,
4921                    NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4922                        link_address: LINK_ADDR2,
4923                    })),
4924                ),
4925                (
4926                    I::LOOKUP_ADDR3,
4927                    NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4928                        transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4929                        pending_frames,
4930                        notifiers: Vec::new(),
4931                        _marker: PhantomData,
4932                    })),
4933                ),
4934            ]),
4935        );
4936        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4937            &mut bindings_ctx,
4938            [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4939        );
4940
4941        // Flushing the table should clear all entries (dynamic and static) and timers.
4942        NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4943        let neighbors = &core_ctx.nud.state.neighbors;
4944        assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4945        assert_eq!(
4946            bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4947            [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4948                .into_iter()
4949                .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4950                .collect(),
4951        );
4952        bindings_ctx.timers.assert_no_timers_installed();
4953    }
4954
4955    #[ip_test(I)]
4956    fn delete_dynamic_entry<I: TestIpExt>() {
4957        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4958        bindings_ctx.timers.assert_no_timers_installed();
4959        assert_eq!(core_ctx.inner.take_frames(), []);
4960
4961        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4962        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4963
4964        delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4965
4966        // Entry should be removed and timer cancelled.
4967        let neighbors = &core_ctx.nud.state.neighbors;
4968        assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4969        bindings_ctx.timers.assert_no_timers_installed();
4970    }
4971
4972    #[ip_test(I)]
4973    #[test_case(InitialState::Reachable; "reachable neighbor")]
4974    #[test_case(InitialState::Stale; "stale neighbor")]
4975    #[test_case(InitialState::Delay; "delay neighbor")]
4976    #[test_case(InitialState::Probe; "probe neighbor")]
4977    #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4978    fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4979        let mut ctx = new_context::<I>();
4980        ctx.bindings_ctx.timers.assert_no_timers_installed();
4981        assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4982
4983        let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4984
4985        let link_addr = assert_matches!(
4986            NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4987                &FakeLinkDeviceId,
4988                &I::LOOKUP_ADDR1,
4989            ),
4990            LinkResolutionResult::Resolved(addr) => addr
4991        );
4992        assert_eq!(link_addr, LINK_ADDR1);
4993        if initial_state == InitialState::Stale {
4994            assert_eq!(
4995                ctx.bindings_ctx.take_events(),
4996                [Event::changed(
4997                    &FakeLinkDeviceId,
4998                    EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4999                    I::LOOKUP_ADDR1,
5000                    ctx.bindings_ctx.now(),
5001                )],
5002            );
5003        }
5004    }
5005
5006    enum ResolutionSuccess {
5007        Confirmation,
5008        StaticEntryAdded,
5009    }
5010
5011    #[ip_test(I)]
5012    #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
5013    #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
5014    fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
5015        let mut ctx = new_context::<I>();
5016
5017        let observers = (0..10)
5018            .map(|_| {
5019                let observer = assert_matches!(
5020                    NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5021                        &FakeLinkDeviceId,
5022                        &I::LOOKUP_ADDR1,
5023                    ),
5024                    LinkResolutionResult::Pending(observer) => observer
5025                );
5026                assert_eq!(*observer.lock(), None);
5027                observer
5028            })
5029            .collect::<Vec<_>>();
5030        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5031        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5032
5033        // We should have initialized an incomplete neighbor and sent a neighbor probe
5034        // to attempt resolution.
5035        assert_neighbor_state(
5036            core_ctx,
5037            bindings_ctx,
5038            DynamicNeighborState::Incomplete(Incomplete {
5039                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5040                pending_frames: VecDeque::new(),
5041                // NB: notifiers is not checked for equality.
5042                notifiers: Vec::new(),
5043                _marker: PhantomData,
5044            }),
5045            Some(ExpectedEvent::Added),
5046        );
5047        assert_neighbor_probe_sent(core_ctx, /* multicast */ None);
5048
5049        match reason {
5050            ResolutionSuccess::Confirmation => {
5051                // Complete neighbor resolution with an incoming neighbor confirmation.
5052                NudHandler::handle_neighbor_update(
5053                    core_ctx,
5054                    bindings_ctx,
5055                    &FakeLinkDeviceId,
5056                    I::LOOKUP_ADDR1,
5057                    DynamicNeighborUpdateSource::Confirmation {
5058                        link_address: Some(LINK_ADDR1),
5059                        flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5060                    },
5061                );
5062                let now = bindings_ctx.now();
5063                assert_neighbor_state(
5064                    core_ctx,
5065                    bindings_ctx,
5066                    DynamicNeighborState::Reachable(Reachable {
5067                        link_address: LINK_ADDR1,
5068                        last_confirmed_at: now,
5069                    }),
5070                    Some(ExpectedEvent::Changed),
5071                );
5072            }
5073            ResolutionSuccess::StaticEntryAdded => {
5074                init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
5075                assert_eq!(
5076                    core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
5077                    Some(&NeighborState::Static(LINK_ADDR1))
5078                );
5079            }
5080        }
5081
5082        // Each observer should have been notified of successful link resolution.
5083        for observer in observers {
5084            assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
5085        }
5086    }
5087
5088    enum ResolutionFailure {
5089        Timeout,
5090        Removed,
5091    }
5092
5093    #[ip_test(I)]
5094    #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
5095    #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
5096    fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
5097        let mut ctx = new_context::<I>();
5098
5099        let observers = (0..10)
5100            .map(|_| {
5101                let observer = assert_matches!(
5102                    NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5103                        &FakeLinkDeviceId,
5104                        &I::LOOKUP_ADDR1,
5105                    ),
5106                    LinkResolutionResult::Pending(observer) => observer
5107                );
5108                assert_eq!(*observer.lock(), None);
5109                observer
5110            })
5111            .collect::<Vec<_>>();
5112
5113        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5114        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5115
5116        // We should have initialized an incomplete neighbor and sent a neighbor probe
5117        // to attempt resolution.
5118        assert_neighbor_state(
5119            core_ctx,
5120            bindings_ctx,
5121            DynamicNeighborState::Incomplete(Incomplete {
5122                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5123                pending_frames: VecDeque::new(),
5124                // NB: notifiers is not checked for equality.
5125                notifiers: Vec::new(),
5126                _marker: PhantomData,
5127            }),
5128            Some(ExpectedEvent::Added),
5129        );
5130        assert_neighbor_probe_sent(core_ctx, /* multicast */ None);
5131
5132        match reason {
5133            ResolutionFailure::Timeout => {
5134                // Wait until neighbor resolution exceeds its maximum probe retransmits and
5135                // times out.
5136                for _ in 1..=max_multicast_solicit {
5137                    let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5138                    assert_eq!(
5139                        bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5140                        [NudTimerId::neighbor()]
5141                    );
5142                }
5143            }
5144            ResolutionFailure::Removed => {
5145                // Flush the neighbor table so the entry is removed.
5146                NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5147            }
5148        }
5149
5150        assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5151        // Each observer should have been notified of link resolution failure.
5152        for observer in observers {
5153            assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5154        }
5155    }
5156
5157    #[ip_test(I)]
5158    #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5159    #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5160    #[test_case(InitialState::Stale, true; "stale neighbor")]
5161    #[test_case(InitialState::Delay, true; "delay neighbor")]
5162    #[test_case(InitialState::Probe, true; "probe neighbor")]
5163    #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5164    fn upper_layer_confirmation<I: TestIpExt>(
5165        initial_state: InitialState,
5166        should_transition_to_reachable: bool,
5167    ) {
5168        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5169        let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5170
5171        let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5172
5173        confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5174
5175        if !should_transition_to_reachable {
5176            assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5177            return;
5178        }
5179
5180        // Neighbor should have transitioned to REACHABLE and scheduled a timer.
5181        let now = bindings_ctx.now();
5182        assert_neighbor_state(
5183            &core_ctx,
5184            &mut bindings_ctx,
5185            DynamicNeighborState::Reachable(Reachable {
5186                link_address: LINK_ADDR1,
5187                last_confirmed_at: now,
5188            }),
5189            (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5190        );
5191        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5192            &mut bindings_ctx,
5193            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5194        );
5195
5196        // Advance the clock by less than ReachableTime and confirm reachability again.
5197        // The existing timer should not have been rescheduled; only the entry's
5198        // `last_confirmed_at` timestamp should have been updated.
5199        bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5200        confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5201        let now = bindings_ctx.now();
5202        assert_neighbor_state(
5203            &core_ctx,
5204            &mut bindings_ctx,
5205            DynamicNeighborState::Reachable(Reachable {
5206                link_address: LINK_ADDR1,
5207                last_confirmed_at: now,
5208            }),
5209            None,
5210        );
5211        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5212            &mut bindings_ctx,
5213            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5214        );
5215
5216        // When the original timer eventually does expire, a new timer should be
5217        // scheduled based on when the entry was last confirmed.
5218        assert_eq!(
5219            bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5220            [NudTimerId::neighbor()]
5221        );
5222        let now = bindings_ctx.now();
5223        assert_neighbor_state(
5224            &core_ctx,
5225            &mut bindings_ctx,
5226            DynamicNeighborState::Reachable(Reachable {
5227                link_address: LINK_ADDR1,
5228                last_confirmed_at: now - base_reachable_time / 2,
5229            }),
5230            None,
5231        );
5232
5233        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5234            &mut bindings_ctx,
5235            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5236        );
5237
5238        // When *that* timer fires, if the entry has not been confirmed since it was
5239        // scheduled, it should move into STALE.
5240        assert_eq!(
5241            bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5242            [NudTimerId::neighbor()]
5243        );
5244        assert_neighbor_state(
5245            &core_ctx,
5246            &mut bindings_ctx,
5247            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5248            Some(ExpectedEvent::Changed),
5249        );
5250        bindings_ctx.timers.assert_no_timers_installed();
5251    }
5252
5253    fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5254        I::map_ip_out(
5255            i,
5256            |i| {
5257                let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5258                let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5259                SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5260            },
5261            |i| {
5262                let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5263                let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5264                SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5265            },
5266        )
5267    }
5268
5269    #[ip_test(I)]
5270    fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5271        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5272
5273        // Add `GC_THRESHOLD` STALE dynamic neighbors and `GC_THRESHOLD` static
5274        // neighbors to the neighbor table, interleaved to avoid accidental
5275        // behavior re: insertion order.
5276        for i in 0..GC_THRESHOLD * 2 {
5277            if i % 2 == 0 {
5278                init_stale_neighbor_with_ip(
5279                    &mut core_ctx,
5280                    &mut bindings_ctx,
5281                    generate_ip_addr::<I>(i),
5282                    LINK_ADDR1,
5283                );
5284            } else {
5285                init_static_neighbor_with_ip(
5286                    &mut core_ctx,
5287                    &mut bindings_ctx,
5288                    generate_ip_addr::<I>(i),
5289                    LINK_ADDR1,
5290                    ExpectedEvent::Added,
5291                );
5292            }
5293        }
5294        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD * 2);
5295
5296        // Perform GC, and ensure that only the dynamic entries are discarded.
5297        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5298        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5299        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5300        for event in bindings_ctx.take_events() {
5301            assert_matches!(event, Event {
5302                device,
5303                addr: _,
5304                kind,
5305                at,
5306            } => {
5307                assert_eq!(kind, EventKind::Removed);
5308                assert_eq!(device, FakeLinkDeviceId);
5309                assert_eq!(at, bindings_ctx.now());
5310            });
5311        }
5312        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5313        for (_, neighbor) in core_ctx.nud.state.neighbors {
5314            assert_matches!(neighbor, NeighborState::Static(_));
5315        }
5316    }
5317
5318    #[ip_test(I)]
5319    fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5320        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5321
5322        // Add enough static entries that the NUD table is near maximum capacity.
5323        for i in 0..GC_THRESHOLD - 1 {
5324            init_static_neighbor_with_ip(
5325                &mut core_ctx,
5326                &mut bindings_ctx,
5327                generate_ip_addr::<I>(i),
5328                LINK_ADDR1,
5329                ExpectedEvent::Added,
5330            );
5331        }
5332
5333        // Add a STALE entry...
5334        let stale_entry = generate_ip_addr::<I>(GC_THRESHOLD - 1);
5335        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5336        // ...and a REACHABLE entry.
5337        let reachable_entry = generate_ip_addr::<I>(GC_THRESHOLD);
5338        init_reachable_neighbor_with_ip(
5339            &mut core_ctx,
5340            &mut bindings_ctx,
5341            reachable_entry,
5342            LINK_ADDR1,
5343        );
5344
5345        // Perform GC, and ensure that the REACHABLE entry was retained.
5346        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5347        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5348        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5349        super::testutil::assert_dynamic_neighbor_state(
5350            &mut core_ctx,
5351            FakeLinkDeviceId,
5352            reachable_entry,
5353            DynamicNeighborState::Reachable(Reachable {
5354                link_address: LINK_ADDR1,
5355                last_confirmed_at: bindings_ctx.now(),
5356            }),
5357        );
5358        assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5359    }
5360
5361    #[ip_test(I)]
5362    fn is_still_dirty_after_garbage_collection<I: TestIpExt>() {
5363        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5364
5365        // Add enough STALE entries to trigger garbage collection.
5366        for i in 0..GC_THRESHOLD + 1 {
5367            init_stale_neighbor_with_ip(
5368                &mut core_ctx,
5369                &mut bindings_ctx,
5370                generate_ip_addr::<I>(i),
5371                LINK_ADDR1,
5372            );
5373        }
5374
5375        // Perform GC, and ensure that the `is_dirty` is still set (because not
5376        // all STALE entries were removed).
5377        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5378        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5379        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5380        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5381        let events = bindings_ctx.take_events();
5382        let removed_event = assert_matches!(&events[..], [event] => event);
5383        assert_eq!(removed_event.kind, EventKind::Removed);
5384    }
5385
5386    #[ip_test(I)]
5387    fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5388        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5389        // Pretend we just ran GC so the next pass will be scheduled after a delay.
5390        core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5391
5392        // Fill the neighbor table to maximum capacity with static entries.
5393        for i in 0..GC_THRESHOLD {
5394            init_static_neighbor_with_ip(
5395                &mut core_ctx,
5396                &mut bindings_ctx,
5397                generate_ip_addr::<I>(i),
5398                LINK_ADDR1,
5399                ExpectedEvent::Added,
5400            );
5401        }
5402
5403        // Add a STALE neighbor entry to the table, which should trigger a GC run
5404        // because it pushes the size of the table over the max.
5405        init_stale_neighbor_with_ip(
5406            &mut core_ctx,
5407            &mut bindings_ctx,
5408            generate_ip_addr::<I>(GC_THRESHOLD + 1),
5409            LINK_ADDR1,
5410        );
5411        let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5412        bindings_ctx
5413            .timers
5414            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5415
5416        // Advance the clock by less than the GC interval and add another STALE entry to
5417        // trigger GC again. The existing GC timer should not have been rescheduled
5418        // given a GC pass is already pending.
5419        bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5420        init_stale_neighbor_with_ip(
5421            &mut core_ctx,
5422            &mut bindings_ctx,
5423            generate_ip_addr::<I>(GC_THRESHOLD + 2),
5424            LINK_ADDR1,
5425        );
5426        bindings_ctx
5427            .timers
5428            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5429    }
5430
5431    #[ip_test(I)]
5432    fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5433        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5434        // Pretend we just ran GC so the next pass will be scheduled after a delay.
5435        core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5436
5437        // Fill the neighbor table to maximum capacity.
5438        for i in 0..GC_THRESHOLD {
5439            init_static_neighbor_with_ip(
5440                &mut core_ctx,
5441                &mut bindings_ctx,
5442                generate_ip_addr::<I>(i),
5443                LINK_ADDR1,
5444                ExpectedEvent::Added,
5445            );
5446        }
5447        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5448
5449        // Add a dynamic neighbor entry to the table and transition it to the
5450        // UNREACHABLE state. This should trigger a GC run.
5451        init_unreachable_neighbor_with_ip(
5452            &mut core_ctx,
5453            &mut bindings_ctx,
5454            generate_ip_addr::<I>(GC_THRESHOLD),
5455            LINK_ADDR1,
5456        );
5457        let expected_gc_time =
5458            core_ctx.nud.state.gc_state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5459        bindings_ctx
5460            .timers
5461            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5462
5463        // Add a new entry and transition it to UNREACHABLE. The existing GC timer
5464        // should not have been rescheduled given a GC pass is already pending.
5465        init_unreachable_neighbor_with_ip(
5466            &mut core_ctx,
5467            &mut bindings_ctx,
5468            generate_ip_addr::<I>(GC_THRESHOLD + 1),
5469            LINK_ADDR1,
5470        );
5471        bindings_ctx
5472            .timers
5473            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5474    }
5475
5476    #[ip_test(I)]
5477    fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5478        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5479
5480        // Fill the neighbor table to maximum capacity with static entries.
5481        for i in 0..GC_THRESHOLD {
5482            init_static_neighbor_with_ip(
5483                &mut core_ctx,
5484                &mut bindings_ctx,
5485                generate_ip_addr::<I>(i),
5486                LINK_ADDR1,
5487                ExpectedEvent::Added,
5488            );
5489        }
5490        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5491
5492        let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5493            &mut core_ctx,
5494            &mut bindings_ctx,
5495            generate_ip_addr::<I>(GC_THRESHOLD),
5496            true,
5497        );
5498        assert_eq!(
5499            bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5500            None
5501        );
5502    }
5503
5504    #[ip_test(I)]
5505    fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5506        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5507
5508        // Initialize a neighbor in STALE.
5509        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5510
5511        // Receive a neighbor confirmation that omits the target link-layer address
5512        // option. Because we have a cached link-layer address, we should still process
5513        // the confirmation (updating the neighbor to REACHABLE).
5514        NudHandler::handle_neighbor_update(
5515            &mut core_ctx,
5516            &mut bindings_ctx,
5517            &FakeLinkDeviceId,
5518            I::LOOKUP_ADDR1,
5519            DynamicNeighborUpdateSource::Confirmation {
5520                link_address: None,
5521                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5522            },
5523        );
5524        let now = bindings_ctx.now();
5525        assert_neighbor_state(
5526            &core_ctx,
5527            &mut bindings_ctx,
5528            DynamicNeighborState::Reachable(Reachable {
5529                link_address: LINK_ADDR1,
5530                last_confirmed_at: now,
5531            }),
5532            Some(ExpectedEvent::Changed),
5533        );
5534    }
5535
5536    #[ip_test(I)]
5537    #[test_case(InitialState::Stale; "stale")]
5538    #[test_case(InitialState::Reachable; "reachable")]
5539    #[test_case(InitialState::Delay; "delay")]
5540    #[test_case(InitialState::Unreachable; "unreachable")]
5541    fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5542        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5543
5544        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5545
5546        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5547        let result = neighbor.enter_probe(
5548            &mut core_ctx.inner.state,
5549            &mut bindings_ctx,
5550            &mut core_ctx.nud.state.timer_heap,
5551            I::LOOKUP_ADDR1,
5552            &FakeLinkDeviceId,
5553        );
5554
5555        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5556        assert_matches!(result, Ok(Some(LINK_ADDR1)));
5557        assert_neighbor_state(
5558            &core_ctx,
5559            &mut bindings_ctx,
5560            DynamicNeighborState::Probe(Probe {
5561                link_address: LINK_ADDR1,
5562                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5563            }),
5564            Some(ExpectedEvent::Changed),
5565        );
5566    }
5567
5568    #[ip_test(I)]
5569    fn enter_probe_from_static_state<I: TestIpExt>() {
5570        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5571
5572        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5573
5574        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5575        let result = neighbor.enter_probe(
5576            &mut core_ctx.inner.state,
5577            &mut bindings_ctx,
5578            &mut core_ctx.nud.state.timer_heap,
5579            I::LOOKUP_ADDR1,
5580            &FakeLinkDeviceId,
5581        );
5582
5583        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5584        assert_matches!(result, Ok(Some(LINK_ADDR1)));
5585        assert_neighbor_state(
5586            &core_ctx,
5587            &mut bindings_ctx,
5588            DynamicNeighborState::Probe(Probe {
5589                link_address: LINK_ADDR1,
5590                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5591            }),
5592            Some(ExpectedEvent::Changed),
5593        );
5594    }
5595
5596    #[ip_test(I)]
5597    fn enter_probe_from_probe_state<I: TestIpExt>() {
5598        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5599
5600        init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5601
5602        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5603        let result = neighbor.enter_probe(
5604            &mut core_ctx.inner.state,
5605            &mut bindings_ctx,
5606            &mut core_ctx.nud.state.timer_heap,
5607            I::LOOKUP_ADDR1,
5608            &FakeLinkDeviceId,
5609        );
5610
5611        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5612        assert_matches!(result, Ok(None)); // No new probe should be transmitted.
5613        assert_neighbor_state(
5614            &core_ctx,
5615            &mut bindings_ctx,
5616            DynamicNeighborState::Probe(Probe {
5617                link_address: LINK_ADDR1,
5618                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5619            }),
5620            None, // No event should be generated.
5621        );
5622    }
5623
5624    #[ip_test(I)]
5625    fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5626        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5627
5628        let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5629
5630        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5631        let result = neighbor.enter_probe(
5632            &mut core_ctx.inner.state,
5633            &mut bindings_ctx,
5634            &mut core_ctx.nud.state.timer_heap,
5635            I::LOOKUP_ADDR1,
5636            &FakeLinkDeviceId,
5637        );
5638
5639        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5640        assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5641        assert_neighbor_state(
5642            &core_ctx,
5643            &mut bindings_ctx,
5644            DynamicNeighborState::Incomplete(Incomplete {
5645                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5646                pending_frames: pending_frames
5647                    .iter()
5648                    .cloned()
5649                    .map(|b| (b, FakeTxMetadata::default()))
5650                    .collect(),
5651                notifiers: Vec::new(),
5652                _marker: PhantomData,
5653            }),
5654            None, // No event should be generated.
5655        );
5656    }
5657
5658    /// Various ways a neighbor entry could be inserted to exercise table full
5659    /// conditions.
5660    enum InsertMethod {
5661        ResolveLinkAddr,
5662        InsertStaticEntry,
5663        NeighborUpdate,
5664        SendIpPacket,
5665    }
5666
5667    impl InsertMethod {
5668        fn insert<I: TestIpExt>(
5669            &self,
5670            context: &mut CtxPair<&mut FakeCoreCtxImpl<I>, &mut FakeBindingsCtxImpl<I>>,
5671            link_address: UnicastAddr<FakeLinkAddress>,
5672            ip: SpecifiedAddr<I::Addr>,
5673            expect_err: bool,
5674        ) {
5675            match self {
5676                Self::ResolveLinkAddr => {
5677                    let result =
5678                        NeighborApi::new(context).resolve_link_addr(&FakeLinkDeviceId, &ip);
5679                    let pending = assert_matches!(
5680                        result, LinkResolutionResult::Pending(pending) => pending
5681                    );
5682                    if expect_err {
5683                        assert_matches!(
5684                            pending.lock().as_ref(),
5685                            Some(Err(AddressResolutionFailed))
5686                        );
5687                    } else {
5688                        assert_matches!(pending.lock().as_ref(), None, "should not be notified");
5689                    }
5690                }
5691                Self::InsertStaticEntry => {
5692                    let result = NeighborApi::new(context).insert_static_entry(
5693                        &FakeLinkDeviceId,
5694                        *ip,
5695                        link_address,
5696                    );
5697                    if expect_err {
5698                        assert_eq!(result, Err(StaticNeighborInsertionError::TableFull))
5699                    } else {
5700                        assert_eq!(result, Ok(()))
5701                    }
5702                }
5703                Self::NeighborUpdate => {
5704                    let CtxPair { core_ctx, bindings_ctx } = context;
5705                    NudHandler::handle_neighbor_update(
5706                        *core_ctx,
5707                        *bindings_ctx,
5708                        &FakeLinkDeviceId,
5709                        ip,
5710                        DynamicNeighborUpdateSource::Probe { link_address },
5711                    );
5712                    // NB: ignore `expect_err` because errors aren't observable
5713                    // on `handle_neighbor_update`.
5714                }
5715                Self::SendIpPacket => {
5716                    let CtxPair { core_ctx, bindings_ctx } = context;
5717                    let packet = Buf::new([0; 10], ..);
5718                    let result = NudHandler::send_ip_packet_to_neighbor(
5719                        *core_ctx,
5720                        *bindings_ctx,
5721                        &FakeLinkDeviceId,
5722                        ip,
5723                        packet,
5724                        FakeTxMetadata::default(),
5725                    );
5726                    if expect_err {
5727                        assert_matches!(
5728                            result,
5729                            Err(ErrorAndSerializer {error, ..})
5730                            if error == SendFrameErrorReason::AddressResolutionFailed
5731                        );
5732                    } else {
5733                        assert_matches!(result, Ok(()));
5734                    }
5735                }
5736            }
5737        }
5738    }
5739
5740    // Verify that a full neighbor table does not allow adding new neighbors.
5741    #[ip_test(I)]
5742    #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5743    #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5744    #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5745    #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5746    fn neighbor_table_max_entries_enforced<I: TestIpExt>(insert_method: InsertMethod) {
5747        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5748        for i in 0..MAX_ENTRIES {
5749            // NB: Static entries will not be discardable.
5750            init_static_neighbor_with_ip(
5751                &mut core_ctx,
5752                &mut bindings_ctx,
5753                generate_ip_addr::<I>(i),
5754                LINK_ADDR1,
5755                ExpectedEvent::Added,
5756            );
5757            assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5758        }
5759
5760        // Attempting to insert an entry beyond the limit should fail.
5761        insert_method.insert(
5762            &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5763            LINK_ADDR1,
5764            generate_ip_addr::<I>(MAX_ENTRIES),
5765            true, /* expect_err */
5766        );
5767        assert_eq!(bindings_ctx.take_events(), []);
5768        assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5769    }
5770
5771    // Verify that a full neighbor table will run the garbage collector to free
5772    // discardable entries.
5773    #[ip_test(I)]
5774    #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5775    #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5776    #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5777    #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5778    fn insert_new_entry_may_collect_garbage<I: TestIpExt>(insert_method: InsertMethod) {
5779        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5780
5781        for i in 0..MAX_ENTRIES {
5782            // NB: STALE entries will be discardable.
5783            init_stale_neighbor_with_ip(
5784                &mut core_ctx,
5785                &mut bindings_ctx,
5786                generate_ip_addr::<I>(i),
5787                LINK_ADDR1,
5788            );
5789            assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5790        }
5791
5792        // Attempting to insert an entry beyond the limit should trigger
5793        // garbage collection.
5794        insert_method.insert(
5795            &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5796            LINK_ADDR1,
5797            generate_ip_addr::<I>(MAX_ENTRIES),
5798            false, /* expect_err */
5799        );
5800        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD + 1);
5801        let mut events = bindings_ctx.take_events();
5802        // Expect 1 `Added` event and several `Removed` events.
5803        let add_event = events.pop().expect("should have added event");
5804        assert_eq!(add_event.addr, generate_ip_addr::<I>(MAX_ENTRIES));
5805        assert_matches!(add_event.kind, EventKind::Added(_));
5806        for _ in 0..(MAX_ENTRIES - GC_THRESHOLD) {
5807            assert_matches!(events.pop(), Some(Event{kind, ..}) if kind == EventKind::Removed);
5808        }
5809        assert_matches!(&events[..], []);
5810    }
5811}