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        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 != &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                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        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                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                log::warn!(
1452                    "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}.
1453                    New address {:?}",
1454                    neighbor,
1455                    current,
1456                    link_address
1457                );
1458                None
1459            }
1460
1461            DynamicNeighborState::Reachable(Reachable {
1462                link_address: current,
1463                last_confirmed_at: _,
1464            })
1465            | DynamicNeighborState::Stale(Stale { link_address: current })
1466            | DynamicNeighborState::Delay(Delay { link_address: current })
1467            | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1468            | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1469                // Per RFC 4861 section 4.4:
1470                //
1471                //    The link-layer address for the target, i.e., the
1472                //    sender of the advertisement ... MUST be
1473                //    included on link layers that have addresses when
1474                //    responding to multicast solicitations.  When
1475                //    responding to a unicast Neighbor Solicitation this
1476                //    option SHOULD be included.
1477                //
1478                //    ... When responding to unicast
1479                //    solicitations, the option can be omitted since the
1480                //    sender of the solicitation has the correct link-
1481                //    layer address; otherwise, it would not be able to
1482                //    send the unicast solicitation in the first place.
1483                //
1484                // Because neighbors may choose to omit the target link-layer address option
1485                // from neighbor confirmations, we must be tolerant of its absence. In the case
1486                // of absence, we use the cached link-layer address if one is available.
1487                let updated_link_address = link_address
1488                    .and_then(|link_address| (*current != link_address).then_some(link_address));
1489
1490                match (solicited_flag, updated_link_address, override_flag) {
1491                    // Per RFC 4861 section 7.2.5:
1492                    //
1493                    //   If [either] the Override flag is set, or the supplied link-layer address is
1494                    //   the same as that in the cache, [and] ... the Solicited flag is set, the
1495                    //   entry MUST be set to REACHABLE.
1496                    (true, _, true) | (true, None, _) => {
1497                        Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1498                    }
1499                    // Per RFC 4861 section 7.2.5:
1500                    //
1501                    //   If the Override flag is clear and the supplied link-layer address differs
1502                    //   from that in the cache, then one of two actions takes place:
1503                    //
1504                    //    a. If the state of the entry is REACHABLE, set it to STALE, but do not
1505                    //       update the entry in any other way.
1506                    //    b. Otherwise, the received advertisement should be ignored and MUST NOT
1507                    //       update the cache.
1508                    (_, Some(_), false) => match self {
1509                        // NB: do not update the link address.
1510                        DynamicNeighborState::Reachable(Reachable {
1511                            link_address,
1512                            last_confirmed_at: _,
1513                        }) => Some(NewState::Stale { link_address: *link_address }),
1514                        // Ignore the advertisement and do not update the cache.
1515                        DynamicNeighborState::Stale(_)
1516                        | DynamicNeighborState::Delay(_)
1517                        | DynamicNeighborState::Probe(_)
1518                        | DynamicNeighborState::Unreachable(_) => None,
1519                        // The INCOMPLETE state was already handled in the outer match.
1520                        DynamicNeighborState::Incomplete(_) => unreachable!(),
1521                    },
1522                    // Per RFC 4861 section 7.2.5:
1523                    //
1524                    //   If the Override flag is set [and] ... the Solicited flag is zero and the
1525                    //   link-layer address was updated with a different address, the state MUST be
1526                    //   set to STALE.
1527                    (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1528                    // Per RFC 4861 section 7.2.5:
1529                    //
1530                    //   There is no need to update the state for unsolicited advertisements that do
1531                    //   not change the contents of the cache.
1532                    (false, None, _) => None,
1533                }
1534            }
1535        };
1536        match new_state {
1537            Some(NewState::Reachable { link_address }) => self.enter_reachable(
1538                core_ctx,
1539                bindings_ctx,
1540                timers,
1541                device_id,
1542                neighbor,
1543                link_address,
1544            ),
1545            Some(NewState::Stale { link_address }) => self.enter_stale(
1546                core_ctx,
1547                bindings_ctx,
1548                timers,
1549                device_id,
1550                neighbor,
1551                link_address,
1552                num_entries,
1553                gc_state,
1554            ),
1555            None => {}
1556        }
1557    }
1558}
1559
1560#[cfg(any(test, feature = "testutils"))]
1561pub(crate) mod testutil {
1562    use super::*;
1563
1564    use alloc::sync::Arc;
1565
1566    use netstack3_base::sync::Mutex;
1567    use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1568
1569    /// Asserts that `device_id`'s `neighbor` resolved to `expected_link_addr`.
1570    pub fn assert_dynamic_neighbor_with_addr<
1571        I: Ip,
1572        D: LinkDevice,
1573        BC: NudBindingsContext<I, D, CC::DeviceId>,
1574        CC: NudContext<I, D, BC>,
1575    >(
1576        core_ctx: &mut CC,
1577        device_id: CC::DeviceId,
1578        neighbor: SpecifiedAddr<I::Addr>,
1579        expected_link_addr: UnicastAddr<D::Address>,
1580    ) {
1581        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1582            assert_matches!(
1583                neighbors.get(&neighbor),
1584                Some(NeighborState::Dynamic(
1585                    DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1586                    | DynamicNeighborState::Stale(Stale{ link_address })
1587                )) => {
1588                    assert_eq!(link_address, &expected_link_addr)
1589                }
1590            )
1591        })
1592    }
1593
1594    /// Asserts that the `device_id`'s `neighbor` is at `expected_state`.
1595    pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1596        core_ctx: &mut CC,
1597        device_id: CC::DeviceId,
1598        neighbor: SpecifiedAddr<I::Addr>,
1599        expected_state: DynamicNeighborState<D, BC>,
1600    ) where
1601        I: Ip,
1602        D: LinkDevice + PartialEq,
1603        BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1604        CC: NudContext<I, D, BC>,
1605    {
1606        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1607            assert_matches!(
1608                neighbors.get(&neighbor),
1609                Some(NeighborState::Dynamic(state)) => {
1610                    assert_eq!(state, &expected_state)
1611                }
1612            )
1613        })
1614    }
1615
1616    /// Asserts that `device_id`'s `neighbor` doesn't exist.
1617    pub fn assert_neighbor_unknown<
1618        I: Ip,
1619        D: LinkDevice,
1620        BC: NudBindingsContext<I, D, CC::DeviceId>,
1621        CC: NudContext<I, D, BC>,
1622    >(
1623        core_ctx: &mut CC,
1624        device_id: CC::DeviceId,
1625        neighbor: SpecifiedAddr<I::Addr>,
1626    ) {
1627        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1628            assert_matches!(neighbors.get(&neighbor), None)
1629        })
1630    }
1631
1632    impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1633        for FakeBindingsCtx<Id, Event, State, FrameMeta>
1634    {
1635        type Notifier = FakeLinkResolutionNotifier<D>;
1636    }
1637
1638    /// A fake implementation of [`LinkResolutionNotifier`].
1639    #[derive(Debug)]
1640    pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1641        Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>,
1642    );
1643
1644    impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1645        type Observer =
1646            Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>;
1647
1648        fn new() -> (Self, Self::Observer) {
1649            let inner = Arc::new(Mutex::new(None));
1650            (Self(inner.clone()), inner)
1651        }
1652
1653        fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>) {
1654            let Self(inner) = self;
1655            let mut inner = inner.lock();
1656            assert_eq!(*inner, None, "resolved link address was set more than once");
1657            *inner = Some(result);
1658        }
1659    }
1660
1661    impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1662        S: UseDelegateNudContext
1663    {
1664    }
1665    impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1666    where
1667        S: DelegateNudContext<I>,
1668    {
1669        type Delegate<T> = S::Delegate<T>;
1670    }
1671}
1672
1673#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1674enum NudEvent {
1675    RetransmitMulticastProbe,
1676    ReachableTime,
1677    DelayFirstProbe,
1678    RetransmitUnicastProbe,
1679}
1680
1681/// The timer ID for the NUD module.
1682#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1683#[generic_over_ip(I, Ip)]
1684pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1685    device_id: D,
1686    timer_type: NudTimerType,
1687    _marker: PhantomData<(I, L)>,
1688}
1689
1690#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1691enum NudTimerType {
1692    Neighbor,
1693    GarbageCollection,
1694}
1695
1696/// A wrapper for [`LocalTimerHeap`] that we can attach NUD helpers to.
1697#[derive(Debug)]
1698pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1699    gc: BT::Timer,
1700    neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1701}
1702
1703impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1704    fn new<
1705        DeviceId: WeakDeviceIdentifier,
1706        L: LinkDevice,
1707        CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1708    >(
1709        bindings_ctx: &mut BC,
1710        device_id: DeviceId,
1711    ) -> Self {
1712        Self {
1713            neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1714                bindings_ctx,
1715                NudTimerId {
1716                    device_id: device_id.clone(),
1717                    timer_type: NudTimerType::Neighbor,
1718                    _marker: PhantomData,
1719                },
1720            ),
1721            gc: CC::new_timer(
1722                bindings_ctx,
1723                NudTimerId {
1724                    device_id,
1725                    timer_type: NudTimerType::GarbageCollection,
1726                    _marker: PhantomData,
1727                },
1728            ),
1729        }
1730    }
1731
1732    fn schedule_neighbor(
1733        &mut self,
1734        bindings_ctx: &mut BC,
1735        after: NonZeroDuration,
1736        neighbor: SpecifiedAddr<I::Addr>,
1737        event: NudEvent,
1738    ) {
1739        let Self { neighbor: heap, gc: _ } = self;
1740        assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1741    }
1742
1743    fn schedule_neighbor_at(
1744        &mut self,
1745        bindings_ctx: &mut BC,
1746        at: BC::Instant,
1747        neighbor: SpecifiedAddr<I::Addr>,
1748        event: NudEvent,
1749    ) {
1750        let Self { neighbor: heap, gc: _ } = self;
1751        assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1752    }
1753
1754    /// Cancels a neighbor timer.
1755    fn cancel_neighbor(
1756        &mut self,
1757        bindings_ctx: &mut BC,
1758        neighbor: SpecifiedAddr<I::Addr>,
1759    ) -> Option<NudEvent> {
1760        let Self { neighbor: heap, gc: _ } = self;
1761        heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1762    }
1763
1764    fn pop_neighbor(
1765        &mut self,
1766        bindings_ctx: &mut BC,
1767    ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1768        let Self { neighbor: heap, gc: _ } = self;
1769        heap.pop(bindings_ctx)
1770    }
1771
1772    /// Schedules a garbage collection IFF we hit the entries threshold and it's
1773    /// not already scheduled.
1774    fn maybe_schedule_gc(
1775        &mut self,
1776        bindings_ctx: &mut BC,
1777        num_entries: usize,
1778        gc_state: &mut GarbageCollectionState<BC::Instant>,
1779    ) {
1780        let GarbageCollectionState { is_dirty, last_gc } = gc_state;
1781        *is_dirty = true;
1782        let Self { gc, neighbor: _ } = self;
1783        if num_entries > GC_THRESHOLD && bindings_ctx.scheduled_instant(gc).is_none() {
1784            let instant = if let Some(last_gc) = last_gc {
1785                last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1786            } else {
1787                bindings_ctx.now()
1788            };
1789            // Scheduling a timer requires a mutable borrow and we're
1790            // currently holding it exclusively. We just checked that the timer
1791            // is not scheduled, so this assertion always holds.
1792            assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1793        }
1794    }
1795
1796    fn cancel_gc(&mut self, bindings_ctx: &mut BC) {
1797        let Self { gc, neighbor: _ } = self;
1798        let _: Option<BC::Instant> = bindings_ctx.cancel_timer(gc);
1799    }
1800}
1801
1802/// State related to neighbor table garbage collection.
1803#[derive(Debug)]
1804pub struct GarbageCollectionState<Instant> {
1805    /// The last time garbage collection was run.
1806    last_gc: Option<Instant>,
1807    /// Whether the table contains potentially discardable entries (e.g. STALE
1808    /// or UNREACHABLE).
1809    is_dirty: bool,
1810}
1811
1812/// NUD module per-device state.
1813#[derive(Debug)]
1814pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1815    // TODO(https://fxbug.dev/42076887): Key neighbors by `UnicastAddr`.
1816    neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1817    gc_state: GarbageCollectionState<BT::Instant>,
1818    timer_heap: TimerHeap<I, BT>,
1819}
1820
1821impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1822    /// Returns current neighbors.
1823    #[cfg(any(test, feature = "testutils"))]
1824    pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1825        &self.neighbors
1826    }
1827}
1828
1829impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1830    /// Constructs a new `NudState` for `device_id`.
1831    pub fn new<
1832        DeviceId: WeakDeviceIdentifier,
1833        CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1834    >(
1835        bindings_ctx: &mut BC,
1836        device_id: DeviceId,
1837    ) -> Self {
1838        Self {
1839            neighbors: Default::default(),
1840            gc_state: GarbageCollectionState { last_gc: None, is_dirty: false },
1841            timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1842        }
1843    }
1844}
1845
1846/// The bindings context for NUD.
1847pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1848    TimerContext
1849    + LinkResolutionContext<D>
1850    + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1851    + NudBindingsTypes<D>
1852{
1853}
1854
1855impl<
1856    I: Ip,
1857    D: LinkDevice,
1858    DeviceId,
1859    BC: TimerContext
1860        + LinkResolutionContext<D>
1861        + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1862        + NudBindingsTypes<D>,
1863> NudBindingsContext<I, D, DeviceId> for BC
1864{
1865}
1866
1867/// A marker trait for types provided by bindings to NUD.
1868pub trait NudBindingsTypes<D: LinkDevice>:
1869    LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1870{
1871}
1872
1873impl<BT, D> NudBindingsTypes<D> for BT
1874where
1875    D: LinkDevice,
1876    BT: LinkResolutionContext<D>
1877        + InstantBindingsTypes
1878        + TimerBindingsTypes
1879        + TxMetadataBindingsTypes,
1880{
1881}
1882
1883/// An execution context that allows creating link resolution notifiers.
1884pub trait LinkResolutionContext<D: LinkDevice> {
1885    /// A notifier held by core that can be used to inform interested parties of
1886    /// the result of link address resolution.
1887    type Notifier: LinkResolutionNotifier<D>;
1888}
1889
1890/// A notifier held by core that can be used to inform interested parties of the
1891/// result of link address resolution.
1892pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1893    /// The corresponding observer that can be used to observe the result of
1894    /// link address resolution.
1895    type Observer;
1896
1897    /// Create a connected (notifier, observer) pair.
1898    fn new() -> (Self, Self::Observer);
1899
1900    /// Signal to Bindings that link address resolution has completed for a
1901    /// neighbor.
1902    fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>);
1903}
1904
1905/// The execution context for NUD for a link device.
1906pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1907    /// The inner configuration context.
1908    type ConfigCtx<'a>: NudConfigContext<I>;
1909    /// The inner send context.
1910    type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1911
1912    /// Calls the function with a mutable reference to the NUD state and the
1913    /// core sender context.
1914    fn with_nud_state_mut_and_sender_ctx<
1915        O,
1916        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1917    >(
1918        &mut self,
1919        device_id: &Self::DeviceId,
1920        cb: F,
1921    ) -> O;
1922
1923    /// Calls the function with a mutable reference to the NUD state and NUD
1924    /// configuration for the device.
1925    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1926        &mut self,
1927        device_id: &Self::DeviceId,
1928        cb: F,
1929    ) -> O;
1930
1931    /// Calls the function with an immutable reference to the NUD state.
1932    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1933        &mut self,
1934        device_id: &Self::DeviceId,
1935        cb: F,
1936    ) -> O;
1937
1938    /// Sends a neighbor probe/solicitation message.
1939    ///
1940    /// If `remote_link_addr` is provided, the message will be unicasted to that
1941    /// address; if it is `None`, the message will be multicast.
1942    fn send_neighbor_solicitation(
1943        &mut self,
1944        bindings_ctx: &mut BC,
1945        device_id: &Self::DeviceId,
1946        lookup_addr: SpecifiedAddr<I::Addr>,
1947        remote_link_addr: Option<UnicastAddr<D::Address>>,
1948    );
1949}
1950
1951/// A marker trait to enable the blanket impl of [`NudContext`] for types
1952/// implementing [`DelegateNudContext`].
1953pub trait UseDelegateNudContext {}
1954
1955/// Enables a blanket implementation of [`NudContext`] via delegate that can
1956/// wrap a mutable reference of `Self`.
1957///
1958/// The `UseDelegateNudContext` requirement here is steering users to to the
1959/// right thing to enable the blanket implementation.
1960pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1961    /// The delegate that implements [`NudContext`].
1962    type Delegate<T>: ref_cast::RefCast<From = T>;
1963    /// Wraps self into a mutable delegate reference.
1964    fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1965        <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1966    }
1967}
1968
1969impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1970where
1971    I: Ip,
1972    D: LinkDevice,
1973    BC: NudBindingsTypes<D>,
1974    CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1975        // This seems redundant with `DelegateNudContext` but it is required to
1976        // get the compiler happy.
1977        + UseDelegateNudContext
1978        + DeviceIdContext<D>,
1979{
1980    type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1981    type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1982    fn with_nud_state_mut_and_sender_ctx<
1983        O,
1984        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1985    >(
1986        &mut self,
1987        device_id: &Self::DeviceId,
1988        cb: F,
1989    ) -> O {
1990        self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1991    }
1992
1993    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1994        &mut self,
1995        device_id: &Self::DeviceId,
1996        cb: F,
1997    ) -> O {
1998        self.wrap().with_nud_state_mut(device_id, cb)
1999    }
2000    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
2001        &mut self,
2002        device_id: &Self::DeviceId,
2003        cb: F,
2004    ) -> O {
2005        self.wrap().with_nud_state(device_id, cb)
2006    }
2007    fn send_neighbor_solicitation(
2008        &mut self,
2009        bindings_ctx: &mut BC,
2010        device_id: &Self::DeviceId,
2011        lookup_addr: SpecifiedAddr<I::Addr>,
2012        remote_link_addr: Option<UnicastAddr<D::Address>>,
2013    ) {
2014        self.wrap().send_neighbor_solicitation(
2015            bindings_ctx,
2016            device_id,
2017            lookup_addr,
2018            remote_link_addr,
2019        )
2020    }
2021}
2022
2023/// IP extension trait to support [`NudIcmpContext`].
2024pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2025    /// IP packet metadata needed when sending ICMP destination unreachable
2026    /// errors as a result of link-layer address resolution failure.
2027    type Metadata;
2028
2029    /// Extracts IP-version specific metadata from `packet`.
2030    fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2031}
2032
2033impl NudIcmpIpExt for Ipv4 {
2034    type Metadata = Ipv4FragmentType;
2035
2036    fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2037        packet.fragment_type()
2038    }
2039}
2040
2041impl NudIcmpIpExt for Ipv6 {
2042    type Metadata = ();
2043
2044    fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2045        ()
2046    }
2047}
2048
2049/// The execution context which allows sending ICMP destination unreachable
2050/// errors, which needs to happen when address resolution fails.
2051pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2052    /// Send an ICMP destination unreachable error to `original_src_ip` as
2053    /// a result of `frame` being unable to be sent/forwarded due to link
2054    /// layer address resolution failure.
2055    ///
2056    /// `original_src_ip`, `original_dst_ip`, and `header_len` are all IP
2057    /// header fields from `frame`.
2058    fn send_icmp_dest_unreachable(
2059        &mut self,
2060        bindings_ctx: &mut BC,
2061        frame: Buf<Vec<u8>>,
2062        device_id: Option<&Self::DeviceId>,
2063        original_src_ip: SocketIpAddr<I::Addr>,
2064        original_dst_ip: SocketIpAddr<I::Addr>,
2065        header_len: usize,
2066        proto: I::Proto,
2067        metadata: I::Metadata,
2068    );
2069}
2070
2071/// NUD configurations.
2072#[derive(Clone, Debug)]
2073pub struct NudUserConfig {
2074    /// The maximum number of unicast solicitations as defined in [RFC 4861
2075    /// section 10].
2076    ///
2077    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2078    pub max_unicast_solicitations: NonZeroU16,
2079    /// The maximum number of multicast solicitations as defined in [RFC 4861
2080    /// section 10].
2081    ///
2082    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2083    pub max_multicast_solicitations: NonZeroU16,
2084    /// The base value used for computing the duration a neighbor is considered
2085    /// reachable after receiving a reachability confirmation as defined in
2086    /// [RFC 4861 section 6.3.2].
2087    ///
2088    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2089    pub base_reachable_time: NonZeroDuration,
2090    /// The time between retransmissions of neighbor probe messages to a neighbor
2091    /// when resolving the address or when probing the reachability of a neighbor
2092    /// as defined in [RFC 4861 section 6.3.2].
2093    ///
2094    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2095    pub retrans_timer: NonZeroDuration,
2096}
2097
2098impl Default for NudUserConfig {
2099    fn default() -> Self {
2100        NudUserConfig {
2101            max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2102            max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2103            base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2104            retrans_timer: DEFAULT_RETRANS_TIMER,
2105        }
2106    }
2107}
2108
2109/// An update structure for [`NudUserConfig`].
2110///
2111/// Only fields with variant `Some` are updated.
2112#[allow(missing_docs)]
2113#[derive(Clone, Debug, Eq, PartialEq, Default)]
2114pub struct NudUserConfigUpdate {
2115    pub max_unicast_solicitations: Option<NonZeroU16>,
2116    pub max_multicast_solicitations: Option<NonZeroU16>,
2117    pub base_reachable_time: Option<NonZeroDuration>,
2118    pub retrans_timer: Option<NonZeroDuration>,
2119}
2120
2121impl NudUserConfigUpdate {
2122    /// Applies the configuration returning a [`NudUserConfigUpdate`] with the
2123    /// changed fields populated.
2124    pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2125        fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2126            if let Some(opt) = opt.as_mut() {
2127                core::mem::swap(opt, target)
2128            }
2129        }
2130        let Self {
2131            max_unicast_solicitations,
2132            max_multicast_solicitations,
2133            base_reachable_time,
2134            retrans_timer,
2135        } = &mut self;
2136        swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2137        swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2138        swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2139        swap_if_set(retrans_timer, &mut config.retrans_timer);
2140
2141        self
2142    }
2143}
2144
2145/// The execution context for NUD that allows accessing NUD configuration (such
2146/// as timer durations) for a particular device.
2147pub trait NudConfigContext<I: Ip> {
2148    /// The amount of time between retransmissions of neighbor probe messages.
2149    ///
2150    /// This corresponds to the configurable per-interface `RetransTimer` value
2151    /// used in NUD as defined in [RFC 4861 section 6.3.2].
2152    ///
2153    /// [RFC 4861 section 6.3.2]: https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.2
2154    fn retransmit_timeout(&mut self) -> NonZeroDuration;
2155
2156    /// Calls the callback with an immutable reference to NUD configurations.
2157    fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2158
2159    /// Returns the maximum number of unicast solicitations.
2160    fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2161        self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2162            *max_unicast_solicitations
2163        })
2164    }
2165
2166    /// Returns the maximum number of multicast solicitations.
2167    fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2168        self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2169            *max_multicast_solicitations
2170        })
2171    }
2172
2173    /// Returns the base reachable time, the duration a neighbor is considered
2174    /// reachable after receiving a reachability confirmation.
2175    fn base_reachable_time(&mut self) -> NonZeroDuration {
2176        self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2177    }
2178
2179    /// Amount of time from the moment a host becomes reachable before the
2180    /// entry can overridden.
2181    fn override_lock_time(&mut self) -> Duration;
2182}
2183
2184/// The execution context for NUD for a link device that allows sending IP
2185/// packets to specific neighbors.
2186pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2187    NudConfigContext<I> + DeviceIdContext<D>
2188{
2189    /// Send an IP frame to the neighbor with the specified link address.
2190    fn send_ip_packet_to_neighbor_link_addr<S>(
2191        &mut self,
2192        bindings_ctx: &mut BC,
2193        neighbor_link_addr: UnicastAddr<D::Address>,
2194        body: S,
2195        meta: BC::TxMetadata,
2196    ) -> Result<(), SendFrameError<S>>
2197    where
2198        S: NetworkSerializer,
2199        S::Buffer: BufferMut;
2200}
2201
2202/// An implementation of NUD for the IP layer.
2203pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2204    /// Handles an incoming neighbor probe message.
2205    ///
2206    /// For IPv6, this can be an NDP Neighbor Solicitation or an NDP Router
2207    /// Advertisement message.
2208    fn handle_neighbor_probe(
2209        &mut self,
2210        bindings_ctx: &mut BC,
2211        device_id: &Self::DeviceId,
2212        neighbor: SpecifiedAddr<I::Addr>,
2213        link_addr: &[u8],
2214    );
2215
2216    /// Handles an incoming neighbor confirmation message.
2217    ///
2218    /// For IPv6, this can be an NDP Neighbor Advertisement.
2219    fn handle_neighbor_confirmation(
2220        &mut self,
2221        bindings_ctx: &mut BC,
2222        device_id: &Self::DeviceId,
2223        neighbor: SpecifiedAddr<I::Addr>,
2224        link_addr: Option<&[u8]>,
2225        flags: ConfirmationFlags,
2226    );
2227
2228    /// Clears the neighbor table.
2229    fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2230}
2231
2232/// Specifies the link-layer address of a neighbor.
2233#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2234pub enum LinkResolutionResult<A, Observer> {
2235    /// The destination is a known neighbor with the given link-layer address.
2236    Resolved(A),
2237    /// The destination is pending neighbor resolution.
2238    Pending(Observer),
2239}
2240
2241/// An implementation of NUD for a link device.
2242pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2243    /// Sets a dynamic neighbor's entry state to the specified values in
2244    /// response to the source packet.
2245    fn handle_neighbor_update(
2246        &mut self,
2247        bindings_ctx: &mut BC,
2248        device_id: &Self::DeviceId,
2249        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
2250        // disallow the address with all host bits equal to 0, and the
2251        // subnet broadcast addresses with all host bits equal to 1.
2252        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
2253        neighbor: SpecifiedAddr<I::Addr>,
2254        source: DynamicNeighborUpdateSource<D::Address>,
2255    );
2256
2257    /// Clears the neighbor table.
2258    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2259
2260    /// Send an IP packet to the neighbor.
2261    ///
2262    /// If the neighbor's link address is not known, link address resolution
2263    /// is performed.
2264    fn send_ip_packet_to_neighbor<S>(
2265        &mut self,
2266        bindings_ctx: &mut BC,
2267        device_id: &Self::DeviceId,
2268        neighbor: SpecifiedAddr<I::Addr>,
2269        body: S,
2270        meta: BC::TxMetadata,
2271    ) -> Result<(), SendFrameError<S>>
2272    where
2273        S: NetworkSerializer,
2274        S::Buffer: BufferMut;
2275}
2276
2277enum TransmitProbe<A> {
2278    Multicast,
2279    Unicast(A),
2280}
2281
2282impl<
2283    I: NudIcmpIpExt,
2284    D: LinkDevice,
2285    BC: NudBindingsContext<I, D, CC::DeviceId>,
2286    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2287> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2288{
2289    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2290        let Self { device_id, timer_type, _marker: PhantomData } = self;
2291        let Some(device_id) = device_id.upgrade() else {
2292            return;
2293        };
2294        match timer_type {
2295            NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2296            NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2297        }
2298    }
2299}
2300
2301fn handle_neighbor_timer<I, D, CC, BC>(
2302    core_ctx: &mut CC,
2303    bindings_ctx: &mut BC,
2304    device_id: CC::DeviceId,
2305) where
2306    I: NudIcmpIpExt,
2307    D: LinkDevice,
2308    BC: NudBindingsContext<I, D, CC::DeviceId>,
2309    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2310{
2311    enum Action<L, A, M> {
2312        TransmitProbe { probe: TransmitProbe<L>, to: A },
2313        SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2314    }
2315    let action = core_ctx.with_nud_state_mut(
2316        &device_id,
2317        |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2318            let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2319            let num_entries = neighbors.len();
2320            let mut entry = match neighbors.entry(lookup_addr) {
2321                Entry::Occupied(entry) => entry,
2322                Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2323            };
2324
2325            match entry.get_mut() {
2326                NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2327                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2328
2329                    if incomplete.schedule_timer_if_should_retransmit(
2330                        core_ctx,
2331                        bindings_ctx,
2332                        timer_heap,
2333                        lookup_addr,
2334                    ) {
2335                        Some(Action::TransmitProbe {
2336                            probe: TransmitProbe::Multicast,
2337                            to: lookup_addr,
2338                        })
2339                    } else {
2340                        // Failed to complete neighbor resolution and no more probes to send.
2341                        // Subsequent traffic to this neighbor will recreate the entry and restart
2342                        // address resolution.
2343                        //
2344                        // TODO(https://fxbug.dev/42082448): consider retaining this neighbor entry in
2345                        // a sentinel `Failed` state, equivalent to its having been discarded except
2346                        // for debugging/observability purposes.
2347                        debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2348                        let Incomplete {
2349                            transmit_counter: _,
2350                            ref mut pending_frames,
2351                            notifiers: _,
2352                            _marker,
2353                        } = assert_matches!(
2354                            entry.remove(),
2355                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2356                                => incomplete
2357                        );
2358                        let pending_frames = core::mem::take(pending_frames);
2359                        bindings_ctx.on_event(Event::removed(
2360                            &device_id,
2361                            lookup_addr,
2362                            bindings_ctx.now(),
2363                        ));
2364                        Some(Action::SendIcmpDestUnreachable(pending_frames))
2365                    }
2366                }
2367                NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2368                    assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2369
2370                    let Probe { link_address, transmit_counter: _ } = probe;
2371                    let link_address = *link_address;
2372                    if probe.schedule_timer_if_should_retransmit(
2373                        core_ctx,
2374                        bindings_ctx,
2375                        timer_heap,
2376                        lookup_addr,
2377                    ) {
2378                        Some(Action::TransmitProbe {
2379                            probe: TransmitProbe::Unicast(link_address),
2380                            to: lookup_addr,
2381                        })
2382                    } else {
2383                        let unreachable = probe.enter_unreachable(
2384                            bindings_ctx,
2385                            timer_heap,
2386                            num_entries,
2387                            gc_state,
2388                        );
2389                        *entry.get_mut() =
2390                            NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2391                        let event_state = entry.get_mut().to_event_state();
2392                        let event = Event::changed(
2393                            &device_id,
2394                            event_state,
2395                            lookup_addr,
2396                            bindings_ctx.now(),
2397                        );
2398                        bindings_ctx.on_event(event);
2399                        None
2400                    }
2401                }
2402                NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2403                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2404                    unreachable
2405                        .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2406                        .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2407                }
2408                NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2409                    link_address,
2410                    last_confirmed_at,
2411                })) => {
2412                    assert_eq!(event, NudEvent::ReachableTime);
2413                    let link_address = *link_address;
2414
2415                    let expiration =
2416                        last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2417                    if expiration > bindings_ctx.now() {
2418                        timer_heap.schedule_neighbor_at(
2419                            bindings_ctx,
2420                            expiration,
2421                            lookup_addr,
2422                            NudEvent::ReachableTime,
2423                        );
2424                    } else {
2425                        // Per [RFC 4861 section 7.3.3]:
2426                        //
2427                        //   When ReachableTime milliseconds have passed since receipt of the last
2428                        //   reachability confirmation for a neighbor, the Neighbor Cache entry's
2429                        //   state changes from REACHABLE to STALE.
2430                        //
2431                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2432                        *entry.get_mut() =
2433                            NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2434                                link_address,
2435                            }));
2436                        let event_state = entry.get_mut().to_event_state();
2437                        let event = Event::changed(
2438                            &device_id,
2439                            event_state,
2440                            lookup_addr,
2441                            bindings_ctx.now(),
2442                        );
2443                        bindings_ctx.on_event(event);
2444
2445                        // This entry is deemed discardable now that it is not in active use;
2446                        // schedule garbage collection for the neighbor table if we are currently
2447                        // over the maximum amount of entries.
2448                        timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
2449                    }
2450
2451                    None
2452                }
2453                NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2454                    assert_eq!(event, NudEvent::DelayFirstProbe);
2455
2456                    // Per [RFC 4861 section 7.3.3]:
2457                    //
2458                    //   If the entry is still in the DELAY state when the timer expires, the
2459                    //   entry's state changes to PROBE.
2460                    //
2461                    // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2462                    let probe @ Probe { link_address, transmit_counter: _ } =
2463                        delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2464                    *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2465                    let event_state = entry.get_mut().to_event_state();
2466                    bindings_ctx.on_event(Event::changed(
2467                        &device_id,
2468                        event_state,
2469                        lookup_addr,
2470                        bindings_ctx.now(),
2471                    ));
2472
2473                    Some(Action::TransmitProbe {
2474                        probe: TransmitProbe::Unicast(link_address),
2475                        to: lookup_addr,
2476                    })
2477                }
2478                state @ (NeighborState::Static(_)
2479                | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2480                    panic!("timer unexpectedly fired in state {state:?}")
2481                }
2482            }
2483        },
2484    );
2485
2486    match action {
2487        Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2488            for (mut frame, meta) in pending_frames.drain(..) {
2489                // This frame is being dropped from the pending NUD queue, we
2490                // can release its tx metadata.
2491                core::mem::drop(meta);
2492
2493                // TODO(https://fxbug.dev/323585811): Avoid needing to parse the packet to get
2494                // IP header fields by extracting them from the serializer passed into the NUD
2495                // layer and storing them alongside the pending frames instead.
2496                let Some((packet, original_src_ip, original_dst_ip)) = frame
2497                    .parse_mut::<I::Packet<_>>()
2498                    .map_err(|e| {
2499                        warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2500                    })
2501                    .ok()
2502                    .and_then(|packet| {
2503                        let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2504                        let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2505                        Some((packet, original_src_ip, original_dst_ip))
2506                    })
2507                    .or_else(|| {
2508                        core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2509                        None
2510                    })
2511                else {
2512                    continue;
2513                };
2514                let header_metadata = I::extract_metadata(&packet);
2515                let header_len = packet.parse_metadata().header_len();
2516                let proto = packet.proto();
2517                let metadata = packet.parse_metadata();
2518                core::mem::drop(packet);
2519                frame.undo_parse(metadata);
2520                core_ctx.send_icmp_dest_unreachable(
2521                    bindings_ctx,
2522                    frame,
2523                    // Provide the device ID if `original_src_ip`, the address the ICMP error
2524                    // is destined for, is link-local. Note that if this address is link-local,
2525                    // it should be an address assigned to one of our own interfaces, because the
2526                    // link-local subnet should always be on-link according to RFC 5942 Section 3:
2527                    //
2528                    //   The link-local prefix is effectively considered a permanent entry on the
2529                    //   Prefix List.
2530                    //
2531                    // Even if the link-local subnet is off-link, passing the device ID is never
2532                    // incorrect because link-local traffic will never be forwarded, and
2533                    // there is only ever one link and thus interface involved.
2534                    original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2535                    original_src_ip,
2536                    original_dst_ip,
2537                    header_len,
2538                    proto,
2539                    header_metadata,
2540                );
2541            }
2542        }
2543        Some(Action::TransmitProbe { probe, to }) => {
2544            let remote_link_addr = match probe {
2545                TransmitProbe::Multicast => None,
2546                TransmitProbe::Unicast(link_addr) => Some(link_addr),
2547            };
2548            core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2549        }
2550        None => {}
2551    }
2552}
2553
2554impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2555    NudHandler<I, D, BC> for CC
2556{
2557    fn handle_neighbor_update(
2558        &mut self,
2559        bindings_ctx: &mut BC,
2560        device_id: &CC::DeviceId,
2561        neighbor: SpecifiedAddr<I::Addr>,
2562        source: DynamicNeighborUpdateSource<D::Address>,
2563    ) {
2564        debug!("received neighbor {:?} from {}", source, neighbor);
2565        self.with_nud_state_mut_and_sender_ctx(
2566            device_id,
2567            |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2568                let num_entries = neighbors.len();
2569                match neighbors.get_mut(&neighbor) {
2570                    None => match source {
2571                        DynamicNeighborUpdateSource::Probe { link_address } => {
2572                            // Per [RFC 4861 section 7.2.3] ("Receipt of Neighbor Solicitations"):
2573                            //
2574                            //   If an entry does not already exist, the node SHOULD create a new
2575                            //   one and set its reachability state to STALE as specified in Section
2576                            //   7.3.3.
2577                            //
2578                            // [RFC 4861 section 7.2.3]: https://tools.ietf.org/html/rfc4861#section-7.2.3
2579                            let result = insert_new_entry(
2580                                neighbors,
2581                                gc_state,
2582                                timer_heap,
2583                                bindings_ctx,
2584                                device_id,
2585                                neighbor,
2586                                NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2587                                    link_address,
2588                                })),
2589                            );
2590                            match result {
2591                                Ok(_entry) => {}
2592                                Err(TableFullError { entry }) => {
2593                                    debug!("Neighbor table full; failed to insert {entry:?}");
2594                                    return;
2595                                }
2596                            }
2597
2598                            // This entry is not currently in active use; if we are currently over
2599                            // the maximum amount of entries, schedule garbage collection.
2600                            timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), gc_state);
2601                        }
2602                        // Per [RFC 4861 section 7.2.5] ("Receipt of Neighbor Advertisements"):
2603                        //
2604                        //   If no entry exists, the advertisement SHOULD be silently discarded.
2605                        //   There is no need to create an entry if none exists, since the
2606                        //   recipient has apparently not initiated any communication with the
2607                        //   target.
2608                        //
2609                        // [RFC 4861 section 7.2.5]: https://tools.ietf.org/html/rfc4861#section-7.2.5
2610                        DynamicNeighborUpdateSource::Confirmation { .. } => {}
2611                    },
2612                    Some(entry) => match entry {
2613                        NeighborState::Dynamic(e) => match source {
2614                            DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2615                                core_ctx,
2616                                bindings_ctx,
2617                                timer_heap,
2618                                device_id,
2619                                neighbor,
2620                                link_address,
2621                                num_entries,
2622                                gc_state,
2623                            ),
2624                            DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2625                                .handle_confirmation(
2626                                    core_ctx,
2627                                    bindings_ctx,
2628                                    timer_heap,
2629                                    device_id,
2630                                    neighbor,
2631                                    link_address,
2632                                    flags,
2633                                    num_entries,
2634                                    gc_state,
2635                                ),
2636                        },
2637                        NeighborState::Static(_) => {}
2638                    },
2639                }
2640            },
2641        );
2642    }
2643
2644    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2645        self.with_nud_state_mut(
2646            device_id,
2647            |NudState { neighbors, gc_state: _, timer_heap }, _config| {
2648                neighbors.drain().for_each(|(neighbor, state)| {
2649                    match state {
2650                        NeighborState::Dynamic(mut entry) => {
2651                            entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2652                        }
2653                        NeighborState::Static(_) => {}
2654                    }
2655                    bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2656                });
2657            },
2658        );
2659    }
2660
2661    fn send_ip_packet_to_neighbor<S>(
2662        &mut self,
2663        bindings_ctx: &mut BC,
2664        device_id: &Self::DeviceId,
2665        lookup_addr: SpecifiedAddr<I::Addr>,
2666        body: S,
2667        meta: BC::TxMetadata,
2668    ) -> Result<(), SendFrameError<S>>
2669    where
2670        S: NetworkSerializer,
2671        S::Buffer: BufferMut,
2672    {
2673        let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2674            device_id,
2675            |NudState { neighbors, gc_state, timer_heap },
2676             core_ctx|
2677             -> Result<_, SendFrameError<S>> {
2678                match neighbors.get_mut(&lookup_addr) {
2679                    None => {
2680                        let incomplete =
2681                            Incomplete::new(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2682                        let result = insert_new_entry(
2683                            neighbors,
2684                            gc_state,
2685                            timer_heap,
2686                            bindings_ctx,
2687                            device_id,
2688                            lookup_addr,
2689                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2690                        );
2691                        match result {
2692                            Err(TableFullError { entry }) => {
2693                                debug!("Neighbor table full; failed to insert {entry:?}");
2694                                return Err(ErrorAndSerializer {
2695                                    serializer: body,
2696                                    error: SendFrameErrorReason::AddressResolutionFailed,
2697                                });
2698                            }
2699                            Ok(mut entry) => {
2700                                let dynamic = assert_matches!(
2701                                    entry.get_mut(),
2702                                    NeighborState::Dynamic(d) => d,
2703                                    "newly inserted entry must still be dynamic"
2704                                );
2705                                let incomplete = assert_matches!(
2706                                    dynamic,
2707                                    DynamicNeighborState::Incomplete(i) => i,
2708                                    "newly inserted entry must still be incomplete"
2709                                );
2710                                // Queue the packet and unwind on failure.
2711                                match incomplete.queue_packet(body, meta) {
2712                                    Ok(()) => Ok(true),
2713                                    Err(e) => {
2714                                        dynamic.cancel_timer(bindings_ctx, timer_heap, lookup_addr);
2715                                        let _entry = entry.remove();
2716                                        Err(e.err_into())
2717                                    }
2718                                }
2719                            }
2720                        }
2721                    }
2722                    Some(entry) => {
2723                        match entry {
2724                            NeighborState::Static(link_address) => {
2725                                // Send the IP packet while holding the NUD lock to prevent a
2726                                // potential ordering violation.
2727                                //
2728                                // If we drop the NUD lock before sending out this packet, another
2729                                // thread could take the NUD lock and send a packet *before* this
2730                                // packet is sent out, resulting in out-of-order transmission to the
2731                                // device.
2732                                core_ctx.send_ip_packet_to_neighbor_link_addr(
2733                                    bindings_ctx,
2734                                    *link_address,
2735                                    body,
2736                                    meta,
2737                                )?;
2738
2739                                Ok(false)
2740                            }
2741                            NeighborState::Dynamic(e) => {
2742                                let do_multicast_solicit = e.handle_packet_queued_to_send(
2743                                    core_ctx,
2744                                    bindings_ctx,
2745                                    timer_heap,
2746                                    device_id,
2747                                    lookup_addr,
2748                                    body,
2749                                    meta,
2750                                )?;
2751
2752                                Ok(do_multicast_solicit)
2753                            }
2754                        }
2755                    }
2756                }
2757            },
2758        )?;
2759
2760        if do_multicast_solicit {
2761            self.send_neighbor_solicitation(
2762                bindings_ctx,
2763                &device_id,
2764                lookup_addr,
2765                /* multicast */ None,
2766            );
2767        }
2768
2769        Ok(())
2770    }
2771}
2772
2773pub(crate) struct TableFullError<E> {
2774    entry: E,
2775}
2776
2777/// Attempts to insert a new entry into the neighbor table.
2778///
2779/// If the table is full, the garbage collector will be run synchronously in
2780/// an attempt to free up space. If space becomes available, the entry will be
2781/// inserted, otherwise a `TableFullError` is returned.
2782///
2783/// Upon successful insertion, the `Added` event is emitted to bindings and a
2784/// the newly inserted entry is returned.
2785///
2786/// # Panics
2787///
2788/// May panic if the entry already exists (depending on whether the garbage
2789/// collector needs to run, and whether the existing entry can be discarded).
2790pub(crate) fn insert_new_entry<
2791    'a,
2792    I: Ip,
2793    D: LinkDevice,
2794    DeviceId: StrongDeviceIdentifier,
2795    BC: NudBindingsContext<I, D, DeviceId>,
2796>(
2797    neighbors: &'a mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2798    gc_state: &mut GarbageCollectionState<BC::Instant>,
2799    timer_heap: &mut TimerHeap<I, BC>,
2800    bindings_ctx: &mut BC,
2801    device_id: &DeviceId,
2802    ip: SpecifiedAddr<I::Addr>,
2803    entry: NeighborState<D, BC>,
2804) -> Result<
2805    OccupiedEntry<'a, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2806    TableFullError<NeighborState<D, BC>>,
2807> {
2808    if neighbors.len() >= MAX_ENTRIES {
2809        // If the garbage collector is already scheduled, cancel it on a best
2810        // effort basis. This may race with the timer firing, but there's no
2811        // real harm of that happening (e.g. it would result in a single
2812        // spurious GC run).
2813        timer_heap.cancel_gc(bindings_ctx);
2814        collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, device_id);
2815    }
2816
2817    if neighbors.len() >= MAX_ENTRIES {
2818        return Err(TableFullError { entry });
2819    }
2820
2821    match neighbors.entry(ip) {
2822        Entry::Occupied(_) => panic!("neighbor entry unexpectedly existed"),
2823        Entry::Vacant(e) => {
2824            let event_state = entry.to_event_state();
2825            let entry = e.insert_entry(entry);
2826            let event = Event::added(device_id, event_state, ip, bindings_ctx.now());
2827            bindings_ctx.on_event(event);
2828            Ok(entry)
2829        }
2830    }
2831}
2832
2833/// Confirm upper-layer forward reachability to the specified neighbor through
2834/// the specified device.
2835pub fn confirm_reachable<I, D, CC, BC>(
2836    core_ctx: &mut CC,
2837    bindings_ctx: &mut BC,
2838    device_id: &CC::DeviceId,
2839    neighbor: SpecifiedAddr<I::Addr>,
2840) where
2841    I: Ip,
2842    D: LinkDevice,
2843    BC: NudBindingsContext<I, D, CC::DeviceId>,
2844    CC: NudContext<I, D, BC>,
2845{
2846    core_ctx.with_nud_state_mut_and_sender_ctx(
2847        device_id,
2848        |NudState { neighbors, timer_heap, .. }, core_ctx| {
2849            match neighbors.entry(neighbor) {
2850                Entry::Vacant(_) => {
2851                    debug!(
2852                        "got an upper-layer confirmation for non-existent neighbor entry {}",
2853                        neighbor
2854                    );
2855                }
2856                Entry::Occupied(e) => match e.into_mut() {
2857                    NeighborState::Static(_) => {}
2858                    NeighborState::Dynamic(e) => {
2859                        // Per [RFC 4861 section 7.3.3]:
2860                        //
2861                        //   When a reachability confirmation is received (either through upper-
2862                        //   layer advice or a solicited Neighbor Advertisement), an entry's state
2863                        //   changes to REACHABLE.  The one exception is that upper-layer advice has
2864                        //   no effect on entries in the INCOMPLETE state (e.g., for which no link-
2865                        //   layer address is cached).
2866                        //
2867                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2868                        let link_address = match e {
2869                            DynamicNeighborState::Incomplete(_) => return,
2870                            DynamicNeighborState::Reachable(Reachable {
2871                                link_address,
2872                                last_confirmed_at: _,
2873                            })
2874                            | DynamicNeighborState::Stale(Stale { link_address })
2875                            | DynamicNeighborState::Delay(Delay { link_address })
2876                            | DynamicNeighborState::Probe(Probe {
2877                                link_address,
2878                                transmit_counter: _,
2879                            })
2880                            | DynamicNeighborState::Unreachable(Unreachable {
2881                                link_address,
2882                                mode: _,
2883                            }) => *link_address,
2884                        };
2885                        e.enter_reachable(
2886                            core_ctx,
2887                            bindings_ctx,
2888                            timer_heap,
2889                            device_id,
2890                            neighbor,
2891                            link_address,
2892                        );
2893                    }
2894                },
2895            }
2896        },
2897    );
2898}
2899
2900/// Performs a linear scan of the neighbor table, discarding enough entries to
2901/// bring the total size under `GC_THRESHOLD` if possible.
2902///
2903/// Static neighbor entries are never discarded, nor are any entries that are
2904/// considered to be in use, which is defined as an entry in REACHABLE,
2905/// INCOMPLETE, DELAY, or PROBE. In other words, the only entries eligible to be
2906/// discarded are those in STALE or UNREACHABLE. This is reasonable because all
2907/// other states represent entries to which we have either recently sent packets
2908/// (REACHABLE, DELAY, PROBE), or which we are actively trying to resolve and
2909/// for which we have recently queued outgoing packets (INCOMPLETE).
2910fn collect_garbage_inner<I, D, DeviceId, BC>(
2911    neighbors: &mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2912    gc_state: &mut GarbageCollectionState<BC::Instant>,
2913    timer_heap: &mut TimerHeap<I, BC>,
2914    bindings_ctx: &mut BC,
2915    device_id: &DeviceId,
2916) where
2917    I: Ip,
2918    D: LinkDevice,
2919    DeviceId: StrongDeviceIdentifier,
2920    BC: NudBindingsContext<I, D, DeviceId>,
2921{
2922    let GarbageCollectionState { last_gc, is_dirty } = gc_state;
2923    // Short circuit if we know there are no discardable entries in the table.
2924    if !*is_dirty {
2925        return;
2926    }
2927
2928    let max_to_remove = neighbors.len().saturating_sub(GC_THRESHOLD);
2929    if max_to_remove == 0 {
2930        return;
2931    }
2932
2933    let mut is_still_dirty = false;
2934
2935    // Define an ordering by priority for garbage collection, such that lower
2936    // numbers correspond to higher usefulness and therefore lower likelihood of
2937    // being discarded.
2938    //
2939    // TODO(https://fxbug.dev/42075782): once neighbor entries hold a timestamp
2940    // tracking when they were last updated, consider using this timestamp to break
2941    // ties between entries in the same state, so that we discard less recently
2942    // updated entries before more recently updated ones.
2943    fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2944        state: &DynamicNeighborState<D, BT>,
2945    ) -> usize {
2946        match state {
2947            DynamicNeighborState::Incomplete(_)
2948            | DynamicNeighborState::Reachable(_)
2949            | DynamicNeighborState::Delay(_)
2950            | DynamicNeighborState::Probe(_) => unreachable!(
2951                "the netstack should only ever discard STALE or UNREACHABLE entries; \
2952                    found {:?}",
2953                state,
2954            ),
2955            DynamicNeighborState::Stale(_) => 0,
2956            DynamicNeighborState::Unreachable(Unreachable {
2957                link_address: _,
2958                mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2959            }) => 1,
2960            DynamicNeighborState::Unreachable(Unreachable {
2961                link_address: _,
2962                mode: UnreachableMode::WaitingForPacketSend,
2963            }) => 2,
2964        }
2965    }
2966
2967    struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2968        key: K,
2969        state: &'a mut DynamicNeighborState<D, BT>,
2970    }
2971
2972    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2973        fn eq(&self, other: &Self) -> bool {
2974            self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2975        }
2976    }
2977    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2978    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2979        fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2980            // Sort in reverse order so `BinaryHeap` will function as a min-heap rather than
2981            // a max-heap. This means it will maintain the minimum (i.e. most useful) entry
2982            // at the top of the heap.
2983            gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2984        }
2985    }
2986    impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2987        fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2988            Some(self.cmp(&other))
2989        }
2990    }
2991
2992    let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2993    for (ip, neighbor) in neighbors.iter_mut() {
2994        match neighbor {
2995            NeighborState::Static(_) => {
2996                // Don't discard static entries.
2997                continue;
2998            }
2999            NeighborState::Dynamic(state) => {
3000                match state {
3001                    DynamicNeighborState::Incomplete(_)
3002                    | DynamicNeighborState::Reachable(_)
3003                    | DynamicNeighborState::Delay(_)
3004                    | DynamicNeighborState::Probe(_) => {
3005                        // Don't discard in-use entries.
3006                        continue;
3007                    }
3008                    DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
3009                        // Unconditionally insert the first `max_to_remove` entries.
3010                        if entries_to_remove.len() < max_to_remove {
3011                            entries_to_remove.push(SortEntry { key: ip, state });
3012                            continue;
3013                        }
3014                        // If we exceed `max_to_remove`, the table will still
3015                        // have discardable entries after this run. Prioritize
3016                        // the removal of neighbors that are less useful (based
3017                        // on their ordering).
3018                        is_still_dirty = true;
3019                        let minimum =
3020                            entries_to_remove.peek().expect("heap should have at least 1 entry");
3021                        let candidate = SortEntry { key: ip, state };
3022                        if &candidate > minimum {
3023                            let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
3024                            entries_to_remove.push(candidate);
3025                        }
3026                    }
3027                }
3028            }
3029        }
3030    }
3031
3032    let entries_to_remove = entries_to_remove
3033        .into_iter()
3034        .map(|SortEntry { key: neighbor, state }| {
3035            state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
3036            *neighbor
3037        })
3038        .collect::<Vec<_>>();
3039
3040    for neighbor in entries_to_remove {
3041        assert_matches!(neighbors.remove(&neighbor), Some(_));
3042        bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
3043    }
3044
3045    *last_gc = Some(bindings_ctx.now());
3046    *is_dirty = is_still_dirty;
3047}
3048
3049fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
3050where
3051    I: Ip,
3052    D: LinkDevice,
3053    BC: NudBindingsContext<I, D, CC::DeviceId>,
3054    CC: NudContext<I, D, BC>,
3055{
3056    core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, gc_state, timer_heap }, _| {
3057        collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, &device_id);
3058    })
3059}
3060
3061#[cfg(test)]
3062mod tests {
3063    use alloc::vec;
3064
3065    use ip_test_macro::ip_test;
3066    use net_declare::{net_ip_v4, net_ip_v6};
3067    use net_types::UnicastAddr;
3068    use net_types::ip::{Ipv4Addr, Ipv6Addr};
3069    use netstack3_base::testutil::{
3070        FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
3071        FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
3072    };
3073    use netstack3_base::{
3074        CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
3075    };
3076    use netstack3_hashmap::HashSet;
3077    use test_case::test_case;
3078
3079    use super::*;
3080    use crate::internal::device::nud::api::{NeighborApi, StaticNeighborInsertionError};
3081    use packet::NestableSerializer as _;
3082
3083    struct FakeNudContext<I: Ip, D: LinkDevice> {
3084        state: NudState<I, D, FakeBindingsCtxImpl<I>>,
3085        counters: NudCounters<I>,
3086    }
3087
3088    struct FakeConfigContext {
3089        retrans_timer: NonZeroDuration,
3090        nud_config: NudUserConfig,
3091    }
3092
3093    struct FakeCoreCtxImpl<I: Ip> {
3094        nud: FakeNudContext<I, FakeLinkDevice>,
3095        inner: FakeInnerCtxImpl<I>,
3096    }
3097
3098    type FakeInnerCtxImpl<I> =
3099        FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
3100
3101    #[derive(Debug, PartialEq, Eq)]
3102    enum FakeNudMessageMeta<I: Ip> {
3103        NeighborSolicitation {
3104            lookup_addr: SpecifiedAddr<I::Addr>,
3105            remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3106        },
3107        IpFrame {
3108            dst_link_address: UnicastAddr<FakeLinkAddress>,
3109        },
3110        IcmpDestUnreachable,
3111    }
3112
3113    type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3114        NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3115        Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3116        (),
3117        (),
3118    >;
3119
3120    impl<I: Ip> FakeCoreCtxImpl<I> {
3121        fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3122            Self {
3123                nud: {
3124                    FakeNudContext {
3125                        state: NudState::new::<_, IntoCoreTimerCtx>(
3126                            bindings_ctx,
3127                            FakeWeakDeviceId(FakeLinkDeviceId),
3128                        ),
3129                        counters: Default::default(),
3130                    }
3131                },
3132                inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3133                    retrans_timer: ONE_SECOND,
3134                    // Use different values from the defaults in tests so we get
3135                    // coverage that the config is used everywhere and not the
3136                    // defaults.
3137                    nud_config: NudUserConfig {
3138                        max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3139                        max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3140                        base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3141                        retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3142                    },
3143                }),
3144            }
3145        }
3146    }
3147
3148    fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3149        CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3150    }
3151
3152    impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3153        type DeviceId = FakeLinkDeviceId;
3154        type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3155    }
3156
3157    impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3158        type ConfigCtx<'a> = FakeConfigContext;
3159
3160        type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3161
3162        fn with_nud_state_mut_and_sender_ctx<
3163            O,
3164            F: FnOnce(
3165                &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3166                &mut Self::SenderCtx<'_>,
3167            ) -> O,
3168        >(
3169            &mut self,
3170            _device_id: &Self::DeviceId,
3171            cb: F,
3172        ) -> O {
3173            cb(&mut self.nud.state, &mut self.inner)
3174        }
3175
3176        fn with_nud_state_mut<
3177            O,
3178            F: FnOnce(
3179                &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3180                &mut Self::ConfigCtx<'_>,
3181            ) -> O,
3182        >(
3183            &mut self,
3184            &FakeLinkDeviceId: &FakeLinkDeviceId,
3185            cb: F,
3186        ) -> O {
3187            cb(&mut self.nud.state, &mut self.inner.state)
3188        }
3189
3190        fn with_nud_state<
3191            O,
3192            F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3193        >(
3194            &mut self,
3195            &FakeLinkDeviceId: &FakeLinkDeviceId,
3196            cb: F,
3197        ) -> O {
3198            cb(&self.nud.state)
3199        }
3200
3201        fn send_neighbor_solicitation(
3202            &mut self,
3203            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3204            &FakeLinkDeviceId: &FakeLinkDeviceId,
3205            lookup_addr: SpecifiedAddr<I::Addr>,
3206            remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3207        ) {
3208            self.inner
3209                .send_frame(
3210                    bindings_ctx,
3211                    FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3212                    Buf::new(Vec::new(), ..),
3213                )
3214                .unwrap()
3215        }
3216    }
3217
3218    impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3219        for FakeCoreCtxImpl<I>
3220    {
3221        fn send_icmp_dest_unreachable(
3222            &mut self,
3223            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3224            frame: Buf<Vec<u8>>,
3225            _device_id: Option<&Self::DeviceId>,
3226            _original_src_ip: SocketIpAddr<I::Addr>,
3227            _original_dst_ip: SocketIpAddr<I::Addr>,
3228            _header_len: usize,
3229            _proto: I::Proto,
3230            _metadata: I::Metadata,
3231        ) {
3232            self.inner
3233                .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3234                .unwrap()
3235        }
3236    }
3237
3238    impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3239        fn counters(&self) -> &NudCounters<I> {
3240            &self.nud.counters
3241        }
3242    }
3243
3244    impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3245        fn retransmit_timeout(&mut self) -> NonZeroDuration {
3246            self.retrans_timer
3247        }
3248
3249        fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3250            cb(&self.nud_config)
3251        }
3252
3253        fn override_lock_time(&mut self) -> Duration {
3254            Duration::ZERO
3255        }
3256    }
3257
3258    impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3259        fn send_ip_packet_to_neighbor_link_addr<S>(
3260            &mut self,
3261            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3262            dst_link_address: UnicastAddr<FakeLinkAddress>,
3263            body: S,
3264            _tx_meta: FakeTxMetadata,
3265        ) -> Result<(), SendFrameError<S>>
3266        where
3267            S: NetworkSerializer,
3268            S::Buffer: BufferMut,
3269        {
3270            self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3271        }
3272    }
3273
3274    impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3275        fn retransmit_timeout(&mut self) -> NonZeroDuration {
3276            <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3277        }
3278
3279        fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3280            <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3281        }
3282
3283        fn override_lock_time(&mut self) -> Duration {
3284            <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3285        }
3286    }
3287
3288    const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3289
3290    #[track_caller]
3291    fn check_lookup_has<I: Ip>(
3292        core_ctx: &mut FakeCoreCtxImpl<I>,
3293        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3294        lookup_addr: SpecifiedAddr<I::Addr>,
3295        expected_link_addr: UnicastAddr<FakeLinkAddress>,
3296    ) {
3297        let entry = assert_matches!(
3298            core_ctx.nud.state.neighbors.get(&lookup_addr),
3299            Some(entry @ (
3300                NeighborState::Dynamic(
3301                    DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3302                    | DynamicNeighborState::Stale (Stale { link_address })
3303                    | DynamicNeighborState::Delay (Delay { link_address })
3304                    | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3305                    | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3306                )
3307                | NeighborState::Static(link_address)
3308            )) => {
3309                assert_eq!(link_address, &expected_link_addr);
3310                entry
3311            }
3312        );
3313        match entry {
3314            NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3315                unreachable!("entry must be static, REACHABLE, or STALE")
3316            }
3317            NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3318                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3319                    bindings_ctx,
3320                    [(
3321                        lookup_addr,
3322                        NudEvent::ReachableTime,
3323                        core_ctx.inner.base_reachable_time().get(),
3324                    )],
3325                );
3326            }
3327            NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3328                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3329                    bindings_ctx,
3330                    [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3331                );
3332            }
3333            NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3334                core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3335                    bindings_ctx,
3336                    [(
3337                        lookup_addr,
3338                        NudEvent::RetransmitUnicastProbe,
3339                        core_ctx.inner.state.retrans_timer.get(),
3340                    )],
3341                );
3342            }
3343            NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3344                link_address: _,
3345                mode,
3346            })) => {
3347                let instant = match mode {
3348                    UnreachableMode::WaitingForPacketSend => None,
3349                    mode @ UnreachableMode::Backoff { .. } => {
3350                        let duration =
3351                            mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3352                        Some(bindings_ctx.now() + duration.get())
3353                    }
3354                };
3355                if let Some(instant) = instant {
3356                    core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3357                        lookup_addr,
3358                        NudEvent::RetransmitUnicastProbe,
3359                        instant,
3360                    )]);
3361                }
3362            }
3363            NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3364            | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3365        }
3366    }
3367
3368    trait TestIpExt: NudIcmpIpExt {
3369        const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3370        const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3371        const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3372    }
3373
3374    impl TestIpExt for Ipv4 {
3375        // Safe because the address is non-zero.
3376        const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3377            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3378        const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3379            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3380        const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3381            unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3382    }
3383
3384    impl TestIpExt for Ipv6 {
3385        // Safe because the address is non-zero.
3386        const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3387            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3388        const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3389            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3390        const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3391            unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3392    }
3393
3394    const LINK_ADDR1: UnicastAddr<FakeLinkAddress> =
3395        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([2])) };
3396    const LINK_ADDR2: UnicastAddr<FakeLinkAddress> =
3397        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([4])) };
3398    const LINK_ADDR3: UnicastAddr<FakeLinkAddress> =
3399        unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([6])) };
3400
3401    impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3402        fn neighbor() -> Self {
3403            Self {
3404                device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3405                timer_type: NudTimerType::Neighbor,
3406                _marker: PhantomData,
3407            }
3408        }
3409
3410        fn garbage_collection() -> Self {
3411            Self {
3412                device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3413                timer_type: NudTimerType::GarbageCollection,
3414                _marker: PhantomData,
3415            }
3416        }
3417    }
3418
3419    fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3420        core_ctx: &mut FakeCoreCtxImpl<I>,
3421        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3422        neighbor: SpecifiedAddr<I::Addr>,
3423        pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3424        body: u8,
3425        expect_event: bool,
3426    ) {
3427        let body = [body];
3428        assert_eq!(
3429            NudHandler::send_ip_packet_to_neighbor(
3430                core_ctx,
3431                bindings_ctx,
3432                &FakeLinkDeviceId,
3433                neighbor,
3434                Buf::new(body, ..),
3435                FakeTxMetadata::default(),
3436            ),
3437            Ok(())
3438        );
3439
3440        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3441
3442        pending_frames.push_back(Buf::new(body.to_vec(), ..));
3443
3444        assert_neighbor_state_with_ip(
3445            core_ctx,
3446            bindings_ctx,
3447            neighbor,
3448            DynamicNeighborState::Incomplete(Incomplete {
3449                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3450                pending_frames: pending_frames
3451                    .iter()
3452                    .cloned()
3453                    .map(|buf| (buf, FakeTxMetadata::default()))
3454                    .collect(),
3455                notifiers: Vec::new(),
3456                _marker: PhantomData,
3457            }),
3458            expect_event.then_some(ExpectedEvent::Added),
3459        );
3460
3461        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3462            bindings_ctx,
3463            [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3464        );
3465    }
3466
3467    fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3468        core_ctx: &mut FakeCoreCtxImpl<I>,
3469        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3470        ip_address: SpecifiedAddr<I::Addr>,
3471        take_probe: bool,
3472    ) -> VecDeque<Buf<Vec<u8>>> {
3473        let mut pending_frames = VecDeque::new();
3474        queue_ip_packet_to_unresolved_neighbor(
3475            core_ctx,
3476            bindings_ctx,
3477            ip_address,
3478            &mut pending_frames,
3479            1,
3480            true, /* expect_event */
3481        );
3482        if take_probe {
3483            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3484        }
3485        pending_frames
3486    }
3487
3488    fn init_incomplete_neighbor<I: TestIpExt>(
3489        core_ctx: &mut FakeCoreCtxImpl<I>,
3490        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3491        take_probe: bool,
3492    ) -> VecDeque<Buf<Vec<u8>>> {
3493        init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3494    }
3495
3496    fn init_stale_neighbor_with_ip<I: TestIpExt>(
3497        core_ctx: &mut FakeCoreCtxImpl<I>,
3498        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3499        ip_address: SpecifiedAddr<I::Addr>,
3500        link_address: UnicastAddr<FakeLinkAddress>,
3501    ) {
3502        NudHandler::handle_neighbor_update(
3503            core_ctx,
3504            bindings_ctx,
3505            &FakeLinkDeviceId,
3506            ip_address,
3507            DynamicNeighborUpdateSource::Probe { link_address },
3508        );
3509        assert_neighbor_state_with_ip(
3510            core_ctx,
3511            bindings_ctx,
3512            ip_address,
3513            DynamicNeighborState::Stale(Stale { link_address }),
3514            Some(ExpectedEvent::Added),
3515        );
3516    }
3517
3518    fn init_stale_neighbor<I: TestIpExt>(
3519        core_ctx: &mut FakeCoreCtxImpl<I>,
3520        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3521        link_address: UnicastAddr<FakeLinkAddress>,
3522    ) {
3523        init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3524    }
3525
3526    fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3527        core_ctx: &mut FakeCoreCtxImpl<I>,
3528        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3529        ip_address: SpecifiedAddr<I::Addr>,
3530        link_address: UnicastAddr<FakeLinkAddress>,
3531    ) {
3532        let queued_frame =
3533            init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3534        NudHandler::handle_neighbor_update(
3535            core_ctx,
3536            bindings_ctx,
3537            &FakeLinkDeviceId,
3538            ip_address,
3539            DynamicNeighborUpdateSource::Confirmation {
3540                link_address: Some(link_address),
3541                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3542            },
3543        );
3544        assert_neighbor_state_with_ip(
3545            core_ctx,
3546            bindings_ctx,
3547            ip_address,
3548            DynamicNeighborState::Reachable(Reachable {
3549                link_address,
3550                last_confirmed_at: bindings_ctx.now(),
3551            }),
3552            Some(ExpectedEvent::Changed),
3553        );
3554        assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3555    }
3556
3557    fn init_reachable_neighbor<I: TestIpExt>(
3558        core_ctx: &mut FakeCoreCtxImpl<I>,
3559        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3560        link_address: UnicastAddr<FakeLinkAddress>,
3561    ) {
3562        init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3563    }
3564
3565    fn init_delay_neighbor_with_ip<I: TestIpExt>(
3566        core_ctx: &mut FakeCoreCtxImpl<I>,
3567        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3568        ip_address: SpecifiedAddr<I::Addr>,
3569        link_address: UnicastAddr<FakeLinkAddress>,
3570    ) {
3571        init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3572        assert_eq!(
3573            NudHandler::send_ip_packet_to_neighbor(
3574                core_ctx,
3575                bindings_ctx,
3576                &FakeLinkDeviceId,
3577                ip_address,
3578                Buf::new([1], ..),
3579                FakeTxMetadata::default(),
3580            ),
3581            Ok(())
3582        );
3583        assert_neighbor_state_with_ip(
3584            core_ctx,
3585            bindings_ctx,
3586            ip_address,
3587            DynamicNeighborState::Delay(Delay { link_address }),
3588            Some(ExpectedEvent::Changed),
3589        );
3590        assert_eq!(
3591            core_ctx.inner.take_frames(),
3592            vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3593        );
3594    }
3595
3596    fn init_delay_neighbor<I: TestIpExt>(
3597        core_ctx: &mut FakeCoreCtxImpl<I>,
3598        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3599        link_address: UnicastAddr<FakeLinkAddress>,
3600    ) {
3601        init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3602    }
3603
3604    fn init_probe_neighbor_with_ip<I: TestIpExt>(
3605        core_ctx: &mut FakeCoreCtxImpl<I>,
3606        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3607        ip_address: SpecifiedAddr<I::Addr>,
3608        link_address: UnicastAddr<FakeLinkAddress>,
3609        take_probe: bool,
3610    ) {
3611        init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3612        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3613        core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3614        assert_eq!(
3615            bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3616            [NudTimerId::neighbor()]
3617        );
3618        assert_neighbor_state_with_ip(
3619            core_ctx,
3620            bindings_ctx,
3621            ip_address,
3622            DynamicNeighborState::Probe(Probe {
3623                link_address,
3624                transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3625            }),
3626            Some(ExpectedEvent::Changed),
3627        );
3628        if take_probe {
3629            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3630        }
3631    }
3632
3633    fn init_probe_neighbor<I: TestIpExt>(
3634        core_ctx: &mut FakeCoreCtxImpl<I>,
3635        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3636        link_address: UnicastAddr<FakeLinkAddress>,
3637        take_probe: bool,
3638    ) {
3639        init_probe_neighbor_with_ip(
3640            core_ctx,
3641            bindings_ctx,
3642            I::LOOKUP_ADDR1,
3643            link_address,
3644            take_probe,
3645        );
3646    }
3647
3648    fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3649        core_ctx: &mut FakeCoreCtxImpl<I>,
3650        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3651        ip_address: SpecifiedAddr<I::Addr>,
3652        link_address: UnicastAddr<FakeLinkAddress>,
3653    ) {
3654        init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3655        let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3656        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3657        for _ in 0..max_unicast_solicit {
3658            assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3659            assert_eq!(
3660                bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3661                [NudTimerId::neighbor()]
3662            );
3663        }
3664        assert_neighbor_state_with_ip(
3665            core_ctx,
3666            bindings_ctx,
3667            ip_address,
3668            DynamicNeighborState::Unreachable(Unreachable {
3669                link_address,
3670                mode: UnreachableMode::WaitingForPacketSend,
3671            }),
3672            Some(ExpectedEvent::Changed),
3673        );
3674    }
3675
3676    fn init_unreachable_neighbor<I: TestIpExt>(
3677        core_ctx: &mut FakeCoreCtxImpl<I>,
3678        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3679        link_address: UnicastAddr<FakeLinkAddress>,
3680    ) {
3681        init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3682    }
3683
3684    #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3685    enum InitialState {
3686        Incomplete,
3687        Stale,
3688        Reachable,
3689        Delay,
3690        Probe,
3691        Unreachable,
3692    }
3693
3694    fn init_neighbor_in_state<I: TestIpExt>(
3695        core_ctx: &mut FakeCoreCtxImpl<I>,
3696        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3697        state: InitialState,
3698    ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3699        match state {
3700            InitialState::Incomplete => {
3701                let _: VecDeque<Buf<Vec<u8>>> =
3702                    init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3703            }
3704            InitialState::Reachable => {
3705                init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3706            }
3707            InitialState::Stale => {
3708                init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3709            }
3710            InitialState::Delay => {
3711                init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3712            }
3713            InitialState::Probe => {
3714                init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3715            }
3716            InitialState::Unreachable => {
3717                init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3718            }
3719        }
3720        assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3721            Some(NeighborState::Dynamic(state)) => state.clone()
3722        )
3723    }
3724
3725    #[track_caller]
3726    fn init_static_neighbor_with_ip<I: TestIpExt>(
3727        core_ctx: &mut FakeCoreCtxImpl<I>,
3728        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3729        ip_address: SpecifiedAddr<I::Addr>,
3730        link_address: UnicastAddr<FakeLinkAddress>,
3731        expected_event: ExpectedEvent,
3732    ) {
3733        let mut ctx = CtxPair { core_ctx, bindings_ctx };
3734        NeighborApi::new(&mut ctx)
3735            .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3736            .unwrap();
3737        assert_eq!(
3738            ctx.bindings_ctx.take_events(),
3739            [Event {
3740                device: FakeLinkDeviceId,
3741                addr: ip_address,
3742                kind: match expected_event {
3743                    ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3744                    ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3745                },
3746                at: ctx.bindings_ctx.now(),
3747            }],
3748        );
3749    }
3750
3751    #[track_caller]
3752    fn init_static_neighbor<I: TestIpExt>(
3753        core_ctx: &mut FakeCoreCtxImpl<I>,
3754        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3755        link_address: UnicastAddr<FakeLinkAddress>,
3756        expected_event: ExpectedEvent,
3757    ) {
3758        init_static_neighbor_with_ip(
3759            core_ctx,
3760            bindings_ctx,
3761            I::LOOKUP_ADDR1,
3762            link_address,
3763            expected_event,
3764        );
3765    }
3766
3767    #[track_caller]
3768    fn delete_neighbor<I: TestIpExt>(
3769        core_ctx: &mut FakeCoreCtxImpl<I>,
3770        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3771    ) {
3772        let mut ctx = CtxPair { core_ctx, bindings_ctx };
3773        NeighborApi::new(&mut ctx)
3774            .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3775            .expect("neighbor entry should exist");
3776        assert_eq!(
3777            ctx.bindings_ctx.take_events(),
3778            [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3779        );
3780    }
3781
3782    #[track_caller]
3783    fn assert_neighbor_state<I: TestIpExt>(
3784        core_ctx: &FakeCoreCtxImpl<I>,
3785        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3786        state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3787        event_kind: Option<ExpectedEvent>,
3788    ) {
3789        assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3790    }
3791
3792    #[derive(Clone, Copy, Debug)]
3793    enum ExpectedEvent {
3794        Added,
3795        Changed,
3796    }
3797
3798    #[track_caller]
3799    fn assert_neighbor_state_with_ip<I: TestIpExt>(
3800        core_ctx: &FakeCoreCtxImpl<I>,
3801        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3802        neighbor: SpecifiedAddr<I::Addr>,
3803        state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3804        expected_event: Option<ExpectedEvent>,
3805    ) {
3806        if let Some(expected_event) = expected_event {
3807            let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3808            assert_eq!(
3809                bindings_ctx.take_events(),
3810                [Event {
3811                    device: FakeLinkDeviceId,
3812                    addr: neighbor,
3813                    kind: match expected_event {
3814                        ExpectedEvent::Added => EventKind::Added(event_state),
3815                        ExpectedEvent::Changed => EventKind::Changed(event_state),
3816                    },
3817                    at: bindings_ctx.now(),
3818                }],
3819            );
3820        }
3821
3822        assert_eq!(
3823            core_ctx.nud.state.neighbors.get(&neighbor),
3824            Some(&NeighborState::Dynamic(state))
3825        );
3826    }
3827
3828    #[track_caller]
3829    fn assert_pending_frame_sent<I: TestIpExt>(
3830        core_ctx: &mut FakeCoreCtxImpl<I>,
3831        pending_frames: VecDeque<Buf<Vec<u8>>>,
3832        link_address: UnicastAddr<FakeLinkAddress>,
3833    ) {
3834        assert_eq!(
3835            core_ctx.inner.take_frames(),
3836            pending_frames
3837                .into_iter()
3838                .map(|f| (
3839                    FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3840                    f.as_ref().to_vec(),
3841                ))
3842                .collect::<Vec<_>>()
3843        );
3844    }
3845
3846    #[track_caller]
3847    fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3848        core_ctx: &mut FakeCoreCtxImpl<I>,
3849        ip_address: SpecifiedAddr<I::Addr>,
3850        link_address: Option<UnicastAddr<FakeLinkAddress>>,
3851    ) {
3852        assert_eq!(
3853            core_ctx.inner.take_frames(),
3854            [(
3855                FakeNudMessageMeta::NeighborSolicitation {
3856                    lookup_addr: ip_address,
3857                    remote_link_addr: link_address,
3858                },
3859                Vec::new()
3860            )]
3861        );
3862    }
3863
3864    #[track_caller]
3865    fn assert_neighbor_probe_sent<I: TestIpExt>(
3866        core_ctx: &mut FakeCoreCtxImpl<I>,
3867        link_address: Option<UnicastAddr<FakeLinkAddress>>,
3868    ) {
3869        assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3870    }
3871
3872    #[track_caller]
3873    fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3874        core_ctx: &mut FakeCoreCtxImpl<I>,
3875        bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3876        neighbor: SpecifiedAddr<I::Addr>,
3877    ) {
3878        super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3879        assert_eq!(
3880            bindings_ctx.take_events(),
3881            [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3882        );
3883    }
3884
3885    #[ip_test(I)]
3886    fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3887        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3888
3889        // Try to send a packet for which serialization will fail due to a size
3890        // constraint.
3891        let packet = Buf::new([0; 2], ..).with_size_limit(1);
3892
3893        let err = assert_matches!(
3894            NudHandler::send_ip_packet_to_neighbor(
3895                &mut core_ctx,
3896                &mut bindings_ctx,
3897                &FakeLinkDeviceId,
3898                I::LOOKUP_ADDR1,
3899                packet,
3900                FakeTxMetadata::default(),
3901            ),
3902            Err(ErrorAndSerializer { error, serializer: _ }) => error
3903        );
3904        assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3905
3906        // The neighbor should not be inserted in the table, a probe should not be sent,
3907        // and no retransmission timer should be scheduled.
3908        super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3909        assert_eq!(core_ctx.inner.take_frames(), []);
3910        bindings_ctx.timers.assert_no_timers_installed();
3911    }
3912
3913    #[ip_test(I)]
3914    fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3915        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3916
3917        // Initialize a neighbor in INCOMPLETE.
3918        let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3919
3920        // Handle an incoming probe from that neighbor.
3921        NudHandler::handle_neighbor_update(
3922            &mut core_ctx,
3923            &mut bindings_ctx,
3924            &FakeLinkDeviceId,
3925            I::LOOKUP_ADDR1,
3926            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3927        );
3928
3929        // Neighbor should now be in STALE, per RFC 4861 section 7.2.3.
3930        assert_neighbor_state(
3931            &core_ctx,
3932            &mut bindings_ctx,
3933            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3934            Some(ExpectedEvent::Changed),
3935        );
3936        assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3937    }
3938
3939    #[ip_test(I)]
3940    #[test_case(true, true; "solicited override")]
3941    #[test_case(true, false; "solicited non-override")]
3942    #[test_case(false, true; "unsolicited override")]
3943    #[test_case(false, false; "unsolicited non-override")]
3944    fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3945        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3946
3947        // Initialize a neighbor in INCOMPLETE.
3948        let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3949
3950        // Handle an incoming confirmation from that neighbor.
3951        NudHandler::handle_neighbor_update(
3952            &mut core_ctx,
3953            &mut bindings_ctx,
3954            &FakeLinkDeviceId,
3955            I::LOOKUP_ADDR1,
3956            DynamicNeighborUpdateSource::Confirmation {
3957                link_address: Some(LINK_ADDR1),
3958                flags: ConfirmationFlags { solicited_flag, override_flag },
3959            },
3960        );
3961
3962        let expected_state = if solicited_flag {
3963            DynamicNeighborState::Reachable(Reachable {
3964                link_address: LINK_ADDR1,
3965                last_confirmed_at: bindings_ctx.now(),
3966            })
3967        } else {
3968            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3969        };
3970        assert_neighbor_state(
3971            &core_ctx,
3972            &mut bindings_ctx,
3973            expected_state,
3974            Some(ExpectedEvent::Changed),
3975        );
3976        assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3977    }
3978
3979    #[ip_test(I)]
3980    fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3981        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3982
3983        // Initialize a neighbor in REACHABLE.
3984        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3985
3986        // After reachable time, neighbor should transition to STALE.
3987        assert_eq!(
3988            bindings_ctx
3989                .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3990            [NudTimerId::neighbor()]
3991        );
3992        assert_neighbor_state(
3993            &core_ctx,
3994            &mut bindings_ctx,
3995            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3996            Some(ExpectedEvent::Changed),
3997        );
3998    }
3999
4000    #[ip_test(I)]
4001    #[test_case(InitialState::Reachable, true; "reachable with different address")]
4002    #[test_case(InitialState::Reachable, false; "reachable with same address")]
4003    #[test_case(InitialState::Stale, true; "stale with different address")]
4004    #[test_case(InitialState::Stale, false; "stale with same address")]
4005    #[test_case(InitialState::Delay, true; "delay with different address")]
4006    #[test_case(InitialState::Delay, false; "delay with same address")]
4007    #[test_case(InitialState::Probe, true; "probe with different address")]
4008    #[test_case(InitialState::Probe, false; "probe with same address")]
4009    #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
4010    #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
4011    fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
4012        initial_state: InitialState,
4013        update_link_address: bool,
4014    ) {
4015        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4016
4017        // Initialize a neighbor.
4018        let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4019
4020        // Handle an incoming probe, possibly with an updated link address.
4021        NudHandler::handle_neighbor_update(
4022            &mut core_ctx,
4023            &mut bindings_ctx,
4024            &FakeLinkDeviceId,
4025            I::LOOKUP_ADDR1,
4026            DynamicNeighborUpdateSource::Probe {
4027                link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
4028            },
4029        );
4030
4031        // If the link address was updated, the neighbor should now be in STALE with the
4032        // new link address, per RFC 4861 section 7.2.3.
4033        //
4034        // If the link address is the same, the entry should remain in its initial
4035        // state.
4036        let expected_state = if update_link_address {
4037            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
4038        } else {
4039            initial_state
4040        };
4041        assert_neighbor_state(
4042            &core_ctx,
4043            &mut bindings_ctx,
4044            expected_state,
4045            update_link_address.then_some(ExpectedEvent::Changed),
4046        );
4047    }
4048
4049    #[ip_test(I)]
4050    #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4051    #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4052    #[test_case(InitialState::Stale, true; "stale with override flag set")]
4053    #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4054    #[test_case(InitialState::Delay, true; "delay with override flag set")]
4055    #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4056    #[test_case(InitialState::Probe, true; "probe with override flag set")]
4057    #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4058    #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4059    #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4060    fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
4061        initial_state: InitialState,
4062        override_flag: bool,
4063    ) {
4064        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4065
4066        // Initialize a neighbor.
4067        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4068
4069        // Handle an incoming solicited confirmation.
4070        NudHandler::handle_neighbor_update(
4071            &mut core_ctx,
4072            &mut bindings_ctx,
4073            &FakeLinkDeviceId,
4074            I::LOOKUP_ADDR1,
4075            DynamicNeighborUpdateSource::Confirmation {
4076                link_address: Some(LINK_ADDR1),
4077                flags: ConfirmationFlags { solicited_flag: true, override_flag },
4078            },
4079        );
4080
4081        // Neighbor should now be in REACHABLE, per RFC 4861 section 7.2.5.
4082        let now = bindings_ctx.now();
4083        assert_neighbor_state(
4084            &core_ctx,
4085            &mut bindings_ctx,
4086            DynamicNeighborState::Reachable(Reachable {
4087                link_address: LINK_ADDR1,
4088                last_confirmed_at: now,
4089            }),
4090            (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4091        );
4092    }
4093
4094    #[ip_test(I)]
4095    #[test_case(InitialState::Reachable; "reachable")]
4096    #[test_case(InitialState::Stale; "stale")]
4097    #[test_case(InitialState::Delay; "delay")]
4098    #[test_case(InitialState::Probe; "probe")]
4099    #[test_case(InitialState::Unreachable; "unreachable")]
4100    fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
4101        I: TestIpExt,
4102    >(
4103        initial_state: InitialState,
4104    ) {
4105        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4106
4107        // Initialize a neighbor.
4108        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4109
4110        // Handle an incoming unsolicited override confirmation with a different link address.
4111        NudHandler::handle_neighbor_update(
4112            &mut core_ctx,
4113            &mut bindings_ctx,
4114            &FakeLinkDeviceId,
4115            I::LOOKUP_ADDR1,
4116            DynamicNeighborUpdateSource::Confirmation {
4117                link_address: Some(LINK_ADDR2),
4118                flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4119            },
4120        );
4121
4122        // Neighbor should now be in STALE, per RFC 4861 section 7.2.5.
4123        assert_neighbor_state(
4124            &core_ctx,
4125            &mut bindings_ctx,
4126            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4127            Some(ExpectedEvent::Changed),
4128        );
4129    }
4130
4131    #[ip_test(I)]
4132    #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4133    #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4134    #[test_case(InitialState::Stale, true; "stale with override flag set")]
4135    #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4136    #[test_case(InitialState::Delay, true; "delay with override flag set")]
4137    #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4138    #[test_case(InitialState::Probe, true; "probe with override flag set")]
4139    #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4140    #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4141    #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4142    fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4143        initial_state: InitialState,
4144        override_flag: bool,
4145    ) {
4146        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4147
4148        // Initialize a neighbor.
4149        let expected_state =
4150            init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4151
4152        // Handle an incoming unsolicited confirmation with the same link address.
4153        NudHandler::handle_neighbor_update(
4154            &mut core_ctx,
4155            &mut bindings_ctx,
4156            &FakeLinkDeviceId,
4157            I::LOOKUP_ADDR1,
4158            DynamicNeighborUpdateSource::Confirmation {
4159                link_address: Some(LINK_ADDR1),
4160                flags: ConfirmationFlags { solicited_flag: false, override_flag },
4161            },
4162        );
4163
4164        // Neighbor should not have been updated.
4165        assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4166    }
4167
4168    #[ip_test(I)]
4169    #[test_case(InitialState::Reachable; "reachable")]
4170    #[test_case(InitialState::Stale; "stale")]
4171    #[test_case(InitialState::Delay; "delay")]
4172    #[test_case(InitialState::Probe; "probe")]
4173    #[test_case(InitialState::Unreachable; "unreachable")]
4174    fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4175        I: TestIpExt,
4176    >(
4177        initial_state: InitialState,
4178    ) {
4179        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4180
4181        // Initialize a neighbor.
4182        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4183
4184        // Handle an incoming solicited override confirmation with a different link address.
4185        NudHandler::handle_neighbor_update(
4186            &mut core_ctx,
4187            &mut bindings_ctx,
4188            &FakeLinkDeviceId,
4189            I::LOOKUP_ADDR1,
4190            DynamicNeighborUpdateSource::Confirmation {
4191                link_address: Some(LINK_ADDR2),
4192                flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4193            },
4194        );
4195
4196        // Neighbor should now be in REACHABLE, per RFC 4861 section 7.2.5.
4197        let now = bindings_ctx.now();
4198        assert_neighbor_state(
4199            &core_ctx,
4200            &mut bindings_ctx,
4201            DynamicNeighborState::Reachable(Reachable {
4202                link_address: LINK_ADDR2,
4203                last_confirmed_at: now,
4204            }),
4205            Some(ExpectedEvent::Changed),
4206        );
4207    }
4208
4209    #[ip_test(I)]
4210    fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4211        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4212
4213        // Initialize a neighbor in REACHABLE.
4214        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4215
4216        // Handle an incoming probe with the same link address.
4217        NudHandler::handle_neighbor_update(
4218            &mut core_ctx,
4219            &mut bindings_ctx,
4220            &FakeLinkDeviceId,
4221            I::LOOKUP_ADDR1,
4222            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4223        );
4224
4225        // Neighbor should still be in REACHABLE with the same link address.
4226        let now = bindings_ctx.now();
4227        assert_neighbor_state(
4228            &core_ctx,
4229            &mut bindings_ctx,
4230            DynamicNeighborState::Reachable(Reachable {
4231                link_address: LINK_ADDR1,
4232                last_confirmed_at: now,
4233            }),
4234            None,
4235        );
4236    }
4237
4238    #[ip_test(I)]
4239    #[test_case(true; "solicited")]
4240    #[test_case(false; "unsolicited")]
4241    fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4242        solicited_flag: bool,
4243    ) {
4244        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4245
4246        // Initialize a neighbor in REACHABLE.
4247        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4248
4249        // Handle an incoming non-override confirmation with a different link address.
4250        NudHandler::handle_neighbor_update(
4251            &mut core_ctx,
4252            &mut bindings_ctx,
4253            &FakeLinkDeviceId,
4254            I::LOOKUP_ADDR1,
4255            DynamicNeighborUpdateSource::Confirmation {
4256                link_address: Some(LINK_ADDR2),
4257                flags: ConfirmationFlags { override_flag: false, solicited_flag },
4258            },
4259        );
4260
4261        // Neighbor should now be in STALE, with the *same* link address as was
4262        // previously cached, per RFC 4861 section 7.2.5.
4263        assert_neighbor_state(
4264            &core_ctx,
4265            &mut bindings_ctx,
4266            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4267            Some(ExpectedEvent::Changed),
4268        );
4269    }
4270
4271    #[ip_test(I)]
4272    #[test_case(InitialState::Stale, true; "stale solicited")]
4273    #[test_case(InitialState::Stale, false; "stale unsolicited")]
4274    #[test_case(InitialState::Delay, true; "delay solicited")]
4275    #[test_case(InitialState::Delay, false; "delay unsolicited")]
4276    #[test_case(InitialState::Probe, true; "probe solicited")]
4277    #[test_case(InitialState::Probe, false; "probe unsolicited")]
4278    #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4279    #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4280    fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4281        initial_state: InitialState,
4282        solicited_flag: bool,
4283    ) {
4284        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4285
4286        // Initialize a neighbor.
4287        let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4288
4289        // Handle an incoming non-override confirmation with a different link address.
4290        NudHandler::handle_neighbor_update(
4291            &mut core_ctx,
4292            &mut bindings_ctx,
4293            &FakeLinkDeviceId,
4294            I::LOOKUP_ADDR1,
4295            DynamicNeighborUpdateSource::Confirmation {
4296                link_address: Some(LINK_ADDR2),
4297                flags: ConfirmationFlags { override_flag: false, solicited_flag },
4298            },
4299        );
4300
4301        // Neighbor should still be in the original state; the link address should *not*
4302        // have been updated.
4303        assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4304    }
4305
4306    #[ip_test(I)]
4307    fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4308        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4309
4310        // Initialize a neighbor in STALE.
4311        init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4312
4313        // Send a packet to the neighbor.
4314        let body = 1;
4315        assert_eq!(
4316            NudHandler::send_ip_packet_to_neighbor(
4317                &mut core_ctx,
4318                &mut bindings_ctx,
4319                &FakeLinkDeviceId,
4320                I::LOOKUP_ADDR1,
4321                Buf::new([body], ..),
4322                FakeTxMetadata::default(),
4323            ),
4324            Ok(())
4325        );
4326
4327        // Neighbor should be in DELAY.
4328        assert_neighbor_state(
4329            &core_ctx,
4330            &mut bindings_ctx,
4331            DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4332            Some(ExpectedEvent::Changed),
4333        );
4334        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4335            &mut bindings_ctx,
4336            [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4337        );
4338        assert_pending_frame_sent(
4339            &mut core_ctx,
4340            VecDeque::from([Buf::new(vec![body], ..)]),
4341            LINK_ADDR1,
4342        );
4343    }
4344
4345    #[ip_test(I)]
4346    #[test_case(InitialState::Delay,
4347                NudEvent::DelayFirstProbe;
4348                "delay to probe")]
4349    #[test_case(InitialState::Probe,
4350                NudEvent::RetransmitUnicastProbe;
4351                "probe retransmit unicast probe")]
4352    fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4353        initial_state: InitialState,
4354        expected_initial_event: NudEvent,
4355    ) {
4356        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4357
4358        // Initialize a neighbor.
4359        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4360
4361        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4362
4363        // If the neighbor started in DELAY, then after DELAY_FIRST_PROBE_TIME, the
4364        // neighbor should transition to PROBE and send out a unicast probe.
4365        //
4366        // If the neighbor started in PROBE, then after RetransTimer expires, the
4367        // neighbor should remain in PROBE and retransmit a unicast probe.
4368        let (time, transmit_counter) = match initial_state {
4369            InitialState::Delay => {
4370                (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4371            }
4372            InitialState::Probe => {
4373                (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4374            }
4375            other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4376        };
4377        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4378            &mut bindings_ctx,
4379            [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4380        );
4381        assert_eq!(
4382            bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4383            [NudTimerId::neighbor()]
4384        );
4385        assert_neighbor_state(
4386            &core_ctx,
4387            &mut bindings_ctx,
4388            DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4389            (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4390        );
4391        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4392            &mut bindings_ctx,
4393            [(
4394                I::LOOKUP_ADDR1,
4395                NudEvent::RetransmitUnicastProbe,
4396                core_ctx.inner.state.retrans_timer.get(),
4397            )],
4398        );
4399        assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4400    }
4401
4402    #[ip_test(I)]
4403    fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4404        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4405
4406        init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4407
4408        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4409        let timer_id = NudTimerId::neighbor();
4410
4411        // No multicast probes should be transmitted even after the retransmit timeout.
4412        assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4413        assert_eq!(core_ctx.inner.take_frames(), []);
4414
4415        // Send a packet and ensure that we also transmit a multicast probe.
4416        const BODY: u8 = 0x33;
4417        assert_eq!(
4418            NudHandler::send_ip_packet_to_neighbor(
4419                &mut core_ctx,
4420                &mut bindings_ctx,
4421                &FakeLinkDeviceId,
4422                I::LOOKUP_ADDR1,
4423                Buf::new([BODY], ..),
4424                FakeTxMetadata::default(),
4425            ),
4426            Ok(())
4427        );
4428        assert_eq!(
4429            core_ctx.inner.take_frames(),
4430            [
4431                (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4432                (
4433                    FakeNudMessageMeta::NeighborSolicitation {
4434                        lookup_addr: I::LOOKUP_ADDR1,
4435                        remote_link_addr: /* multicast */ None,
4436                    },
4437                    Vec::new()
4438                )
4439            ]
4440        );
4441
4442        let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4443            UnreachableMode::Backoff {
4444                probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4445                packet_sent: /* unused */ false,
4446            }
4447            .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4448            .get()
4449        };
4450
4451        const ITERATIONS: u8 = 2;
4452        for i in 1..ITERATIONS {
4453            let probes_sent = u32::from(i);
4454
4455            // Send another packet before the retransmit timer expires: only the packet
4456            // should be sent (not a probe), and the `packet_sent` flag should be set.
4457            assert_eq!(
4458                NudHandler::send_ip_packet_to_neighbor(
4459                    &mut core_ctx,
4460                    &mut bindings_ctx,
4461                    &FakeLinkDeviceId,
4462                    I::LOOKUP_ADDR1,
4463                    Buf::new([BODY + i], ..),
4464                    FakeTxMetadata::default(),
4465                ),
4466                Ok(())
4467            );
4468            assert_eq!(
4469                core_ctx.inner.take_frames(),
4470                [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4471            );
4472
4473            // Fast forward until the current retransmit timer should fire, taking
4474            // exponential backoff into account. Another multicast probe should be
4475            // transmitted and a new timer should be scheduled (backing off further) because
4476            // a packet was recently sent.
4477            assert_eq!(
4478                bindings_ctx.trigger_timers_for(
4479                    next_backoff_timer(&mut core_ctx, probes_sent),
4480                    &mut core_ctx,
4481                ),
4482                [timer_id]
4483            );
4484            assert_neighbor_probe_sent(&mut core_ctx, /* multicast */ None);
4485            bindings_ctx.timers.assert_timers_installed([(
4486                timer_id,
4487                bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4488            )]);
4489        }
4490
4491        // If no more packets are sent, no multicast probes should be transmitted even
4492        // after the next backoff timer expires.
4493        let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4494        assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4495        assert_eq!(core_ctx.inner.take_frames(), []);
4496        bindings_ctx.timers.assert_no_timers_installed();
4497
4498        // Finally, if another packet is sent, we resume transmitting multicast probes
4499        // and "reset" the exponential backoff.
4500        assert_eq!(
4501            NudHandler::send_ip_packet_to_neighbor(
4502                &mut core_ctx,
4503                &mut bindings_ctx,
4504                &FakeLinkDeviceId,
4505                I::LOOKUP_ADDR1,
4506                Buf::new([BODY], ..),
4507                FakeTxMetadata::default(),
4508            ),
4509            Ok(())
4510        );
4511        assert_eq!(
4512            core_ctx.inner.take_frames(),
4513            [
4514                (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4515                (
4516                    FakeNudMessageMeta::NeighborSolicitation {
4517                        lookup_addr: I::LOOKUP_ADDR1,
4518                        remote_link_addr: /* multicast */ None,
4519                    },
4520                    Vec::new()
4521                )
4522            ]
4523        );
4524        bindings_ctx.timers.assert_timers_installed([(
4525            timer_id,
4526            bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4527        )]);
4528    }
4529
4530    #[ip_test(I)]
4531    #[test_case(true; "solicited confirmation")]
4532    #[test_case(false; "unsolicited confirmation")]
4533    fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4534        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4535
4536        let link_address = Some(LINK_ADDR1);
4537        NudHandler::handle_neighbor_update(
4538            &mut core_ctx,
4539            &mut bindings_ctx,
4540            &FakeLinkDeviceId,
4541            I::LOOKUP_ADDR1,
4542            DynamicNeighborUpdateSource::Confirmation {
4543                link_address,
4544                flags: ConfirmationFlags { solicited_flag, override_flag: false },
4545            },
4546        );
4547        assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4548    }
4549
4550    #[ip_test(I)]
4551    #[test_case(true; "set_with_dynamic")]
4552    #[test_case(false; "set_with_static")]
4553    fn pending_frames<I: TestIpExt>(dynamic: bool) {
4554        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4555        assert_eq!(core_ctx.inner.take_frames(), []);
4556
4557        // Send up to the maximum number of pending frames to some neighbor
4558        // which requires resolution. This should cause all frames to be queued
4559        // pending resolution completion.
4560        const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4561        let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4562            .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4563            .collect::<VecDeque<_>>();
4564
4565        for (body, meta) in expected_pending_frames.iter() {
4566            assert_eq!(
4567                NudHandler::send_ip_packet_to_neighbor(
4568                    &mut core_ctx,
4569                    &mut bindings_ctx,
4570                    &FakeLinkDeviceId,
4571                    I::LOOKUP_ADDR1,
4572                    body.clone(),
4573                    meta.clone(),
4574                ),
4575                Ok(())
4576            );
4577        }
4578        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4579        // Should have only sent out a single neighbor probe message.
4580        assert_neighbor_probe_sent(&mut core_ctx, None);
4581        assert_neighbor_state(
4582            &core_ctx,
4583            &mut bindings_ctx,
4584            DynamicNeighborState::Incomplete(Incomplete {
4585                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4586                pending_frames: expected_pending_frames.clone(),
4587                notifiers: Vec::new(),
4588                _marker: PhantomData,
4589            }),
4590            Some(ExpectedEvent::Added),
4591        );
4592
4593        // The next frame should be dropped.
4594        assert_eq!(
4595            NudHandler::send_ip_packet_to_neighbor(
4596                &mut core_ctx,
4597                &mut bindings_ctx,
4598                &FakeLinkDeviceId,
4599                I::LOOKUP_ADDR1,
4600                Buf::new([123], ..),
4601                FakeTxMetadata::default(),
4602            ),
4603            Ok(())
4604        );
4605        assert_eq!(core_ctx.inner.take_frames(), []);
4606        assert_neighbor_state(
4607            &core_ctx,
4608            &mut bindings_ctx,
4609            DynamicNeighborState::Incomplete(Incomplete {
4610                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4611                pending_frames: expected_pending_frames.clone(),
4612                notifiers: Vec::new(),
4613                _marker: PhantomData,
4614            }),
4615            None,
4616        );
4617
4618        // Completing resolution should result in all queued packets being sent.
4619        if dynamic {
4620            NudHandler::handle_neighbor_update(
4621                &mut core_ctx,
4622                &mut bindings_ctx,
4623                &FakeLinkDeviceId,
4624                I::LOOKUP_ADDR1,
4625                DynamicNeighborUpdateSource::Confirmation {
4626                    link_address: Some(LINK_ADDR1),
4627                    flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4628                },
4629            );
4630            core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4631                &mut bindings_ctx,
4632                [(
4633                    I::LOOKUP_ADDR1,
4634                    NudEvent::ReachableTime,
4635                    core_ctx.inner.base_reachable_time().get(),
4636                )],
4637            );
4638            let last_confirmed_at = bindings_ctx.now();
4639            assert_neighbor_state(
4640                &core_ctx,
4641                &mut bindings_ctx,
4642                DynamicNeighborState::Reachable(Reachable {
4643                    link_address: LINK_ADDR1,
4644                    last_confirmed_at,
4645                }),
4646                Some(ExpectedEvent::Changed),
4647            );
4648        } else {
4649            init_static_neighbor(
4650                &mut core_ctx,
4651                &mut bindings_ctx,
4652                LINK_ADDR1,
4653                ExpectedEvent::Changed,
4654            );
4655            bindings_ctx.timers.assert_no_timers_installed();
4656        }
4657        assert_eq!(
4658            core_ctx.inner.take_frames(),
4659            expected_pending_frames
4660                .into_iter()
4661                .map(|(p, FakeTxMetadata)| (
4662                    FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4663                    p.as_ref().to_vec()
4664                ))
4665                .collect::<Vec<_>>()
4666        );
4667    }
4668
4669    #[ip_test(I)]
4670    fn static_neighbor<I: TestIpExt>() {
4671        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4672
4673        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4674        bindings_ctx.timers.assert_no_timers_installed();
4675        assert_eq!(core_ctx.inner.take_frames(), []);
4676        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4677
4678        // Dynamic entries should not overwrite static entries.
4679        NudHandler::handle_neighbor_update(
4680            &mut core_ctx,
4681            &mut bindings_ctx,
4682            &FakeLinkDeviceId,
4683            I::LOOKUP_ADDR1,
4684            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4685        );
4686        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4687
4688        delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4689
4690        let neighbors = &core_ctx.nud.state.neighbors;
4691        assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4692    }
4693
4694    #[ip_test(I)]
4695    fn dynamic_neighbor<I: TestIpExt>() {
4696        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4697
4698        init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4699        bindings_ctx.timers.assert_no_timers_installed();
4700        assert_eq!(core_ctx.inner.take_frames(), []);
4701        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4702
4703        // Dynamic entries may be overwritten by new dynamic entries.
4704        NudHandler::handle_neighbor_update(
4705            &mut core_ctx,
4706            &mut bindings_ctx,
4707            &FakeLinkDeviceId,
4708            I::LOOKUP_ADDR1,
4709            DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4710        );
4711        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4712        assert_eq!(core_ctx.inner.take_frames(), []);
4713        assert_neighbor_state(
4714            &core_ctx,
4715            &mut bindings_ctx,
4716            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4717            Some(ExpectedEvent::Changed),
4718        );
4719
4720        // A static entry may overwrite a dynamic entry.
4721        init_static_neighbor_with_ip(
4722            &mut core_ctx,
4723            &mut bindings_ctx,
4724            I::LOOKUP_ADDR1,
4725            LINK_ADDR3,
4726            ExpectedEvent::Changed,
4727        );
4728        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4729        assert_eq!(core_ctx.inner.take_frames(), []);
4730    }
4731
4732    #[ip_test(I)]
4733    fn send_solicitation_on_lookup<I: TestIpExt>() {
4734        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4735        bindings_ctx.timers.assert_no_timers_installed();
4736        assert_eq!(core_ctx.inner.take_frames(), []);
4737
4738        let mut pending_frames = VecDeque::new();
4739
4740        queue_ip_packet_to_unresolved_neighbor(
4741            &mut core_ctx,
4742            &mut bindings_ctx,
4743            I::LOOKUP_ADDR1,
4744            &mut pending_frames,
4745            1,
4746            true, /* expect_event */
4747        );
4748        assert_neighbor_probe_sent(&mut core_ctx, None);
4749
4750        queue_ip_packet_to_unresolved_neighbor(
4751            &mut core_ctx,
4752            &mut bindings_ctx,
4753            I::LOOKUP_ADDR1,
4754            &mut pending_frames,
4755            2,
4756            false, /* expect_event */
4757        );
4758        assert_eq!(core_ctx.inner.take_frames(), []);
4759
4760        // Complete link resolution.
4761        NudHandler::handle_neighbor_update(
4762            &mut core_ctx,
4763            &mut bindings_ctx,
4764            &FakeLinkDeviceId,
4765            I::LOOKUP_ADDR1,
4766            DynamicNeighborUpdateSource::Confirmation {
4767                link_address: Some(LINK_ADDR1),
4768                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4769            },
4770        );
4771        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4772
4773        let now = bindings_ctx.now();
4774        assert_neighbor_state(
4775            &core_ctx,
4776            &mut bindings_ctx,
4777            DynamicNeighborState::Reachable(Reachable {
4778                link_address: LINK_ADDR1,
4779                last_confirmed_at: now,
4780            }),
4781            Some(ExpectedEvent::Changed),
4782        );
4783        assert_eq!(
4784            core_ctx.inner.take_frames(),
4785            pending_frames
4786                .into_iter()
4787                .map(|f| (
4788                    FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4789                    f.as_ref().to_vec(),
4790                ))
4791                .collect::<Vec<_>>()
4792        );
4793    }
4794
4795    #[ip_test(I)]
4796    fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4797        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4798        bindings_ctx.timers.assert_no_timers_installed();
4799        assert_eq!(core_ctx.inner.take_frames(), []);
4800
4801        let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4802
4803        let timer_id = NudTimerId::neighbor();
4804
4805        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4806        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4807
4808        for i in 1..=max_multicast_solicit {
4809            assert_neighbor_state(
4810                &core_ctx,
4811                &mut bindings_ctx,
4812                DynamicNeighborState::Incomplete(Incomplete {
4813                    transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4814                    pending_frames: pending_frames
4815                        .iter()
4816                        .cloned()
4817                        .map(|b| (b, FakeTxMetadata::default()))
4818                        .collect(),
4819                    notifiers: Vec::new(),
4820                    _marker: PhantomData,
4821                }),
4822                None,
4823            );
4824
4825            bindings_ctx
4826                .timers
4827                .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4828            assert_neighbor_probe_sent(&mut core_ctx, /* multicast */ None);
4829
4830            assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4831        }
4832
4833        // The neighbor entry should have been removed.
4834        assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4835        bindings_ctx.timers.assert_no_timers_installed();
4836
4837        // The ICMP destination unreachable error sent as a result of solicitation failure
4838        // will be dropped because the packets pending address resolution in this test
4839        // is not a valid IP packet.
4840        assert_eq!(core_ctx.inner.take_frames(), []);
4841        assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4842    }
4843
4844    #[ip_test(I)]
4845    fn solicitation_failure_in_probe<I: TestIpExt>() {
4846        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4847        bindings_ctx.timers.assert_no_timers_installed();
4848        assert_eq!(core_ctx.inner.take_frames(), []);
4849
4850        init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4851
4852        let timer_id = NudTimerId::neighbor();
4853        let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4854        let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4855        for i in 1..=max_unicast_solicit {
4856            assert_neighbor_state(
4857                &core_ctx,
4858                &mut bindings_ctx,
4859                DynamicNeighborState::Probe(Probe {
4860                    transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4861                    link_address: LINK_ADDR1,
4862                }),
4863                None,
4864            );
4865
4866            bindings_ctx
4867                .timers
4868                .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4869            assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4870
4871            assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4872        }
4873
4874        assert_neighbor_state(
4875            &core_ctx,
4876            &mut bindings_ctx,
4877            DynamicNeighborState::Unreachable(Unreachable {
4878                link_address: LINK_ADDR1,
4879                mode: UnreachableMode::WaitingForPacketSend,
4880            }),
4881            Some(ExpectedEvent::Changed),
4882        );
4883        bindings_ctx.timers.assert_no_timers_installed();
4884        assert_eq!(core_ctx.inner.take_frames(), []);
4885    }
4886
4887    #[ip_test(I)]
4888    fn flush_entries<I: TestIpExt>() {
4889        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4890        bindings_ctx.timers.assert_no_timers_installed();
4891        assert_eq!(core_ctx.inner.take_frames(), []);
4892
4893        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4894        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4895        let pending_frames = init_incomplete_neighbor_with_ip(
4896            &mut core_ctx,
4897            &mut bindings_ctx,
4898            I::LOOKUP_ADDR3,
4899            true,
4900        );
4901        let pending_frames =
4902            pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4903
4904        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4905        assert_eq!(
4906            core_ctx.nud.state.neighbors,
4907            HashMap::from([
4908                (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4909                (
4910                    I::LOOKUP_ADDR2,
4911                    NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4912                        link_address: LINK_ADDR2,
4913                    })),
4914                ),
4915                (
4916                    I::LOOKUP_ADDR3,
4917                    NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4918                        transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4919                        pending_frames,
4920                        notifiers: Vec::new(),
4921                        _marker: PhantomData,
4922                    })),
4923                ),
4924            ]),
4925        );
4926        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4927            &mut bindings_ctx,
4928            [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4929        );
4930
4931        // Flushing the table should clear all entries (dynamic and static) and timers.
4932        NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4933        let neighbors = &core_ctx.nud.state.neighbors;
4934        assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4935        assert_eq!(
4936            bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4937            [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4938                .into_iter()
4939                .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4940                .collect(),
4941        );
4942        bindings_ctx.timers.assert_no_timers_installed();
4943    }
4944
4945    #[ip_test(I)]
4946    fn delete_dynamic_entry<I: TestIpExt>() {
4947        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4948        bindings_ctx.timers.assert_no_timers_installed();
4949        assert_eq!(core_ctx.inner.take_frames(), []);
4950
4951        init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4952        check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4953
4954        delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4955
4956        // Entry should be removed and timer cancelled.
4957        let neighbors = &core_ctx.nud.state.neighbors;
4958        assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4959        bindings_ctx.timers.assert_no_timers_installed();
4960    }
4961
4962    #[ip_test(I)]
4963    #[test_case(InitialState::Reachable; "reachable neighbor")]
4964    #[test_case(InitialState::Stale; "stale neighbor")]
4965    #[test_case(InitialState::Delay; "delay neighbor")]
4966    #[test_case(InitialState::Probe; "probe neighbor")]
4967    #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4968    fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4969        let mut ctx = new_context::<I>();
4970        ctx.bindings_ctx.timers.assert_no_timers_installed();
4971        assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4972
4973        let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4974
4975        let link_addr = assert_matches!(
4976            NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4977                &FakeLinkDeviceId,
4978                &I::LOOKUP_ADDR1,
4979            ),
4980            LinkResolutionResult::Resolved(addr) => addr
4981        );
4982        assert_eq!(link_addr, LINK_ADDR1);
4983        if initial_state == InitialState::Stale {
4984            assert_eq!(
4985                ctx.bindings_ctx.take_events(),
4986                [Event::changed(
4987                    &FakeLinkDeviceId,
4988                    EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4989                    I::LOOKUP_ADDR1,
4990                    ctx.bindings_ctx.now(),
4991                )],
4992            );
4993        }
4994    }
4995
4996    enum ResolutionSuccess {
4997        Confirmation,
4998        StaticEntryAdded,
4999    }
5000
5001    #[ip_test(I)]
5002    #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
5003    #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
5004    fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
5005        let mut ctx = new_context::<I>();
5006
5007        let observers = (0..10)
5008            .map(|_| {
5009                let observer = assert_matches!(
5010                    NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5011                        &FakeLinkDeviceId,
5012                        &I::LOOKUP_ADDR1,
5013                    ),
5014                    LinkResolutionResult::Pending(observer) => observer
5015                );
5016                assert_eq!(*observer.lock(), None);
5017                observer
5018            })
5019            .collect::<Vec<_>>();
5020        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5021        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5022
5023        // We should have initialized an incomplete neighbor and sent a neighbor probe
5024        // to attempt resolution.
5025        assert_neighbor_state(
5026            core_ctx,
5027            bindings_ctx,
5028            DynamicNeighborState::Incomplete(Incomplete {
5029                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5030                pending_frames: VecDeque::new(),
5031                // NB: notifiers is not checked for equality.
5032                notifiers: Vec::new(),
5033                _marker: PhantomData,
5034            }),
5035            Some(ExpectedEvent::Added),
5036        );
5037        assert_neighbor_probe_sent(core_ctx, /* multicast */ None);
5038
5039        match reason {
5040            ResolutionSuccess::Confirmation => {
5041                // Complete neighbor resolution with an incoming neighbor confirmation.
5042                NudHandler::handle_neighbor_update(
5043                    core_ctx,
5044                    bindings_ctx,
5045                    &FakeLinkDeviceId,
5046                    I::LOOKUP_ADDR1,
5047                    DynamicNeighborUpdateSource::Confirmation {
5048                        link_address: Some(LINK_ADDR1),
5049                        flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5050                    },
5051                );
5052                let now = bindings_ctx.now();
5053                assert_neighbor_state(
5054                    core_ctx,
5055                    bindings_ctx,
5056                    DynamicNeighborState::Reachable(Reachable {
5057                        link_address: LINK_ADDR1,
5058                        last_confirmed_at: now,
5059                    }),
5060                    Some(ExpectedEvent::Changed),
5061                );
5062            }
5063            ResolutionSuccess::StaticEntryAdded => {
5064                init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
5065                assert_eq!(
5066                    core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
5067                    Some(&NeighborState::Static(LINK_ADDR1))
5068                );
5069            }
5070        }
5071
5072        // Each observer should have been notified of successful link resolution.
5073        for observer in observers {
5074            assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
5075        }
5076    }
5077
5078    enum ResolutionFailure {
5079        Timeout,
5080        Removed,
5081    }
5082
5083    #[ip_test(I)]
5084    #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
5085    #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
5086    fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
5087        let mut ctx = new_context::<I>();
5088
5089        let observers = (0..10)
5090            .map(|_| {
5091                let observer = assert_matches!(
5092                    NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5093                        &FakeLinkDeviceId,
5094                        &I::LOOKUP_ADDR1,
5095                    ),
5096                    LinkResolutionResult::Pending(observer) => observer
5097                );
5098                assert_eq!(*observer.lock(), None);
5099                observer
5100            })
5101            .collect::<Vec<_>>();
5102
5103        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5104        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5105
5106        // We should have initialized an incomplete neighbor and sent a neighbor probe
5107        // to attempt resolution.
5108        assert_neighbor_state(
5109            core_ctx,
5110            bindings_ctx,
5111            DynamicNeighborState::Incomplete(Incomplete {
5112                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5113                pending_frames: VecDeque::new(),
5114                // NB: notifiers is not checked for equality.
5115                notifiers: Vec::new(),
5116                _marker: PhantomData,
5117            }),
5118            Some(ExpectedEvent::Added),
5119        );
5120        assert_neighbor_probe_sent(core_ctx, /* multicast */ None);
5121
5122        match reason {
5123            ResolutionFailure::Timeout => {
5124                // Wait until neighbor resolution exceeds its maximum probe retransmits and
5125                // times out.
5126                for _ in 1..=max_multicast_solicit {
5127                    let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5128                    assert_eq!(
5129                        bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5130                        [NudTimerId::neighbor()]
5131                    );
5132                }
5133            }
5134            ResolutionFailure::Removed => {
5135                // Flush the neighbor table so the entry is removed.
5136                NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5137            }
5138        }
5139
5140        assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5141        // Each observer should have been notified of link resolution failure.
5142        for observer in observers {
5143            assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5144        }
5145    }
5146
5147    #[ip_test(I)]
5148    #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5149    #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5150    #[test_case(InitialState::Stale, true; "stale neighbor")]
5151    #[test_case(InitialState::Delay, true; "delay neighbor")]
5152    #[test_case(InitialState::Probe, true; "probe neighbor")]
5153    #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5154    fn upper_layer_confirmation<I: TestIpExt>(
5155        initial_state: InitialState,
5156        should_transition_to_reachable: bool,
5157    ) {
5158        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5159        let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5160
5161        let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5162
5163        confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5164
5165        if !should_transition_to_reachable {
5166            assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5167            return;
5168        }
5169
5170        // Neighbor should have transitioned to REACHABLE and scheduled a timer.
5171        let now = bindings_ctx.now();
5172        assert_neighbor_state(
5173            &core_ctx,
5174            &mut bindings_ctx,
5175            DynamicNeighborState::Reachable(Reachable {
5176                link_address: LINK_ADDR1,
5177                last_confirmed_at: now,
5178            }),
5179            (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5180        );
5181        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5182            &mut bindings_ctx,
5183            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5184        );
5185
5186        // Advance the clock by less than ReachableTime and confirm reachability again.
5187        // The existing timer should not have been rescheduled; only the entry's
5188        // `last_confirmed_at` timestamp should have been updated.
5189        bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5190        confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5191        let now = bindings_ctx.now();
5192        assert_neighbor_state(
5193            &core_ctx,
5194            &mut bindings_ctx,
5195            DynamicNeighborState::Reachable(Reachable {
5196                link_address: LINK_ADDR1,
5197                last_confirmed_at: now,
5198            }),
5199            None,
5200        );
5201        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5202            &mut bindings_ctx,
5203            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5204        );
5205
5206        // When the original timer eventually does expire, a new timer should be
5207        // scheduled based on when the entry was last confirmed.
5208        assert_eq!(
5209            bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5210            [NudTimerId::neighbor()]
5211        );
5212        let now = bindings_ctx.now();
5213        assert_neighbor_state(
5214            &core_ctx,
5215            &mut bindings_ctx,
5216            DynamicNeighborState::Reachable(Reachable {
5217                link_address: LINK_ADDR1,
5218                last_confirmed_at: now - base_reachable_time / 2,
5219            }),
5220            None,
5221        );
5222
5223        core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5224            &mut bindings_ctx,
5225            [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5226        );
5227
5228        // When *that* timer fires, if the entry has not been confirmed since it was
5229        // scheduled, it should move into STALE.
5230        assert_eq!(
5231            bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5232            [NudTimerId::neighbor()]
5233        );
5234        assert_neighbor_state(
5235            &core_ctx,
5236            &mut bindings_ctx,
5237            DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5238            Some(ExpectedEvent::Changed),
5239        );
5240        bindings_ctx.timers.assert_no_timers_installed();
5241    }
5242
5243    fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5244        I::map_ip_out(
5245            i,
5246            |i| {
5247                let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5248                let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5249                SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5250            },
5251            |i| {
5252                let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5253                let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5254                SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5255            },
5256        )
5257    }
5258
5259    #[ip_test(I)]
5260    fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5261        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5262
5263        // Add `GC_THRESHOLD` STALE dynamic neighbors and `GC_THRESHOLD` static
5264        // neighbors to the neighbor table, interleaved to avoid accidental
5265        // behavior re: insertion order.
5266        for i in 0..GC_THRESHOLD * 2 {
5267            if i % 2 == 0 {
5268                init_stale_neighbor_with_ip(
5269                    &mut core_ctx,
5270                    &mut bindings_ctx,
5271                    generate_ip_addr::<I>(i),
5272                    LINK_ADDR1,
5273                );
5274            } else {
5275                init_static_neighbor_with_ip(
5276                    &mut core_ctx,
5277                    &mut bindings_ctx,
5278                    generate_ip_addr::<I>(i),
5279                    LINK_ADDR1,
5280                    ExpectedEvent::Added,
5281                );
5282            }
5283        }
5284        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD * 2);
5285
5286        // Perform GC, and ensure that only the dynamic entries are discarded.
5287        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5288        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5289        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5290        for event in bindings_ctx.take_events() {
5291            assert_matches!(event, Event {
5292                device,
5293                addr: _,
5294                kind,
5295                at,
5296            } => {
5297                assert_eq!(kind, EventKind::Removed);
5298                assert_eq!(device, FakeLinkDeviceId);
5299                assert_eq!(at, bindings_ctx.now());
5300            });
5301        }
5302        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5303        for (_, neighbor) in core_ctx.nud.state.neighbors {
5304            assert_matches!(neighbor, NeighborState::Static(_));
5305        }
5306    }
5307
5308    #[ip_test(I)]
5309    fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5310        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5311
5312        // Add enough static entries that the NUD table is near maximum capacity.
5313        for i in 0..GC_THRESHOLD - 1 {
5314            init_static_neighbor_with_ip(
5315                &mut core_ctx,
5316                &mut bindings_ctx,
5317                generate_ip_addr::<I>(i),
5318                LINK_ADDR1,
5319                ExpectedEvent::Added,
5320            );
5321        }
5322
5323        // Add a STALE entry...
5324        let stale_entry = generate_ip_addr::<I>(GC_THRESHOLD - 1);
5325        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5326        // ...and a REACHABLE entry.
5327        let reachable_entry = generate_ip_addr::<I>(GC_THRESHOLD);
5328        init_reachable_neighbor_with_ip(
5329            &mut core_ctx,
5330            &mut bindings_ctx,
5331            reachable_entry,
5332            LINK_ADDR1,
5333        );
5334
5335        // Perform GC, and ensure that the REACHABLE entry was retained.
5336        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5337        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5338        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5339        super::testutil::assert_dynamic_neighbor_state(
5340            &mut core_ctx,
5341            FakeLinkDeviceId,
5342            reachable_entry,
5343            DynamicNeighborState::Reachable(Reachable {
5344                link_address: LINK_ADDR1,
5345                last_confirmed_at: bindings_ctx.now(),
5346            }),
5347        );
5348        assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5349    }
5350
5351    #[ip_test(I)]
5352    fn is_still_dirty_after_garbage_collection<I: TestIpExt>() {
5353        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5354
5355        // Add enough STALE entries to trigger garbage collection.
5356        for i in 0..GC_THRESHOLD + 1 {
5357            init_stale_neighbor_with_ip(
5358                &mut core_ctx,
5359                &mut bindings_ctx,
5360                generate_ip_addr::<I>(i),
5361                LINK_ADDR1,
5362            );
5363        }
5364
5365        // Perform GC, and ensure that the `is_dirty` is still set (because not
5366        // all STALE entries were removed).
5367        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5368        collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5369        assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5370        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5371        let events = bindings_ctx.take_events();
5372        let removed_event = assert_matches!(&events[..], [event] => event);
5373        assert_eq!(removed_event.kind, EventKind::Removed);
5374    }
5375
5376    #[ip_test(I)]
5377    fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5378        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5379        // Pretend we just ran GC so the next pass will be scheduled after a delay.
5380        core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5381
5382        // Fill the neighbor table to maximum capacity with static entries.
5383        for i in 0..GC_THRESHOLD {
5384            init_static_neighbor_with_ip(
5385                &mut core_ctx,
5386                &mut bindings_ctx,
5387                generate_ip_addr::<I>(i),
5388                LINK_ADDR1,
5389                ExpectedEvent::Added,
5390            );
5391        }
5392
5393        // Add a STALE neighbor entry to the table, which should trigger a GC run
5394        // because it pushes the size of the table over the max.
5395        init_stale_neighbor_with_ip(
5396            &mut core_ctx,
5397            &mut bindings_ctx,
5398            generate_ip_addr::<I>(GC_THRESHOLD + 1),
5399            LINK_ADDR1,
5400        );
5401        let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5402        bindings_ctx
5403            .timers
5404            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5405
5406        // Advance the clock by less than the GC interval and add another STALE entry to
5407        // trigger GC again. The existing GC timer should not have been rescheduled
5408        // given a GC pass is already pending.
5409        bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5410        init_stale_neighbor_with_ip(
5411            &mut core_ctx,
5412            &mut bindings_ctx,
5413            generate_ip_addr::<I>(GC_THRESHOLD + 2),
5414            LINK_ADDR1,
5415        );
5416        bindings_ctx
5417            .timers
5418            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5419    }
5420
5421    #[ip_test(I)]
5422    fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5423        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5424        // Pretend we just ran GC so the next pass will be scheduled after a delay.
5425        core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5426
5427        // Fill the neighbor table to maximum capacity.
5428        for i in 0..GC_THRESHOLD {
5429            init_static_neighbor_with_ip(
5430                &mut core_ctx,
5431                &mut bindings_ctx,
5432                generate_ip_addr::<I>(i),
5433                LINK_ADDR1,
5434                ExpectedEvent::Added,
5435            );
5436        }
5437        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5438
5439        // Add a dynamic neighbor entry to the table and transition it to the
5440        // UNREACHABLE state. This should trigger a GC run.
5441        init_unreachable_neighbor_with_ip(
5442            &mut core_ctx,
5443            &mut bindings_ctx,
5444            generate_ip_addr::<I>(GC_THRESHOLD),
5445            LINK_ADDR1,
5446        );
5447        let expected_gc_time =
5448            core_ctx.nud.state.gc_state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5449        bindings_ctx
5450            .timers
5451            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5452
5453        // Add a new entry and transition it to UNREACHABLE. The existing GC timer
5454        // should not have been rescheduled given a GC pass is already pending.
5455        init_unreachable_neighbor_with_ip(
5456            &mut core_ctx,
5457            &mut bindings_ctx,
5458            generate_ip_addr::<I>(GC_THRESHOLD + 1),
5459            LINK_ADDR1,
5460        );
5461        bindings_ctx
5462            .timers
5463            .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5464    }
5465
5466    #[ip_test(I)]
5467    fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5468        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5469
5470        // Fill the neighbor table to maximum capacity with static entries.
5471        for i in 0..GC_THRESHOLD {
5472            init_static_neighbor_with_ip(
5473                &mut core_ctx,
5474                &mut bindings_ctx,
5475                generate_ip_addr::<I>(i),
5476                LINK_ADDR1,
5477                ExpectedEvent::Added,
5478            );
5479        }
5480        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5481
5482        let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5483            &mut core_ctx,
5484            &mut bindings_ctx,
5485            generate_ip_addr::<I>(GC_THRESHOLD),
5486            true,
5487        );
5488        assert_eq!(
5489            bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5490            None
5491        );
5492    }
5493
5494    #[ip_test(I)]
5495    fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5496        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5497
5498        // Initialize a neighbor in STALE.
5499        init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5500
5501        // Receive a neighbor confirmation that omits the target link-layer address
5502        // option. Because we have a cached link-layer address, we should still process
5503        // the confirmation (updating the neighbor to REACHABLE).
5504        NudHandler::handle_neighbor_update(
5505            &mut core_ctx,
5506            &mut bindings_ctx,
5507            &FakeLinkDeviceId,
5508            I::LOOKUP_ADDR1,
5509            DynamicNeighborUpdateSource::Confirmation {
5510                link_address: None,
5511                flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5512            },
5513        );
5514        let now = bindings_ctx.now();
5515        assert_neighbor_state(
5516            &core_ctx,
5517            &mut bindings_ctx,
5518            DynamicNeighborState::Reachable(Reachable {
5519                link_address: LINK_ADDR1,
5520                last_confirmed_at: now,
5521            }),
5522            Some(ExpectedEvent::Changed),
5523        );
5524    }
5525
5526    #[ip_test(I)]
5527    #[test_case(InitialState::Stale; "stale")]
5528    #[test_case(InitialState::Reachable; "reachable")]
5529    #[test_case(InitialState::Delay; "delay")]
5530    #[test_case(InitialState::Unreachable; "unreachable")]
5531    fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5532        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5533
5534        let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5535
5536        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5537        let result = neighbor.enter_probe(
5538            &mut core_ctx.inner.state,
5539            &mut bindings_ctx,
5540            &mut core_ctx.nud.state.timer_heap,
5541            I::LOOKUP_ADDR1,
5542            &FakeLinkDeviceId,
5543        );
5544
5545        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5546        assert_matches!(result, Ok(Some(LINK_ADDR1)));
5547        assert_neighbor_state(
5548            &core_ctx,
5549            &mut bindings_ctx,
5550            DynamicNeighborState::Probe(Probe {
5551                link_address: LINK_ADDR1,
5552                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5553            }),
5554            Some(ExpectedEvent::Changed),
5555        );
5556    }
5557
5558    #[ip_test(I)]
5559    fn enter_probe_from_static_state<I: TestIpExt>() {
5560        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5561
5562        init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5563
5564        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5565        let result = neighbor.enter_probe(
5566            &mut core_ctx.inner.state,
5567            &mut bindings_ctx,
5568            &mut core_ctx.nud.state.timer_heap,
5569            I::LOOKUP_ADDR1,
5570            &FakeLinkDeviceId,
5571        );
5572
5573        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5574        assert_matches!(result, Ok(Some(LINK_ADDR1)));
5575        assert_neighbor_state(
5576            &core_ctx,
5577            &mut bindings_ctx,
5578            DynamicNeighborState::Probe(Probe {
5579                link_address: LINK_ADDR1,
5580                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5581            }),
5582            Some(ExpectedEvent::Changed),
5583        );
5584    }
5585
5586    #[ip_test(I)]
5587    fn enter_probe_from_probe_state<I: TestIpExt>() {
5588        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5589
5590        init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5591
5592        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5593        let result = neighbor.enter_probe(
5594            &mut core_ctx.inner.state,
5595            &mut bindings_ctx,
5596            &mut core_ctx.nud.state.timer_heap,
5597            I::LOOKUP_ADDR1,
5598            &FakeLinkDeviceId,
5599        );
5600
5601        let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5602        assert_matches!(result, Ok(None)); // No new probe should be transmitted.
5603        assert_neighbor_state(
5604            &core_ctx,
5605            &mut bindings_ctx,
5606            DynamicNeighborState::Probe(Probe {
5607                link_address: LINK_ADDR1,
5608                transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5609            }),
5610            None, // No event should be generated.
5611        );
5612    }
5613
5614    #[ip_test(I)]
5615    fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5616        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5617
5618        let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5619
5620        let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5621        let result = neighbor.enter_probe(
5622            &mut core_ctx.inner.state,
5623            &mut bindings_ctx,
5624            &mut core_ctx.nud.state.timer_heap,
5625            I::LOOKUP_ADDR1,
5626            &FakeLinkDeviceId,
5627        );
5628
5629        let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5630        assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5631        assert_neighbor_state(
5632            &core_ctx,
5633            &mut bindings_ctx,
5634            DynamicNeighborState::Incomplete(Incomplete {
5635                transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5636                pending_frames: pending_frames
5637                    .iter()
5638                    .cloned()
5639                    .map(|b| (b, FakeTxMetadata::default()))
5640                    .collect(),
5641                notifiers: Vec::new(),
5642                _marker: PhantomData,
5643            }),
5644            None, // No event should be generated.
5645        );
5646    }
5647
5648    /// Various ways a neighbor entry could be inserted to exercise table full
5649    /// conditions.
5650    enum InsertMethod {
5651        ResolveLinkAddr,
5652        InsertStaticEntry,
5653        NeighborUpdate,
5654        SendIpPacket,
5655    }
5656
5657    impl InsertMethod {
5658        fn insert<I: TestIpExt>(
5659            &self,
5660            context: &mut CtxPair<&mut FakeCoreCtxImpl<I>, &mut FakeBindingsCtxImpl<I>>,
5661            link_address: UnicastAddr<FakeLinkAddress>,
5662            ip: SpecifiedAddr<I::Addr>,
5663            expect_err: bool,
5664        ) {
5665            match self {
5666                Self::ResolveLinkAddr => {
5667                    let result =
5668                        NeighborApi::new(context).resolve_link_addr(&FakeLinkDeviceId, &ip);
5669                    let pending = assert_matches!(
5670                        result, LinkResolutionResult::Pending(pending) => pending
5671                    );
5672                    if expect_err {
5673                        assert_matches!(
5674                            pending.lock().as_ref(),
5675                            Some(Err(AddressResolutionFailed))
5676                        );
5677                    } else {
5678                        assert_matches!(pending.lock().as_ref(), None, "should not be notified");
5679                    }
5680                }
5681                Self::InsertStaticEntry => {
5682                    let result = NeighborApi::new(context).insert_static_entry(
5683                        &FakeLinkDeviceId,
5684                        *ip,
5685                        link_address,
5686                    );
5687                    if expect_err {
5688                        assert_eq!(result, Err(StaticNeighborInsertionError::TableFull))
5689                    } else {
5690                        assert_eq!(result, Ok(()))
5691                    }
5692                }
5693                Self::NeighborUpdate => {
5694                    let CtxPair { core_ctx, bindings_ctx } = context;
5695                    NudHandler::handle_neighbor_update(
5696                        *core_ctx,
5697                        *bindings_ctx,
5698                        &FakeLinkDeviceId,
5699                        ip,
5700                        DynamicNeighborUpdateSource::Probe { link_address },
5701                    );
5702                    // NB: ignore `expect_err` because errors aren't observable
5703                    // on `handle_neighbor_update`.
5704                }
5705                Self::SendIpPacket => {
5706                    let CtxPair { core_ctx, bindings_ctx } = context;
5707                    let packet = Buf::new([0; 10], ..);
5708                    let result = NudHandler::send_ip_packet_to_neighbor(
5709                        *core_ctx,
5710                        *bindings_ctx,
5711                        &FakeLinkDeviceId,
5712                        ip,
5713                        packet,
5714                        FakeTxMetadata::default(),
5715                    );
5716                    if expect_err {
5717                        assert_matches!(
5718                            result,
5719                            Err(ErrorAndSerializer {error, ..})
5720                            if error == SendFrameErrorReason::AddressResolutionFailed
5721                        );
5722                    } else {
5723                        assert_matches!(result, Ok(()));
5724                    }
5725                }
5726            }
5727        }
5728    }
5729
5730    // Verify that a full neighbor table does not allow adding new neighbors.
5731    #[ip_test(I)]
5732    #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5733    #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5734    #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5735    #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5736    fn neighbor_table_max_entries_enforced<I: TestIpExt>(insert_method: InsertMethod) {
5737        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5738        for i in 0..MAX_ENTRIES {
5739            // NB: Static entries will not be discardable.
5740            init_static_neighbor_with_ip(
5741                &mut core_ctx,
5742                &mut bindings_ctx,
5743                generate_ip_addr::<I>(i),
5744                LINK_ADDR1,
5745                ExpectedEvent::Added,
5746            );
5747            assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5748        }
5749
5750        // Attempting to insert an entry beyond the limit should fail.
5751        insert_method.insert(
5752            &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5753            LINK_ADDR1,
5754            generate_ip_addr::<I>(MAX_ENTRIES),
5755            true, /* expect_err */
5756        );
5757        assert_eq!(bindings_ctx.take_events(), []);
5758        assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5759    }
5760
5761    // Verify that a full neighbor table will run the garbage collector to free
5762    // discardable entries.
5763    #[ip_test(I)]
5764    #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5765    #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5766    #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5767    #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5768    fn insert_new_entry_may_collect_garbage<I: TestIpExt>(insert_method: InsertMethod) {
5769        let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5770
5771        for i in 0..MAX_ENTRIES {
5772            // NB: STALE entries will be discardable.
5773            init_stale_neighbor_with_ip(
5774                &mut core_ctx,
5775                &mut bindings_ctx,
5776                generate_ip_addr::<I>(i),
5777                LINK_ADDR1,
5778            );
5779            assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5780        }
5781
5782        // Attempting to insert an entry beyond the limit should trigger
5783        // garbage collection.
5784        insert_method.insert(
5785            &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5786            LINK_ADDR1,
5787            generate_ip_addr::<I>(MAX_ENTRIES),
5788            false, /* expect_err */
5789        );
5790        assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD + 1);
5791        let mut events = bindings_ctx.take_events();
5792        // Expect 1 `Added` event and several `Removed` events.
5793        let add_event = events.pop().expect("should have added event");
5794        assert_eq!(add_event.addr, generate_ip_addr::<I>(MAX_ENTRIES));
5795        assert_matches!(add_event.kind, EventKind::Added(_));
5796        for _ in 0..(MAX_ENTRIES - GC_THRESHOLD) {
5797            assert_matches!(events.pop(), Some(Event{kind, ..}) if kind == EventKind::Removed);
5798        }
5799        assert_matches!(&events[..], []);
5800    }
5801}