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