Skip to main content

netstack3_ip/device/
nud.rs

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