Skip to main content

netstack3_ip/device/
nud.rs

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