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    NetworkSerializationContext, NetworkSerializer, SendFrameError, StrongDeviceIdentifier,
27    TimerBindingsTypes, TimerContext, TxMetadataBindingsTypes, WeakDeviceIdentifier,
28};
29use netstack3_hashmap::hash_map::{self, Entry, HashMap};
30use packet::{
31    Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
32};
33use packet_formats::ip::IpPacket as _;
34use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
35use packet_formats::ipv6::Ipv6Packet;
36use packet_formats::utils::NonZeroDuration;
37use thiserror::Error;
38use zerocopy::SplitByteSlice;
39
40pub(crate) mod api;
41
42/// The default maximum number of multicast solicitations as defined in [RFC
43/// 4861 section 10].
44///
45/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
46pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
47
48/// The default maximum number of unicast solicitations as defined in [RFC 4861
49/// section 10].
50///
51/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
52const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
53
54/// The maximum amount of time between retransmissions of neighbor probe
55/// messages as defined in [RFC 7048 section 4].
56///
57/// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
58const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
59
60/// The default value for *RetransTimer* as defined in [RFC 4861 section 10].
61///
62/// Note that both ARP (IPv4) and NDP (IPv6) uses this default value to align
63/// behavior between IPv4 and IPv6 and simplify testing.
64///
65/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
66pub const DEFAULT_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
67
68/// The exponential backoff factor for retransmissions of multicast neighbor
69/// probe messages as defined in [RFC 7048 section 4].
70///
71/// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
72const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
73
74const MAX_PENDING_FRAMES: usize = 10;
75
76/// The time a neighbor is considered reachable after receiving a reachability
77/// confirmation, as defined in [RFC 4861 section 6.3.2].
78///
79/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
80const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
81
82/// The time after which a neighbor in the DELAY state transitions to PROBE, as
83/// defined in [RFC 4861 section 10].
84///
85/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
86const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
87
88/// The maximum number of neighbor entries in the neighbor table for a given
89/// device. When the number of entries is above this number and an entry
90/// transitions into a discardable state, a garbage collection task will be
91/// scheduled to remove any entries that are not in use.
92pub const MAX_ENTRIES: usize = 512;
93
94/// The minimum amount of time between garbage collection passes when the
95/// neighbor table grows beyond `MAX_SIZE`.
96const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
97
98/// NUD counters.
99#[derive(Default)]
100pub struct NudCountersInner {
101    /// Count of ICMP destination unreachable errors that could not be sent.
102    pub icmp_dest_unreachable_dropped: Counter,
103}
104
105/// NUD counters.
106pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
107
108/// Neighbor confirmation flags.
109#[derive(Debug, Copy, Clone)]
110pub struct ConfirmationFlags {
111    /// True if neighbor was explicitly solicited.
112    pub solicited_flag: bool,
113    /// True if must override neighbor entry.
114    pub override_flag: bool,
115}
116
117/// The type of message with a dynamic neighbor update.
118#[derive(Debug, Copy, Clone)]
119pub enum DynamicNeighborUpdateSource<A> {
120    /// Indicates an update from a neighbor probe message.
121    ///
122    /// E.g. NDP Neighbor Solicitation.
123    Probe {
124        /// The source link-layer address option.
125        // TODO(https://fxbug.dev/42083958): Wrap in `UnicastAddr`.
126        link_address: A,
127    },
128
129    /// Indicates an update from a neighbor confirmation message.
130    ///
131    /// E.g. NDP Neighbor Advertisement.
132    Confirmation {
133        /// The target link-layer address option.
134        // TODO(https://fxbug.dev/42083958): Wrap in `UnicastAddr`.
135        link_address: Option<A>,
136        /// The flags set on the neighbor confirmation.
137        flags: ConfirmationFlags,
138    },
139}
140
141/// A neighbor's state.
142#[derive(Derivative)]
143#[derivative(Debug(bound = ""))]
144#[cfg_attr(
145    any(test, feature = "testutils"),
146    derivative(
147        Clone(bound = "BT::TxMetadata: Clone"),
148        PartialEq(bound = "BT::TxMetadata: PartialEq"),
149        Eq(bound = "BT::TxMetadata: Eq")
150    )
151)]
152#[allow(missing_docs)]
153pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
154    Dynamic(DynamicNeighborState<D, BT>),
155    Static(D::Address),
156}
157
158/// The state of a dynamic entry in the neighbor cache within the Neighbor
159/// Unreachability Detection state machine, defined in [RFC 4861 section 7.3.2]
160/// and [RFC 7048 section 3].
161///
162/// [RFC 4861 section 7.3.2]: https://tools.ietf.org/html/rfc4861#section-7.3.2
163/// [RFC 7048 section 3]: https://tools.ietf.org/html/rfc7048#section-3
164#[derive(Derivative)]
165#[derivative(Debug(bound = ""))]
166#[cfg_attr(
167    any(test, feature = "testutils"),
168    derivative(
169        Clone(bound = "BT::TxMetadata: Clone"),
170        PartialEq(bound = "BT::TxMetadata: PartialEq"),
171        Eq(bound = "BT::TxMetadata: Eq")
172    )
173)]
174pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
175    /// Address resolution is being performed on the entry.
176    ///
177    /// Specifically, a probe has been sent to the solicited-node multicast
178    /// address of the target, but the corresponding confirmation has not yet
179    /// been received.
180    Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
181
182    /// Positive confirmation was received within the last ReachableTime
183    /// milliseconds that the forward path to the neighbor was functioning
184    /// properly. While `Reachable`, no special action takes place as packets
185    /// are sent.
186    Reachable(Reachable<D, BT::Instant>),
187
188    /// More than ReachableTime milliseconds have elapsed since the last
189    /// positive confirmation was received that the forward path was functioning
190    /// properly. While stale, no action takes place until a packet is sent.
191    ///
192    /// The `Stale` state is entered upon receiving an unsolicited neighbor
193    /// message that updates the cached link-layer address. Receipt of such a
194    /// message does not confirm reachability, and entering the `Stale` state
195    /// ensures reachability is verified quickly if the entry is actually being
196    /// used. However, reachability is not actually verified until the entry is
197    /// actually used.
198    Stale(Stale<D>),
199
200    /// A packet has been recently sent to the neighbor, which has stale
201    /// reachability information (i.e. we have not received recent positive
202    /// confirmation that the forward path is functioning properly).
203    ///
204    /// The `Delay` state is an optimization that gives upper-layer protocols
205    /// additional time to provide reachability confirmation in those cases
206    /// where ReachableTime milliseconds have passed since the last confirmation
207    /// due to lack of recent traffic. Without this optimization, the opening of
208    /// a TCP connection after a traffic lull would initiate probes even though
209    /// the subsequent three-way handshake would provide a reachability
210    /// confirmation almost immediately.
211    Delay(Delay<D>),
212
213    /// A reachability confirmation is actively sought by retransmitting probes
214    /// every RetransTimer milliseconds until a reachability confirmation is
215    /// received.
216    Probe(Probe<D>),
217
218    /// Similarly to the `Probe` state, a reachability confirmation is actively
219    /// sought by retransmitting probes; however, probes are multicast to the
220    /// solicited-node multicast address, using a timeout with exponential
221    /// backoff, rather than unicast to the cached link address. Also, probes
222    /// are only transmitted as long as packets continue to be sent to the
223    /// neighbor.
224    Unreachable(Unreachable<D>),
225}
226
227/// The state of dynamic neighbor table entries as published via events.
228///
229/// Note that this is not how state is held in the neighbor table itself,
230/// see [`DynamicNeighborState`].
231///
232/// Modeled after RFC 4861 section 7.3.2. Descriptions are kept
233/// implementation-independent by using a set of generic terminology.
234///
235/// ,------------------------------------------------------------------.
236/// | Generic Term              | ARP Term    | NDP Term               |
237/// |---------------------------+-------------+------------------------|
238/// | Reachability Probe        | ARP Request | Neighbor Solicitation  |
239/// | Reachability Confirmation | ARP Reply   | Neighbor Advertisement |
240/// `---------------------------+-------------+------------------------'
241#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
242pub enum EventDynamicState<L: LinkUnicastAddress> {
243    /// Reachability is in the process of being confirmed for a newly
244    /// created entry.
245    Incomplete,
246    /// Forward reachability has been confirmed; the path to the neighbor
247    /// is functioning properly.
248    Reachable(L),
249    /// Reachability is considered unknown.
250    ///
251    /// Occurs in one of two ways:
252    ///   1. Too much time has elapsed since the last positive reachability
253    ///      confirmation was received.
254    ///   2. Received a reachability confirmation from a neighbor with a
255    ///      different MAC address than the one cached.
256    Stale(L),
257    /// A packet was recently sent while reachability was considered
258    /// unknown.
259    ///
260    /// This state is an optimization that gives non-Neighbor-Discovery
261    /// related protocols time to confirm reachability after the last
262    /// confirmation of reachability has expired due to lack of recent
263    /// traffic.
264    Delay(L),
265    /// A reachability confirmation is actively sought by periodically
266    /// retransmitting unicast reachability probes until a reachability
267    /// confirmation is received, or until the maximum number of probes has
268    /// been sent.
269    Probe(L),
270    /// Target is considered unreachable. A reachability confirmation was not
271    /// received after transmitting the maximum number of reachability
272    /// probes.
273    Unreachable(L),
274}
275
276/// Neighbor state published via events.
277///
278/// Note that this is not how state is held in the neighbor table itself,
279/// see [`NeighborState`].
280///
281/// Either a dynamic state within the Neighbor Unreachability Detection (NUD)
282/// state machine, or a static entry that never expires.
283#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
284pub enum EventState<L: LinkUnicastAddress> {
285    /// Dynamic neighbor state.
286    Dynamic(EventDynamicState<L>),
287    /// Static neighbor state.
288    Static(L),
289}
290
291/// Neighbor event kind.
292#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
293pub enum EventKind<L: LinkUnicastAddress> {
294    /// A neighbor entry was added.
295    Added(EventState<L>),
296    /// A neighbor entry has changed.
297    Changed(EventState<L>),
298    /// A neighbor entry was removed.
299    Removed,
300}
301
302/// Neighbor event.
303#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
304#[generic_over_ip(I, Ip)]
305pub struct Event<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> {
306    /// The device.
307    pub device: DeviceId,
308    /// The neighbor's address.
309    pub addr: SpecifiedAddr<I::Addr>,
310    /// The kind of this neighbor event.
311    pub kind: EventKind<L>,
312    /// Time of this event.
313    pub at: Instant,
314}
315
316impl<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
317    /// Changes the device id type with `map`.
318    pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
319        let Self { device, kind, addr, at } = self;
320        Event { device: map(device), kind, addr, at }
321    }
322}
323
324impl<L: LinkUnicastAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
325    fn changed(
326        device: &DeviceId,
327        event_state: EventState<L>,
328        addr: SpecifiedAddr<I::Addr>,
329        at: Instant,
330    ) -> Self {
331        Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
332    }
333
334    fn added(
335        device: &DeviceId,
336        event_state: EventState<L>,
337        addr: SpecifiedAddr<I::Addr>,
338        at: Instant,
339    ) -> Self {
340        Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
341    }
342
343    fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
344        Self { device: device.clone(), kind: EventKind::Removed, addr, at }
345    }
346}
347
348fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
349    core_ctx: &mut CC,
350    bindings_ctx: &mut BC,
351    timers: &mut TimerHeap<I, BC>,
352    neighbor: SpecifiedAddr<I::Addr>,
353    event: NudEvent,
354    counter: &mut Option<NonZeroU16>,
355) -> bool
356where
357    I: Ip,
358    D: LinkDevice,
359    DeviceId: StrongDeviceIdentifier,
360    BC: NudBindingsContext<I, D, DeviceId>,
361    CC: NudConfigContext<I>,
362{
363    match counter {
364        Some(c) => {
365            *counter = NonZeroU16::new(c.get() - 1);
366            let retransmit_timeout = core_ctx.retransmit_timeout();
367            timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
368            true
369        }
370        None => false,
371    }
372}
373
374/// The state for an incomplete neighbor entry.
375#[derive(Debug, Derivative)]
376#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
377pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
378    transmit_counter: Option<NonZeroU16>,
379    pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
380    #[derivative(PartialEq = "ignore")]
381    notifiers: Vec<N>,
382    _marker: PhantomData<D>,
383}
384
385#[cfg(any(test, feature = "testutils"))]
386impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
387    fn clone(&self) -> Self {
388        // Do not clone `notifiers` since the LinkResolutionNotifier type is not
389        // required to implement `Clone` and notifiers are not used in equality
390        // checks in tests.
391        let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
392        Self {
393            transmit_counter: transmit_counter.clone(),
394            pending_frames: pending_frames.clone(),
395            notifiers: Vec::new(),
396            _marker: PhantomData,
397        }
398    }
399}
400
401impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
402    fn drop(&mut self) {
403        let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
404        for notifier in notifiers.drain(..) {
405            notifier.notify(Err(AddressResolutionFailed));
406        }
407    }
408}
409
410impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
411    /// Creates a new `Incomplete` entry with `pending_frames` and remaining
412    /// transmits `transmit_counter`.
413    #[cfg(any(test, feature = "testutils"))]
414    pub fn new_with_pending_frames_and_transmit_counter(
415        pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
416        transmit_counter: Option<NonZeroU16>,
417    ) -> Self {
418        Self {
419            transmit_counter,
420            pending_frames,
421            notifiers: Default::default(),
422            _marker: PhantomData,
423        }
424    }
425
426    fn new_with_packet<I, CC, BC, DeviceId, B, S>(
427        core_ctx: &mut CC,
428        bindings_ctx: &mut BC,
429        timers: &mut TimerHeap<I, BC>,
430        neighbor: SpecifiedAddr<I::Addr>,
431        packet: S,
432        meta: M,
433    ) -> Result<Self, ErrorAndSerializer<SerializeError<Never>, S>>
434    where
435        I: Ip,
436        D: LinkDevice,
437        BC: NudBindingsContext<I, D, DeviceId>,
438        CC: NudConfigContext<I>,
439        DeviceId: StrongDeviceIdentifier,
440        B: BufferMut,
441        S: NetworkSerializer<Buffer = B>,
442    {
443        // NB: it's important that we attempt to serialize the packet *before*
444        // scheduling a retransmission timer, so that if serialization fails and we
445        // propagate an error, we're not leaving a dangling timer.
446        let packet = packet
447            .serialize_vec_outer(&mut NetworkSerializationContext::default())
448            .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
449            .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
450            .into_inner();
451
452        let mut this = Incomplete {
453            transmit_counter: Some(core_ctx.max_multicast_solicit()),
454            pending_frames: VecDeque::from([(packet, meta)]),
455            notifiers: Vec::new(),
456            _marker: PhantomData,
457        };
458        // NB: transmission of a neighbor probe on entering INCOMPLETE (and subsequent
459        // retransmissions) is done by `handle_timer`, as it need not be done with the
460        // neighbor table lock held.
461        assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
462
463        Ok(this)
464    }
465
466    fn new_with_notifier<I, CC, BC, DeviceId>(
467        core_ctx: &mut CC,
468        bindings_ctx: &mut BC,
469        timers: &mut TimerHeap<I, BC>,
470        neighbor: SpecifiedAddr<I::Addr>,
471        notifier: BC::Notifier,
472    ) -> Self
473    where
474        I: Ip,
475        D: LinkDevice,
476        BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
477        CC: NudConfigContext<I>,
478        DeviceId: StrongDeviceIdentifier,
479    {
480        let mut this = Incomplete {
481            transmit_counter: Some(core_ctx.max_multicast_solicit()),
482            pending_frames: VecDeque::new(),
483            notifiers: [notifier].into(),
484            _marker: PhantomData,
485        };
486        // NB: transmission of a neighbor probe on entering INCOMPLETE (and subsequent
487        // retransmissions) is done by `handle_timer`, as it need not be done with the
488        // neighbor table lock held.
489        assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
490
491        this
492    }
493
494    fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
495        &mut self,
496        core_ctx: &mut CC,
497        bindings_ctx: &mut BC,
498        timers: &mut TimerHeap<I, BC>,
499        neighbor: SpecifiedAddr<I::Addr>,
500    ) -> bool
501    where
502        I: Ip,
503        D: LinkDevice,
504        DeviceId: StrongDeviceIdentifier,
505        BC: NudBindingsContext<I, D, DeviceId>,
506        CC: NudConfigContext<I>,
507    {
508        let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
509        schedule_timer_if_should_retransmit(
510            core_ctx,
511            bindings_ctx,
512            timers,
513            neighbor,
514            NudEvent::RetransmitMulticastProbe,
515            transmit_counter,
516        )
517    }
518
519    fn queue_packet<B, S>(
520        &mut self,
521        body: S,
522        meta: M,
523    ) -> Result<(), ErrorAndSerializer<SerializeError<Never>, S>>
524    where
525        B: BufferMut,
526        S: NetworkSerializer<Buffer = B>,
527    {
528        let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
529
530        // We don't accept new packets when the queue is full because earlier packets
531        // are more likely to initiate connections whereas later packets are more likely
532        // to carry data. E.g. A TCP SYN/SYN-ACK is likely to appear before a TCP
533        // segment with data and dropping the SYN/SYN-ACK may result in the TCP peer not
534        // processing the segment with data since the segment completing the handshake
535        // has not been received and handled yet.
536        if pending_frames.len() < MAX_PENDING_FRAMES {
537            pending_frames.push_back((
538                body.serialize_vec_outer(&mut NetworkSerializationContext::default())
539                    .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
540                    .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
541                    .into_inner(),
542                meta,
543            ));
544        }
545        Ok(())
546    }
547
548    /// Flush pending packets to the resolved link address and notify any observers
549    /// that link address resolution is complete.
550    fn complete_resolution<I, CC, BC>(
551        &mut self,
552        core_ctx: &mut CC,
553        bindings_ctx: &mut BC,
554        link_address: D::Address,
555    ) where
556        I: Ip,
557        D: LinkDevice,
558        BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
559        CC: NudSenderContext<I, D, BC>,
560    {
561        let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
562
563        // Send out pending packets while holding the NUD lock to prevent a potential
564        // ordering violation.
565        //
566        // If we drop the NUD lock before sending out these queued packets, another
567        // thread could take the NUD lock, observe that neighbor resolution is complete,
568        // and send a packet *before* these pending packets are sent out, resulting in
569        // out-of-order transmission to the device.
570        for (body, meta) in pending_frames.drain(..) {
571            // Ignore any errors on sending the IP packet, because a failure at this point
572            // is not actionable for the caller: failing to send a previously-queued packet
573            // doesn't mean that updating the neighbor entry should fail.
574            core_ctx
575                .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
576                .unwrap_or_else(|err| {
577                    error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
578                })
579        }
580        for notifier in notifiers.drain(..) {
581            notifier.notify(Ok(link_address));
582        }
583    }
584}
585
586/// State associated with a reachable neighbor.
587#[derive(Debug, Derivative)]
588#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
589pub struct Reachable<D: LinkDevice, I: Instant> {
590    /// The resolved link address.
591    pub link_address: D::Address,
592    /// The last confirmed instant.
593    pub last_confirmed_at: I,
594}
595
596/// State associated with a stale neighbor.
597#[derive(Debug, Derivative)]
598#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
599pub struct Stale<D: LinkDevice> {
600    /// The resolved link address.
601    pub link_address: D::Address,
602}
603
604impl<D: LinkDevice> Stale<D> {
605    fn enter_delay<I, BC, DeviceId: Clone>(
606        &mut self,
607        bindings_ctx: &mut BC,
608        timers: &mut TimerHeap<I, BC>,
609        neighbor: SpecifiedAddr<I::Addr>,
610    ) -> Delay<D>
611    where
612        I: Ip,
613        BC: NudBindingsContext<I, D, DeviceId>,
614    {
615        let Self { link_address } = *self;
616
617        // Start a timer to transition into PROBE after DELAY_FIRST_PROBE seconds if no
618        // packets are sent to this neighbor.
619        timers.schedule_neighbor(
620            bindings_ctx,
621            DELAY_FIRST_PROBE_TIME,
622            neighbor,
623            NudEvent::DelayFirstProbe,
624        );
625
626        Delay { link_address }
627    }
628}
629
630/// State associated with a neighbor in delay state.
631#[derive(Debug, Derivative)]
632#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
633pub struct Delay<D: LinkDevice> {
634    /// The resolved link address.
635    pub link_address: D::Address,
636}
637
638impl<D: LinkDevice> Delay<D> {
639    fn enter_probe<I, DeviceId, CC, BC>(
640        &mut self,
641        core_ctx: &mut CC,
642        bindings_ctx: &mut BC,
643        timers: &mut TimerHeap<I, BC>,
644        neighbor: SpecifiedAddr<I::Addr>,
645    ) -> Probe<D>
646    where
647        I: Ip,
648        DeviceId: StrongDeviceIdentifier,
649        BC: NudBindingsContext<I, D, DeviceId>,
650        CC: NudConfigContext<I>,
651    {
652        let Self { link_address } = *self;
653
654        // NB: transmission of a neighbor probe on entering PROBE (and subsequent
655        // retransmissions) is done by `handle_timer`, as it need not be done with the
656        // neighbor table lock held.
657        let retransmit_timeout = core_ctx.retransmit_timeout();
658        timers.schedule_neighbor(
659            bindings_ctx,
660            retransmit_timeout,
661            neighbor,
662            NudEvent::RetransmitUnicastProbe,
663        );
664
665        Probe {
666            link_address,
667            transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
668        }
669    }
670}
671
672#[derive(Debug, Derivative)]
673#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
674pub struct Probe<D: LinkDevice> {
675    link_address: D::Address,
676    transmit_counter: Option<NonZeroU16>,
677}
678
679impl<D: LinkDevice> Probe<D> {
680    fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
681        &mut self,
682        core_ctx: &mut CC,
683        bindings_ctx: &mut BC,
684        timers: &mut TimerHeap<I, BC>,
685        neighbor: SpecifiedAddr<I::Addr>,
686    ) -> bool
687    where
688        I: Ip,
689        DeviceId: StrongDeviceIdentifier,
690        BC: NudBindingsContext<I, D, DeviceId>,
691        CC: NudConfigContext<I>,
692    {
693        let Self { link_address: _, transmit_counter } = self;
694        schedule_timer_if_should_retransmit(
695            core_ctx,
696            bindings_ctx,
697            timers,
698            neighbor,
699            NudEvent::RetransmitUnicastProbe,
700            transmit_counter,
701        )
702    }
703
704    fn enter_unreachable<I, BC, DeviceId>(
705        &mut self,
706        bindings_ctx: &mut BC,
707        timers: &mut TimerHeap<I, BC>,
708        num_entries: usize,
709        last_gc: &mut Option<BC::Instant>,
710    ) -> Unreachable<D>
711    where
712        I: Ip,
713        BC: NudBindingsContext<I, D, DeviceId>,
714        DeviceId: Clone,
715    {
716        // This entry is deemed discardable now that it is not in active use; schedule
717        // garbage collection for the neighbor table if we are currently over the
718        // maximum amount of entries.
719        timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
720
721        let Self { link_address, transmit_counter: _ } = self;
722        Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
723    }
724}
725
726#[derive(Debug, Derivative)]
727#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
728pub struct Unreachable<D: LinkDevice> {
729    link_address: D::Address,
730    mode: UnreachableMode,
731}
732
733/// The dynamic neighbor state specific to the UNREACHABLE state as defined in
734/// [RFC 7048].
735///
736/// When a neighbor entry transitions to UNREACHABLE, the netstack will stop
737/// actively retransmitting probes if no packets are being sent to the neighbor.
738///
739/// If packets are sent through the neighbor, the netstack will continue to
740/// retransmit multicast probes, but with exponential backoff on the timer,
741/// based on the `BACKOFF_MULTIPLE` and clamped at `MAX_RETRANS_TIMER`.
742///
743/// [RFC 7048]: https://tools.ietf.org/html/rfc7048
744#[derive(Debug, Clone, Copy, Derivative)]
745#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
746pub(crate) enum UnreachableMode {
747    WaitingForPacketSend,
748    Backoff { probes_sent: NonZeroU32, packet_sent: bool },
749}
750
751impl UnreachableMode {
752    /// The amount of time to wait before transmitting another multicast probe
753    /// to the cached link address, based on how many probes we have transmitted
754    /// so far, as defined in [RFC 7048 section 4].
755    ///
756    /// [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
757    fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
758    where
759        I: Ip,
760        CC: NudConfigContext<I>,
761    {
762        let probes_sent = match self {
763            UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
764            UnreachableMode::WaitingForPacketSend => {
765                panic!("cannot calculate exponential backoff in state {self:?}")
766            }
767        };
768        // TODO(https://fxbug.dev/42083368): vary this retransmit timeout by some random
769        // "jitter factor" to avoid synchronization of transmissions from different
770        // hosts.
771        (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
772            .min(MAX_RETRANS_TIMER)
773    }
774}
775
776impl<D: LinkDevice> Unreachable<D> {
777    fn handle_timer<I, DeviceId, CC, BC>(
778        &mut self,
779        core_ctx: &mut CC,
780        bindings_ctx: &mut BC,
781        timers: &mut TimerHeap<I, BC>,
782        device_id: &DeviceId,
783        neighbor: SpecifiedAddr<I::Addr>,
784    ) -> Option<TransmitProbe<D::Address>>
785    where
786        I: Ip,
787        DeviceId: StrongDeviceIdentifier,
788        BC: NudBindingsContext<I, D, DeviceId>,
789        CC: NudConfigContext<I>,
790    {
791        let Self { link_address: _, mode } = self;
792        match mode {
793            UnreachableMode::WaitingForPacketSend => {
794                panic!(
795                    "timer should not have fired in UNREACHABLE while waiting for packet send; got \
796                    a retransmit multicast probe event for {neighbor} on {device_id:?}",
797                );
798            }
799            UnreachableMode::Backoff { probes_sent, packet_sent } => {
800                if *packet_sent {
801                    // It is all but guaranteed that we will never end up transmitting u32::MAX
802                    // probes, given the retransmit timeout backs off to MAX_RETRANS_TIMER (1 minute
803                    // by default), and u32::MAX minutes is over 8,000 years. By then we almost
804                    // certainly would have garbage-collected the neighbor entry.
805                    //
806                    // But we do a saturating add just to be safe.
807                    *probes_sent = probes_sent.saturating_add(1);
808                    *packet_sent = false;
809
810                    let duration = mode.next_backoff_retransmit_timeout(core_ctx);
811                    timers.schedule_neighbor(
812                        bindings_ctx,
813                        duration,
814                        neighbor,
815                        NudEvent::RetransmitMulticastProbe,
816                    );
817
818                    Some(TransmitProbe::Multicast)
819                } else {
820                    *mode = UnreachableMode::WaitingForPacketSend;
821
822                    None
823                }
824            }
825        }
826    }
827
828    /// Advance the UNREACHABLE state machine based on a packet being queued for
829    /// transmission.
830    ///
831    /// Returns whether a multicast neighbor probe should be sent as a result.
832    fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
833        &mut self,
834        core_ctx: &mut CC,
835        bindings_ctx: &mut BC,
836        timers: &mut TimerHeap<I, BC>,
837        neighbor: SpecifiedAddr<I::Addr>,
838    ) -> bool
839    where
840        I: Ip,
841        DeviceId: StrongDeviceIdentifier,
842        BC: NudBindingsContext<I, D, DeviceId>,
843        CC: NudConfigContext<I>,
844    {
845        let Self { link_address: _, mode } = self;
846        match mode {
847            UnreachableMode::WaitingForPacketSend => {
848                // We already transmitted MAX_MULTICAST_SOLICIT probes to the neighbor
849                // without confirmation, but now a packet is being sent to that neighbor, so
850                // we are resuming transmission of probes for as long as packets continue to
851                // be sent to the neighbor. Instead of retransmitting on a fixed timeout,
852                // use exponential backoff per [RFC 7048 section 4]:
853                //
854                //   If an implementation transmits more than MAX_UNICAST_SOLICIT/
855                //   MAX_MULTICAST_SOLICIT packets, then it SHOULD use the exponential
856                //   backoff of the retransmit timer.  This is to avoid any significant
857                //   load due to a steady background level of retransmissions from
858                //   implementations that retransmit a large number of Neighbor
859                //   Solicitations (NS) before discarding the NCE.
860                //
861                // [RFC 7048 section 4]: https://tools.ietf.org/html/rfc7048#section-4
862                let probes_sent = NonZeroU32::new(1).unwrap();
863                *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
864
865                let duration = mode.next_backoff_retransmit_timeout(core_ctx);
866                timers.schedule_neighbor(
867                    bindings_ctx,
868                    duration,
869                    neighbor,
870                    NudEvent::RetransmitMulticastProbe,
871                );
872
873                // Transmit a multicast probe.
874                true
875            }
876            UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
877                // We are in the exponential backoff phase of sending probes. Make a note
878                // that a packet was sent since the last transmission so that we will send
879                // another when the timer fires.
880                *packet_sent = true;
881
882                false
883            }
884        }
885    }
886}
887
888#[derive(Debug, PartialEq, Eq, Error)]
889pub(crate) enum EnterProbeError {
890    #[error("link address is unknown")]
891    LinkAddressUnknown,
892}
893
894impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
895    fn to_event_state(&self) -> EventState<D::Address> {
896        match self {
897            NeighborState::Dynamic(dynamic_state) => {
898                EventState::Dynamic(dynamic_state.to_event_dynamic_state())
899            }
900            NeighborState::Static(addr) => EventState::Static(*addr),
901        }
902    }
903
904    /// Transitions this neighbor to the Probe state. If a state change
905    /// resulted, returns the link address that should be probed by the caller
906    /// once the neighbor table lock is released.
907    ///
908    /// NB: if `neighbor` is a static entry, this will cause it to become and
909    /// remain a dynamic entry.
910    ///
911    /// Returns an error if this neighbor is in the Incomplete state because the
912    /// link address must be known in order to trigger a unicast probe.
913    pub(crate) fn enter_probe<I, DeviceId, CC>(
914        &mut self,
915        core_ctx: &mut CC,
916        bindings_ctx: &mut BT,
917        timers: &mut TimerHeap<I, BT>,
918        neighbor: SpecifiedAddr<I::Addr>,
919        device_id: &DeviceId,
920    ) -> Result<Option<D::Address>, EnterProbeError>
921    where
922        I: Ip,
923        DeviceId: StrongDeviceIdentifier,
924        BT: NudBindingsContext<I, D, DeviceId>,
925        CC: NudConfigContext<I>,
926    {
927        let link_addr = match self {
928            NeighborState::Dynamic(dynamic_state) => {
929                let link_addr = match dynamic_state {
930                    DynamicNeighborState::Reachable(Reachable { link_address, .. }) => {
931                        *link_address
932                    }
933                    DynamicNeighborState::Stale(Stale { link_address }) => *link_address,
934                    DynamicNeighborState::Delay(Delay { link_address }) => *link_address,
935                    DynamicNeighborState::Unreachable(Unreachable { link_address, .. }) => {
936                        *link_address
937                    }
938                    // The neighbor cannot be probed because its link address is
939                    // unknown.
940                    DynamicNeighborState::Incomplete(_) => {
941                        return Err(EnterProbeError::LinkAddressUnknown);
942                    }
943                    // The neighbor probe is already in progress; do not send
944                    // another probe.
945                    DynamicNeighborState::Probe(_) => return Ok(None),
946                };
947
948                // Cancel any existing timers in preparation for scheduling the
949                // retransmit timer.
950                dynamic_state.cancel_timer(bindings_ctx, timers, neighbor);
951                link_addr
952            }
953            NeighborState::Static(link_address) => *link_address,
954        };
955
956        // Schedule retransmit.
957        let retransmit_timeout = core_ctx.retransmit_timeout();
958        timers.schedule_neighbor(
959            bindings_ctx,
960            retransmit_timeout,
961            neighbor,
962            NudEvent::RetransmitUnicastProbe,
963        );
964
965        // Transition state.
966        *self = NeighborState::Dynamic(DynamicNeighborState::Probe(Probe {
967            link_address: link_addr,
968            transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
969        }));
970        let event_state = self.to_event_state();
971        bindings_ctx.on_event(Event::changed(
972            &device_id,
973            event_state,
974            neighbor,
975            bindings_ctx.now(),
976        ));
977        Ok(Some(link_addr))
978    }
979}
980
981impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
982    fn cancel_timer<I, DeviceId>(
983        &mut self,
984        bindings_ctx: &mut BC,
985        timers: &mut TimerHeap<I, BC>,
986        neighbor: SpecifiedAddr<I::Addr>,
987    ) where
988        I: Ip,
989        DeviceId: StrongDeviceIdentifier,
990        BC: NudBindingsContext<I, D, DeviceId>,
991    {
992        let expected_event = match self {
993            DynamicNeighborState::Incomplete(Incomplete {
994                transmit_counter: _,
995                pending_frames: _,
996                notifiers: _,
997                _marker,
998            }) => Some(NudEvent::RetransmitMulticastProbe),
999            DynamicNeighborState::Reachable(Reachable {
1000                link_address: _,
1001                last_confirmed_at: _,
1002            }) => Some(NudEvent::ReachableTime),
1003            DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
1004            DynamicNeighborState::Delay(Delay { link_address: _ }) => {
1005                Some(NudEvent::DelayFirstProbe)
1006            }
1007            DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
1008                Some(NudEvent::RetransmitUnicastProbe)
1009            }
1010            DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
1011                // A timer should be scheduled iff a packet was recently sent to the neighbor
1012                // and we are retransmitting probes with exponential backoff.
1013                match mode {
1014                    UnreachableMode::WaitingForPacketSend => None,
1015                    UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
1016                        Some(NudEvent::RetransmitMulticastProbe)
1017                    }
1018                }
1019            }
1020        };
1021        assert_eq!(
1022            timers.cancel_neighbor(bindings_ctx, neighbor),
1023            expected_event,
1024            "neighbor {neighbor} ({self:?}) had unexpected timer installed"
1025        );
1026    }
1027
1028    fn cancel_timer_and_complete_resolution<I, CC>(
1029        mut self,
1030        core_ctx: &mut CC,
1031        bindings_ctx: &mut BC,
1032        timers: &mut TimerHeap<I, BC>,
1033        neighbor: SpecifiedAddr<I::Addr>,
1034        link_address: D::Address,
1035    ) where
1036        I: Ip,
1037        BC: NudBindingsContext<I, D, CC::DeviceId>,
1038        CC: NudSenderContext<I, D, BC>,
1039    {
1040        self.cancel_timer(bindings_ctx, timers, neighbor);
1041
1042        match self {
1043            DynamicNeighborState::Incomplete(mut incomplete) => {
1044                incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
1045            }
1046            DynamicNeighborState::Reachable(_)
1047            | DynamicNeighborState::Stale(_)
1048            | DynamicNeighborState::Delay(_)
1049            | DynamicNeighborState::Probe(_)
1050            | DynamicNeighborState::Unreachable(_) => {}
1051        }
1052    }
1053
1054    fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
1055        match self {
1056            Self::Incomplete(_) => EventDynamicState::Incomplete,
1057            Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
1058                EventDynamicState::Reachable(*link_address)
1059            }
1060            Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
1061            Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
1062            Self::Probe(Probe { link_address, transmit_counter: _ }) => {
1063                EventDynamicState::Probe(*link_address)
1064            }
1065            Self::Unreachable(Unreachable { link_address, mode: _ }) => {
1066                EventDynamicState::Unreachable(*link_address)
1067            }
1068        }
1069    }
1070
1071    // Enters reachable state.
1072    fn enter_reachable<I, CC>(
1073        &mut self,
1074        core_ctx: &mut CC,
1075        bindings_ctx: &mut BC,
1076        timers: &mut TimerHeap<I, BC>,
1077        device_id: &CC::DeviceId,
1078        neighbor: SpecifiedAddr<I::Addr>,
1079        link_address: D::Address,
1080    ) where
1081        I: Ip,
1082        BC: NudBindingsContext<I, D, CC::DeviceId>,
1083        CC: NudSenderContext<I, D, BC>,
1084    {
1085        // TODO(https://fxbug.dev/42075782): if the new state matches the current state,
1086        // update the link address as necessary, but do not cancel + reschedule timers.
1087        let now = bindings_ctx.now();
1088        match self {
1089            // If this neighbor entry is already in REACHABLE, rather than proactively
1090            // rescheduling the timer (which can be a relatively expensive operation
1091            // especially in the hot path), simply update `last_confirmed_at` so that when
1092            // the timer does eventually fire, we can reschedule it accordingly.
1093            DynamicNeighborState::Reachable(Reachable {
1094                link_address: current,
1095                last_confirmed_at,
1096            }) if *current == link_address => {
1097                *last_confirmed_at = now;
1098                return;
1099            }
1100            DynamicNeighborState::Incomplete(_)
1101            | DynamicNeighborState::Reachable(_)
1102            | DynamicNeighborState::Stale(_)
1103            | DynamicNeighborState::Delay(_)
1104            | DynamicNeighborState::Probe(_)
1105            | DynamicNeighborState::Unreachable(_) => {}
1106        }
1107        let previous = core::mem::replace(
1108            self,
1109            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1110        );
1111        let event_dynamic_state = self.to_event_dynamic_state();
1112        debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1113        let event_state = EventState::Dynamic(event_dynamic_state);
1114        bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1115        previous.cancel_timer_and_complete_resolution(
1116            core_ctx,
1117            bindings_ctx,
1118            timers,
1119            neighbor,
1120            link_address,
1121        );
1122        timers.schedule_neighbor(
1123            bindings_ctx,
1124            core_ctx.base_reachable_time(),
1125            neighbor,
1126            NudEvent::ReachableTime,
1127        );
1128    }
1129
1130    // Enters the Stale state.
1131    //
1132    // # Panics
1133    //
1134    // Panics if `self` is already in Stale with a link address equal to
1135    // `link_address`, i.e. this function should only be called when state
1136    // actually changes.
1137    fn enter_stale<I, CC>(
1138        &mut self,
1139        core_ctx: &mut CC,
1140        bindings_ctx: &mut BC,
1141        timers: &mut TimerHeap<I, BC>,
1142        device_id: &CC::DeviceId,
1143        neighbor: SpecifiedAddr<I::Addr>,
1144        link_address: D::Address,
1145        num_entries: usize,
1146        last_gc: &mut Option<BC::Instant>,
1147    ) where
1148        I: Ip,
1149        BC: NudBindingsContext<I, D, CC::DeviceId>,
1150        CC: NudSenderContext<I, D, BC>,
1151    {
1152        // TODO(https://fxbug.dev/42075782): if the new state matches the current state,
1153        // update the link address as necessary, but do not cancel + reschedule timers.
1154        let previous =
1155            core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1156        let event_dynamic_state = self.to_event_dynamic_state();
1157        debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1158        let event_state = EventState::Dynamic(event_dynamic_state);
1159        bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1160        previous.cancel_timer_and_complete_resolution(
1161            core_ctx,
1162            bindings_ctx,
1163            timers,
1164            neighbor,
1165            link_address,
1166        );
1167
1168        // This entry is deemed discardable now that it is not in active use; schedule
1169        // garbage collection for the neighbor table if we are currently over the
1170        // maximum amount of entries.
1171        timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
1172
1173        // Stale entries don't do anything until an outgoing packet is queued for
1174        // transmission.
1175    }
1176
1177    /// Resolve the cached link address for this neighbor entry, or return an
1178    /// observer for an unresolved neighbor, and advance the NUD state machine
1179    /// accordingly (as if a packet had been sent to the neighbor).
1180    ///
1181    /// Also returns whether a multicast neighbor probe should be sent as a result.
1182    fn resolve_link_addr<I, DeviceId, CC>(
1183        &mut self,
1184        core_ctx: &mut CC,
1185        bindings_ctx: &mut BC,
1186        timers: &mut TimerHeap<I, BC>,
1187        device_id: &DeviceId,
1188        neighbor: SpecifiedAddr<I::Addr>,
1189    ) -> (
1190        LinkResolutionResult<
1191            D::Address,
1192            <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1193        >,
1194        bool,
1195    )
1196    where
1197        I: Ip,
1198        DeviceId: StrongDeviceIdentifier,
1199        BC: NudBindingsContext<I, D, DeviceId>,
1200        CC: NudConfigContext<I>,
1201    {
1202        match self {
1203            DynamicNeighborState::Incomplete(Incomplete {
1204                notifiers,
1205                transmit_counter: _,
1206                pending_frames: _,
1207                _marker,
1208            }) => {
1209                let (notifier, observer) = BC::Notifier::new();
1210                notifiers.push(notifier);
1211
1212                (LinkResolutionResult::Pending(observer), false)
1213            }
1214            DynamicNeighborState::Stale(entry) => {
1215                // Advance the state machine as if a packet had been sent to this neighbor.
1216                //
1217                // This is not required by the RFC, and it may result in neighbor probes going
1218                // out for this neighbor that would not have otherwise (the only other way a
1219                // STALE entry moves to DELAY is due to a packet being sent to it). However,
1220                // sending neighbor probes to confirm reachability is likely to be useful given
1221                // a client is attempting to resolve this neighbor. Additionally, this maintains
1222                // consistency with Netstack2's behavior.
1223                let delay @ Delay { link_address } =
1224                    entry.enter_delay(bindings_ctx, timers, neighbor);
1225                *self = DynamicNeighborState::Delay(delay);
1226                let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1227                bindings_ctx.on_event(Event::changed(
1228                    device_id,
1229                    event_state,
1230                    neighbor,
1231                    bindings_ctx.now(),
1232                ));
1233
1234                (LinkResolutionResult::Resolved(link_address), false)
1235            }
1236            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1237            | DynamicNeighborState::Delay(Delay { link_address })
1238            | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1239                (LinkResolutionResult::Resolved(*link_address), false)
1240            }
1241            DynamicNeighborState::Unreachable(unreachable) => {
1242                let Unreachable { link_address, mode: _ } = unreachable;
1243                let link_address = *link_address;
1244
1245                // Advance the state machine as if a packet had been sent to this neighbor.
1246                let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1247                    core_ctx,
1248                    bindings_ctx,
1249                    timers,
1250                    neighbor,
1251                );
1252                (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1253            }
1254        }
1255    }
1256
1257    /// Handle a packet being queued for transmission: either queue it as a
1258    /// pending packet for an unresolved neighbor, or send it to the cached link
1259    /// address, and advance the NUD state machine accordingly.
1260    ///
1261    /// Returns whether a multicast neighbor probe should be sent as a result.
1262    fn handle_packet_queued_to_send<I, CC, S>(
1263        &mut self,
1264        core_ctx: &mut CC,
1265        bindings_ctx: &mut BC,
1266        timers: &mut TimerHeap<I, BC>,
1267        device_id: &CC::DeviceId,
1268        neighbor: SpecifiedAddr<I::Addr>,
1269        body: S,
1270        meta: BC::TxMetadata,
1271    ) -> Result<bool, SendFrameError<S>>
1272    where
1273        I: Ip,
1274        BC: NudBindingsContext<I, D, CC::DeviceId>,
1275        CC: NudSenderContext<I, D, BC>,
1276        S: NetworkSerializer,
1277        S::Buffer: BufferMut,
1278    {
1279        match self {
1280            DynamicNeighborState::Incomplete(incomplete) => {
1281                incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1282            }
1283            // Send the IP packet while holding the NUD lock to prevent a potential
1284            // ordering violation.
1285            //
1286            // If we drop the NUD lock before sending out this packet, another thread
1287            // could take the NUD lock and send a packet *before* this packet is sent
1288            // out, resulting in out-of-order transmission to the device.
1289            DynamicNeighborState::Stale(entry) => {
1290                // Per [RFC 4861 section 7.3.3]:
1291                //
1292                //   The first time a node sends a packet to a neighbor whose entry is
1293                //   STALE, the sender changes the state to DELAY and sets a timer to
1294                //   expire in DELAY_FIRST_PROBE_TIME seconds.
1295                //
1296                // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
1297                let delay @ Delay { link_address } =
1298                    entry.enter_delay(bindings_ctx, timers, neighbor);
1299                *self = DynamicNeighborState::Delay(delay);
1300                let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1301                bindings_ctx.on_event(Event::changed(
1302                    device_id,
1303                    event_state,
1304                    neighbor,
1305                    bindings_ctx.now(),
1306                ));
1307
1308                core_ctx.send_ip_packet_to_neighbor_link_addr(
1309                    bindings_ctx,
1310                    link_address,
1311                    body,
1312                    meta,
1313                )?;
1314
1315                Ok(false)
1316            }
1317            DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1318            | DynamicNeighborState::Delay(Delay { link_address })
1319            | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1320                core_ctx.send_ip_packet_to_neighbor_link_addr(
1321                    bindings_ctx,
1322                    *link_address,
1323                    body,
1324                    meta,
1325                )?;
1326
1327                Ok(false)
1328            }
1329            DynamicNeighborState::Unreachable(unreachable) => {
1330                let Unreachable { link_address, mode: _ } = unreachable;
1331                core_ctx.send_ip_packet_to_neighbor_link_addr(
1332                    bindings_ctx,
1333                    *link_address,
1334                    body,
1335                    meta,
1336                )?;
1337
1338                let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1339                    core_ctx,
1340                    bindings_ctx,
1341                    timers,
1342                    neighbor,
1343                );
1344                Ok(do_multicast_solicit)
1345            }
1346        }
1347    }
1348
1349    fn handle_probe<I, CC>(
1350        &mut self,
1351        core_ctx: &mut CC,
1352        bindings_ctx: &mut BC,
1353        timers: &mut TimerHeap<I, BC>,
1354        device_id: &CC::DeviceId,
1355        neighbor: SpecifiedAddr<I::Addr>,
1356        link_address: D::Address,
1357        num_entries: usize,
1358        last_gc: &mut Option<BC::Instant>,
1359    ) where
1360        I: Ip,
1361        BC: NudBindingsContext<I, D, CC::DeviceId>,
1362        CC: NudSenderContext<I, D, BC>,
1363    {
1364        // Per [RFC 4861 section 7.2.3] ("Receipt of Neighbor Solicitations"):
1365        //
1366        //   If an entry already exists, and the cached link-layer address
1367        //   differs from the one in the received Source Link-Layer option, the
1368        //   cached address should be replaced by the received address, and the
1369        //   entry's reachability state MUST be set to STALE.
1370        //
1371        // [RFC 4861 section 7.2.3]: https://tools.ietf.org/html/rfc4861#section-7.2.3
1372        let transition_to_stale = match self {
1373            DynamicNeighborState::Incomplete(_) => true,
1374            DynamicNeighborState::Reachable(Reachable {
1375                link_address: current,
1376                last_confirmed_at: _,
1377            })
1378            | DynamicNeighborState::Stale(Stale { link_address: current })
1379            | DynamicNeighborState::Delay(Delay { link_address: current })
1380            | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1381            | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1382                current != &link_address
1383            }
1384        };
1385        if transition_to_stale {
1386            self.enter_stale(
1387                core_ctx,
1388                bindings_ctx,
1389                timers,
1390                device_id,
1391                neighbor,
1392                link_address,
1393                num_entries,
1394                last_gc,
1395            );
1396        }
1397    }
1398
1399    fn handle_confirmation<I, CC>(
1400        &mut self,
1401        core_ctx: &mut CC,
1402        bindings_ctx: &mut BC,
1403        timers: &mut TimerHeap<I, BC>,
1404        device_id: &CC::DeviceId,
1405        neighbor: SpecifiedAddr<I::Addr>,
1406        link_address: Option<D::Address>,
1407        flags: ConfirmationFlags,
1408        num_entries: usize,
1409        last_gc: &mut Option<BC::Instant>,
1410    ) where
1411        I: Ip,
1412        BC: NudBindingsContext<I, D, CC::DeviceId>,
1413        CC: NudSenderContext<I, D, BC>,
1414    {
1415        let ConfirmationFlags { solicited_flag, override_flag } = flags;
1416        enum NewState<A> {
1417            Reachable { link_address: A },
1418            Stale { link_address: A },
1419        }
1420
1421        let new_state = match self {
1422            DynamicNeighborState::Incomplete(Incomplete {
1423                transmit_counter: _,
1424                pending_frames: _,
1425                notifiers: _,
1426                _marker,
1427            }) => {
1428                // Per RFC 4861 section 7.2.5:
1429                //
1430                //   If the advertisement's Solicited flag is set, the state of the
1431                //   entry is set to REACHABLE; otherwise, it is set to STALE.
1432                //
1433                //   Note that the Override flag is ignored if the entry is in the
1434                //   INCOMPLETE state.
1435                //
1436                // Also note that if the target link-layer address was not specified in this
1437                // neighbor confirmation, we ignore the confirmation: there is nothing we can do
1438                // since we don't have a cached link-layer address.
1439                link_address.map(|link_address| {
1440                    if solicited_flag {
1441                        NewState::Reachable { link_address }
1442                    } else {
1443                        NewState::Stale { link_address }
1444                    }
1445                })
1446            }
1447
1448            // Ignore duplicate neighbor confirmations that arrive less than
1449            // `override_lock_time()` from the previous one. This matches Linux
1450            // behavior. See https://fxbug.dev/490161899 .
1451            DynamicNeighborState::Reachable(Reachable {
1452                link_address: current,
1453                last_confirmed_at,
1454            }) if !core_ctx.override_lock_time().is_zero()
1455                && solicited_flag
1456                && bindings_ctx.now().saturating_duration_since(*last_confirmed_at)
1457                    < core_ctx.override_lock_time() =>
1458            {
1459                log::warn!(
1460                    "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}.
1461                    New address {:?}",
1462                    neighbor,
1463                    current,
1464                    link_address
1465                );
1466                None
1467            }
1468
1469            DynamicNeighborState::Reachable(Reachable {
1470                link_address: current,
1471                last_confirmed_at: _,
1472            })
1473            | DynamicNeighborState::Stale(Stale { link_address: current })
1474            | DynamicNeighborState::Delay(Delay { link_address: current })
1475            | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1476            | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1477                // Per RFC 4861 section 4.4:
1478                //
1479                //    The link-layer address for the target, i.e., the
1480                //    sender of the advertisement ... MUST be
1481                //    included on link layers that have addresses when
1482                //    responding to multicast solicitations.  When
1483                //    responding to a unicast Neighbor Solicitation this
1484                //    option SHOULD be included.
1485                //
1486                //    ... When responding to unicast
1487                //    solicitations, the option can be omitted since the
1488                //    sender of the solicitation has the correct link-
1489                //    layer address; otherwise, it would not be able to
1490                //    send the unicast solicitation in the first place.
1491                //
1492                // Because neighbors may choose to omit the target link-layer address option
1493                // from neighbor confirmations, we must be tolerant of its absence. In the case
1494                // of absence, we use the cached link-layer address if one is available.
1495                let updated_link_address = link_address
1496                    .and_then(|link_address| (current != &link_address).then_some(link_address));
1497
1498                match (solicited_flag, updated_link_address, override_flag) {
1499                    // Per RFC 4861 section 7.2.5:
1500                    //
1501                    //   If [either] the Override flag is set, or the supplied link-layer address is
1502                    //   the same as that in the cache, [and] ... the Solicited flag is set, the
1503                    //   entry MUST be set to REACHABLE.
1504                    (true, _, true) | (true, None, _) => {
1505                        Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1506                    }
1507                    // Per RFC 4861 section 7.2.5:
1508                    //
1509                    //   If the Override flag is clear and the supplied link-layer address differs
1510                    //   from that in the cache, then one of two actions takes place:
1511                    //
1512                    //    a. If the state of the entry is REACHABLE, set it to STALE, but do not
1513                    //       update the entry in any other way.
1514                    //    b. Otherwise, the received advertisement should be ignored and MUST NOT
1515                    //       update the cache.
1516                    (_, Some(_), false) => match self {
1517                        // NB: do not update the link address.
1518                        DynamicNeighborState::Reachable(Reachable {
1519                            link_address,
1520                            last_confirmed_at: _,
1521                        }) => Some(NewState::Stale { link_address: *link_address }),
1522                        // Ignore the advertisement and do not update the cache.
1523                        DynamicNeighborState::Stale(_)
1524                        | DynamicNeighborState::Delay(_)
1525                        | DynamicNeighborState::Probe(_)
1526                        | DynamicNeighborState::Unreachable(_) => None,
1527                        // The INCOMPLETE state was already handled in the outer match.
1528                        DynamicNeighborState::Incomplete(_) => unreachable!(),
1529                    },
1530                    // Per RFC 4861 section 7.2.5:
1531                    //
1532                    //   If the Override flag is set [and] ... the Solicited flag is zero and the
1533                    //   link-layer address was updated with a different address, the state MUST be
1534                    //   set to STALE.
1535                    (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1536                    // Per RFC 4861 section 7.2.5:
1537                    //
1538                    //   There is no need to update the state for unsolicited advertisements that do
1539                    //   not change the contents of the cache.
1540                    (false, None, _) => None,
1541                }
1542            }
1543        };
1544        match new_state {
1545            Some(NewState::Reachable { link_address }) => self.enter_reachable(
1546                core_ctx,
1547                bindings_ctx,
1548                timers,
1549                device_id,
1550                neighbor,
1551                link_address,
1552            ),
1553            Some(NewState::Stale { link_address }) => self.enter_stale(
1554                core_ctx,
1555                bindings_ctx,
1556                timers,
1557                device_id,
1558                neighbor,
1559                link_address,
1560                num_entries,
1561                last_gc,
1562            ),
1563            None => {}
1564        }
1565    }
1566}
1567
1568#[cfg(any(test, feature = "testutils"))]
1569pub(crate) mod testutil {
1570    use super::*;
1571
1572    use alloc::sync::Arc;
1573
1574    use netstack3_base::sync::Mutex;
1575    use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1576
1577    /// Asserts that `device_id`'s `neighbor` resolved to `expected_link_addr`.
1578    pub fn assert_dynamic_neighbor_with_addr<
1579        I: Ip,
1580        D: LinkDevice,
1581        BC: NudBindingsContext<I, D, CC::DeviceId>,
1582        CC: NudContext<I, D, BC>,
1583    >(
1584        core_ctx: &mut CC,
1585        device_id: CC::DeviceId,
1586        neighbor: SpecifiedAddr<I::Addr>,
1587        expected_link_addr: D::Address,
1588    ) {
1589        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1590            assert_matches!(
1591                neighbors.get(&neighbor),
1592                Some(NeighborState::Dynamic(
1593                    DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1594                    | DynamicNeighborState::Stale(Stale{ link_address })
1595                )) => {
1596                    assert_eq!(link_address, &expected_link_addr)
1597                }
1598            )
1599        })
1600    }
1601
1602    /// Asserts that the `device_id`'s `neighbor` is at `expected_state`.
1603    pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1604        core_ctx: &mut CC,
1605        device_id: CC::DeviceId,
1606        neighbor: SpecifiedAddr<I::Addr>,
1607        expected_state: DynamicNeighborState<D, BC>,
1608    ) where
1609        I: Ip,
1610        D: LinkDevice + PartialEq,
1611        BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1612        CC: NudContext<I, D, BC>,
1613    {
1614        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1615            assert_matches!(
1616                neighbors.get(&neighbor),
1617                Some(NeighborState::Dynamic(state)) => {
1618                    assert_eq!(state, &expected_state)
1619                }
1620            )
1621        })
1622    }
1623
1624    /// Asserts that `device_id`'s `neighbor` doesn't exist.
1625    pub fn assert_neighbor_unknown<
1626        I: Ip,
1627        D: LinkDevice,
1628        BC: NudBindingsContext<I, D, CC::DeviceId>,
1629        CC: NudContext<I, D, BC>,
1630    >(
1631        core_ctx: &mut CC,
1632        device_id: CC::DeviceId,
1633        neighbor: SpecifiedAddr<I::Addr>,
1634    ) {
1635        core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1636            assert_matches!(neighbors.get(&neighbor), None)
1637        })
1638    }
1639
1640    impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1641        for FakeBindingsCtx<Id, Event, State, FrameMeta>
1642    {
1643        type Notifier = FakeLinkResolutionNotifier<D>;
1644    }
1645
1646    /// A fake implementation of [`LinkResolutionNotifier`].
1647    #[derive(Debug)]
1648    pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1649        Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1650    );
1651
1652    impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1653        type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1654
1655        fn new() -> (Self, Self::Observer) {
1656            let inner = Arc::new(Mutex::new(None));
1657            (Self(inner.clone()), inner)
1658        }
1659
1660        fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1661            let Self(inner) = self;
1662            let mut inner = inner.lock();
1663            assert_eq!(*inner, None, "resolved link address was set more than once");
1664            *inner = Some(result);
1665        }
1666    }
1667
1668    impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1669        S: UseDelegateNudContext
1670    {
1671    }
1672    impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1673    where
1674        S: DelegateNudContext<I>,
1675    {
1676        type Delegate<T> = S::Delegate<T>;
1677    }
1678}
1679
1680#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1681enum NudEvent {
1682    RetransmitMulticastProbe,
1683    ReachableTime,
1684    DelayFirstProbe,
1685    RetransmitUnicastProbe,
1686}
1687
1688/// The timer ID for the NUD module.
1689#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1690#[generic_over_ip(I, Ip)]
1691pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1692    device_id: D,
1693    timer_type: NudTimerType,
1694    _marker: PhantomData<(I, L)>,
1695}
1696
1697#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1698enum NudTimerType {
1699    Neighbor,
1700    GarbageCollection,
1701}
1702
1703/// A wrapper for [`LocalTimerHeap`] that we can attach NUD helpers to.
1704#[derive(Debug)]
1705pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1706    gc: BT::Timer,
1707    neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1708}
1709
1710impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1711    fn new<
1712        DeviceId: WeakDeviceIdentifier,
1713        L: LinkDevice,
1714        CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1715    >(
1716        bindings_ctx: &mut BC,
1717        device_id: DeviceId,
1718    ) -> Self {
1719        Self {
1720            neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1721                bindings_ctx,
1722                NudTimerId {
1723                    device_id: device_id.clone(),
1724                    timer_type: NudTimerType::Neighbor,
1725                    _marker: PhantomData,
1726                },
1727            ),
1728            gc: CC::new_timer(
1729                bindings_ctx,
1730                NudTimerId {
1731                    device_id,
1732                    timer_type: NudTimerType::GarbageCollection,
1733                    _marker: PhantomData,
1734                },
1735            ),
1736        }
1737    }
1738
1739    fn schedule_neighbor(
1740        &mut self,
1741        bindings_ctx: &mut BC,
1742        after: NonZeroDuration,
1743        neighbor: SpecifiedAddr<I::Addr>,
1744        event: NudEvent,
1745    ) {
1746        let Self { neighbor: heap, gc: _ } = self;
1747        assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1748    }
1749
1750    fn schedule_neighbor_at(
1751        &mut self,
1752        bindings_ctx: &mut BC,
1753        at: BC::Instant,
1754        neighbor: SpecifiedAddr<I::Addr>,
1755        event: NudEvent,
1756    ) {
1757        let Self { neighbor: heap, gc: _ } = self;
1758        assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1759    }
1760
1761    /// Cancels a neighbor timer.
1762    fn cancel_neighbor(
1763        &mut self,
1764        bindings_ctx: &mut BC,
1765        neighbor: SpecifiedAddr<I::Addr>,
1766    ) -> Option<NudEvent> {
1767        let Self { neighbor: heap, gc: _ } = self;
1768        heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1769    }
1770
1771    fn pop_neighbor(
1772        &mut self,
1773        bindings_ctx: &mut BC,
1774    ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1775        let Self { neighbor: heap, gc: _ } = self;
1776        heap.pop(bindings_ctx)
1777    }
1778
1779    /// Schedules a garbage collection IFF we hit the entries threshold and it's
1780    /// not already scheduled.
1781    fn maybe_schedule_gc(
1782        &mut self,
1783        bindings_ctx: &mut BC,
1784        num_entries: usize,
1785        last_gc: &Option<BC::Instant>,
1786    ) {
1787        let Self { gc, neighbor: _ } = self;
1788        if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1789            let instant = if let Some(last_gc) = last_gc {
1790                last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1791            } else {
1792                bindings_ctx.now()
1793            };
1794            // Scheduling a timer requires a mutable borrow and we're
1795            // currently holding it exclusively. We just checked that the timer
1796            // is not scheduled, so this assertion always holds.
1797            assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1798        }
1799    }
1800}
1801
1802/// NUD module per-device state.
1803#[derive(Debug)]
1804pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1805    // TODO(https://fxbug.dev/42076887): Key neighbors by `UnicastAddr`.
1806    neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1807    last_gc: Option<BT::Instant>,
1808    timer_heap: TimerHeap<I, BT>,
1809}
1810
1811impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1812    /// Returns current neighbors.
1813    #[cfg(any(test, feature = "testutils"))]
1814    pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1815        &self.neighbors
1816    }
1817
1818    fn entry_and_timer_heap(
1819        &mut self,
1820        addr: SpecifiedAddr<I::Addr>,
1821    ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1822        let Self { neighbors, timer_heap, .. } = self;
1823        (neighbors.entry(addr), timer_heap)
1824    }
1825}
1826
1827impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1828    /// Constructs a new `NudState` for `device_id`.
1829    pub fn new<
1830        DeviceId: WeakDeviceIdentifier,
1831        CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1832    >(
1833        bindings_ctx: &mut BC,
1834        device_id: DeviceId,
1835    ) -> Self {
1836        Self {
1837            neighbors: Default::default(),
1838            last_gc: None,
1839            timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1840        }
1841    }
1842}
1843
1844/// The bindings context for NUD.
1845pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1846    TimerContext
1847    + LinkResolutionContext<D>
1848    + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1849    + NudBindingsTypes<D>
1850{
1851}
1852
1853impl<
1854    I: Ip,
1855    D: LinkDevice,
1856    DeviceId,
1857    BC: TimerContext
1858        + LinkResolutionContext<D>
1859        + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1860        + NudBindingsTypes<D>,
1861> NudBindingsContext<I, D, DeviceId> for BC
1862{
1863}
1864
1865/// A marker trait for types provided by bindings to NUD.
1866pub trait NudBindingsTypes<D: LinkDevice>:
1867    LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1868{
1869}
1870
1871impl<BT, D> NudBindingsTypes<D> for BT
1872where
1873    D: LinkDevice,
1874    BT: LinkResolutionContext<D>
1875        + InstantBindingsTypes
1876        + TimerBindingsTypes
1877        + TxMetadataBindingsTypes,
1878{
1879}
1880
1881/// An execution context that allows creating link resolution notifiers.
1882pub trait LinkResolutionContext<D: LinkDevice> {
1883    /// A notifier held by core that can be used to inform interested parties of
1884    /// the result of link address resolution.
1885    type Notifier: LinkResolutionNotifier<D>;
1886}
1887
1888/// A notifier held by core that can be used to inform interested parties of the
1889/// result of link address resolution.
1890pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1891    /// The corresponding observer that can be used to observe the result of
1892    /// link address resolution.
1893    type Observer;
1894
1895    /// Create a connected (notifier, observer) pair.
1896    fn new() -> (Self, Self::Observer);
1897
1898    /// Signal to Bindings that link address resolution has completed for a
1899    /// neighbor.
1900    fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1901}
1902
1903/// The execution context for NUD for a link device.
1904pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1905    /// The inner configuration context.
1906    type ConfigCtx<'a>: NudConfigContext<I>;
1907    /// The inner send context.
1908    type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1909
1910    /// Calls the function with a mutable reference to the NUD state and the
1911    /// core sender context.
1912    fn with_nud_state_mut_and_sender_ctx<
1913        O,
1914        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1915    >(
1916        &mut self,
1917        device_id: &Self::DeviceId,
1918        cb: F,
1919    ) -> O;
1920
1921    /// Calls the function with a mutable reference to the NUD state and NUD
1922    /// configuration for the device.
1923    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1924        &mut self,
1925        device_id: &Self::DeviceId,
1926        cb: F,
1927    ) -> O;
1928
1929    /// Calls the function with an immutable reference to the NUD state.
1930    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1931        &mut self,
1932        device_id: &Self::DeviceId,
1933        cb: F,
1934    ) -> O;
1935
1936    /// Sends a neighbor probe/solicitation message.
1937    ///
1938    /// If `remote_link_addr` is provided, the message will be unicasted to that
1939    /// address; if it is `None`, the message will be multicast.
1940    fn send_neighbor_solicitation(
1941        &mut self,
1942        bindings_ctx: &mut BC,
1943        device_id: &Self::DeviceId,
1944        lookup_addr: SpecifiedAddr<I::Addr>,
1945        remote_link_addr: Option<D::Address>,
1946    );
1947}
1948
1949/// A marker trait to enable the blanket impl of [`NudContext`] for types
1950/// implementing [`DelegateNudContext`].
1951pub trait UseDelegateNudContext {}
1952
1953/// Enables a blanket implementation of [`NudContext`] via delegate that can
1954/// wrap a mutable reference of `Self`.
1955///
1956/// The `UseDelegateNudContext` requirement here is steering users to to the
1957/// right thing to enable the blanket implementation.
1958pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1959    /// The delegate that implements [`NudContext`].
1960    type Delegate<T>: ref_cast::RefCast<From = T>;
1961    /// Wraps self into a mutable delegate reference.
1962    fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1963        <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1964    }
1965}
1966
1967impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1968where
1969    I: Ip,
1970    D: LinkDevice,
1971    BC: NudBindingsTypes<D>,
1972    CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1973        // This seems redundant with `DelegateNudContext` but it is required to
1974        // get the compiler happy.
1975        + UseDelegateNudContext
1976        + DeviceIdContext<D>,
1977{
1978    type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1979    type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1980    fn with_nud_state_mut_and_sender_ctx<
1981        O,
1982        F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1983    >(
1984        &mut self,
1985        device_id: &Self::DeviceId,
1986        cb: F,
1987    ) -> O {
1988        self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1989    }
1990
1991    fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1992        &mut self,
1993        device_id: &Self::DeviceId,
1994        cb: F,
1995    ) -> O {
1996        self.wrap().with_nud_state_mut(device_id, cb)
1997    }
1998    fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1999        &mut self,
2000        device_id: &Self::DeviceId,
2001        cb: F,
2002    ) -> O {
2003        self.wrap().with_nud_state(device_id, cb)
2004    }
2005    fn send_neighbor_solicitation(
2006        &mut self,
2007        bindings_ctx: &mut BC,
2008        device_id: &Self::DeviceId,
2009        lookup_addr: SpecifiedAddr<I::Addr>,
2010        remote_link_addr: Option<D::Address>,
2011    ) {
2012        self.wrap().send_neighbor_solicitation(
2013            bindings_ctx,
2014            device_id,
2015            lookup_addr,
2016            remote_link_addr,
2017        )
2018    }
2019}
2020
2021/// IP extension trait to support [`NudIcmpContext`].
2022pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2023    /// IP packet metadata needed when sending ICMP destination unreachable
2024    /// errors as a result of link-layer address resolution failure.
2025    type Metadata;
2026
2027    /// Extracts IP-version specific metadata from `packet`.
2028    fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2029}
2030
2031impl NudIcmpIpExt for Ipv4 {
2032    type Metadata = Ipv4FragmentType;
2033
2034    fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2035        packet.fragment_type()
2036    }
2037}
2038
2039impl NudIcmpIpExt for Ipv6 {
2040    type Metadata = ();
2041
2042    fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2043        ()
2044    }
2045}
2046
2047/// The execution context which allows sending ICMP destination unreachable
2048/// errors, which needs to happen when address resolution fails.
2049pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2050    /// Send an ICMP destination unreachable error to `original_src_ip` as
2051    /// a result of `frame` being unable to be sent/forwarded due to link
2052    /// layer address resolution failure.
2053    ///
2054    /// `original_src_ip`, `original_dst_ip`, and `header_len` are all IP
2055    /// header fields from `frame`.
2056    fn send_icmp_dest_unreachable(
2057        &mut self,
2058        bindings_ctx: &mut BC,
2059        frame: Buf<Vec<u8>>,
2060        device_id: Option<&Self::DeviceId>,
2061        original_src_ip: SocketIpAddr<I::Addr>,
2062        original_dst_ip: SocketIpAddr<I::Addr>,
2063        header_len: usize,
2064        proto: I::Proto,
2065        metadata: I::Metadata,
2066    );
2067}
2068
2069/// NUD configurations.
2070#[derive(Clone, Debug)]
2071pub struct NudUserConfig {
2072    /// The maximum number of unicast solicitations as defined in [RFC 4861
2073    /// section 10].
2074    ///
2075    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2076    pub max_unicast_solicitations: NonZeroU16,
2077    /// The maximum number of multicast solicitations as defined in [RFC 4861
2078    /// section 10].
2079    ///
2080    /// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
2081    pub max_multicast_solicitations: NonZeroU16,
2082    /// The base value used for computing the duration a neighbor is considered
2083    /// reachable after receiving a reachability confirmation as defined in
2084    /// [RFC 4861 section 6.3.2].
2085    ///
2086    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2087    pub base_reachable_time: NonZeroDuration,
2088    /// The time between retransmissions of neighbor probe messages to a neighbor
2089    /// when resolving the address or when probing the reachability of a neighbor
2090    /// as defined in [RFC 4861 section 6.3.2].
2091    ///
2092    /// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
2093    pub retrans_timer: NonZeroDuration,
2094}
2095
2096impl Default for NudUserConfig {
2097    fn default() -> Self {
2098        NudUserConfig {
2099            max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2100            max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2101            base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2102            retrans_timer: DEFAULT_RETRANS_TIMER,
2103        }
2104    }
2105}
2106
2107/// An update structure for [`NudUserConfig`].
2108///
2109/// Only fields with variant `Some` are updated.
2110#[allow(missing_docs)]
2111#[derive(Clone, Debug, Eq, PartialEq, Default)]
2112pub struct NudUserConfigUpdate {
2113    pub max_unicast_solicitations: Option<NonZeroU16>,
2114    pub max_multicast_solicitations: Option<NonZeroU16>,
2115    pub base_reachable_time: Option<NonZeroDuration>,
2116    pub retrans_timer: Option<NonZeroDuration>,
2117}
2118
2119impl NudUserConfigUpdate {
2120    /// Applies the configuration returning a [`NudUserConfigUpdate`] with the
2121    /// changed fields populated.
2122    pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2123        fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2124            if let Some(opt) = opt.as_mut() {
2125                core::mem::swap(opt, target)
2126            }
2127        }
2128        let Self {
2129            max_unicast_solicitations,
2130            max_multicast_solicitations,
2131            base_reachable_time,
2132            retrans_timer,
2133        } = &mut self;
2134        swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2135        swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2136        swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2137        swap_if_set(retrans_timer, &mut config.retrans_timer);
2138
2139        self
2140    }
2141}
2142
2143/// The execution context for NUD that allows accessing NUD configuration (such
2144/// as timer durations) for a particular device.
2145pub trait NudConfigContext<I: Ip> {
2146    /// The amount of time between retransmissions of neighbor probe messages.
2147    ///
2148    /// This corresponds to the configurable per-interface `RetransTimer` value
2149    /// used in NUD as defined in [RFC 4861 section 6.3.2].
2150    ///
2151    /// [RFC 4861 section 6.3.2]: https://datatracker.ietf.org/doc/html/rfc4861#section-6.3.2
2152    fn retransmit_timeout(&mut self) -> NonZeroDuration;
2153
2154    /// Calls the callback with an immutable reference to NUD configurations.
2155    fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2156
2157    /// Returns the maximum number of unicast solicitations.
2158    fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2159        self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2160            *max_unicast_solicitations
2161        })
2162    }
2163
2164    /// Returns the maximum number of multicast solicitations.
2165    fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2166        self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2167            *max_multicast_solicitations
2168        })
2169    }
2170
2171    /// Returns the base reachable time, the duration a neighbor is considered
2172    /// reachable after receiving a reachability confirmation.
2173    fn base_reachable_time(&mut self) -> NonZeroDuration {
2174        self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2175    }
2176
2177    /// Amount of time from the moment a host becomes reachable before the
2178    /// entry can overridden.
2179    fn override_lock_time(&mut self) -> Duration;
2180}
2181
2182/// The execution context for NUD for a link device that allows sending IP
2183/// packets to specific neighbors.
2184pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2185    NudConfigContext<I> + DeviceIdContext<D>
2186{
2187    /// Send an IP frame to the neighbor with the specified link address.
2188    fn send_ip_packet_to_neighbor_link_addr<S>(
2189        &mut self,
2190        bindings_ctx: &mut BC,
2191        neighbor_link_addr: D::Address,
2192        body: S,
2193        meta: BC::TxMetadata,
2194    ) -> Result<(), SendFrameError<S>>
2195    where
2196        S: NetworkSerializer,
2197        S::Buffer: BufferMut;
2198}
2199
2200/// An implementation of NUD for the IP layer.
2201pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2202    /// Handles an incoming neighbor probe message.
2203    ///
2204    /// For IPv6, this can be an NDP Neighbor Solicitation or an NDP Router
2205    /// Advertisement message.
2206    fn handle_neighbor_probe(
2207        &mut self,
2208        bindings_ctx: &mut BC,
2209        device_id: &Self::DeviceId,
2210        neighbor: SpecifiedAddr<I::Addr>,
2211        link_addr: &[u8],
2212    );
2213
2214    /// Handles an incoming neighbor confirmation message.
2215    ///
2216    /// For IPv6, this can be an NDP Neighbor Advertisement.
2217    fn handle_neighbor_confirmation(
2218        &mut self,
2219        bindings_ctx: &mut BC,
2220        device_id: &Self::DeviceId,
2221        neighbor: SpecifiedAddr<I::Addr>,
2222        link_addr: Option<&[u8]>,
2223        flags: ConfirmationFlags,
2224    );
2225
2226    /// Clears the neighbor table.
2227    fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2228}
2229
2230/// Specifies the link-layer address of a neighbor.
2231#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2232pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2233    /// The destination is a known neighbor with the given link-layer address.
2234    Resolved(A),
2235    /// The destination is pending neighbor resolution.
2236    Pending(Observer),
2237}
2238
2239/// An implementation of NUD for a link device.
2240pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2241    /// Sets a dynamic neighbor's entry state to the specified values in
2242    /// response to the source packet.
2243    fn handle_neighbor_update(
2244        &mut self,
2245        bindings_ctx: &mut BC,
2246        device_id: &Self::DeviceId,
2247        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
2248        // disallow the address with all host bits equal to 0, and the
2249        // subnet broadcast addresses with all host bits equal to 1.
2250        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
2251        neighbor: SpecifiedAddr<I::Addr>,
2252        source: DynamicNeighborUpdateSource<D::Address>,
2253    );
2254
2255    /// Clears the neighbor table.
2256    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2257
2258    /// Send an IP packet to the neighbor.
2259    ///
2260    /// If the neighbor's link address is not known, link address resolution
2261    /// is performed.
2262    fn send_ip_packet_to_neighbor<S>(
2263        &mut self,
2264        bindings_ctx: &mut BC,
2265        device_id: &Self::DeviceId,
2266        neighbor: SpecifiedAddr<I::Addr>,
2267        body: S,
2268        meta: BC::TxMetadata,
2269    ) -> Result<(), SendFrameError<S>>
2270    where
2271        S: NetworkSerializer,
2272        S::Buffer: BufferMut;
2273}
2274
2275enum TransmitProbe<A> {
2276    Multicast,
2277    Unicast(A),
2278}
2279
2280impl<
2281    I: NudIcmpIpExt,
2282    D: LinkDevice,
2283    BC: NudBindingsContext<I, D, CC::DeviceId>,
2284    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2285> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2286{
2287    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2288        let Self { device_id, timer_type, _marker: PhantomData } = self;
2289        let Some(device_id) = device_id.upgrade() else {
2290            return;
2291        };
2292        match timer_type {
2293            NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2294            NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2295        }
2296    }
2297}
2298
2299fn handle_neighbor_timer<I, D, CC, BC>(
2300    core_ctx: &mut CC,
2301    bindings_ctx: &mut BC,
2302    device_id: CC::DeviceId,
2303) where
2304    I: NudIcmpIpExt,
2305    D: LinkDevice,
2306    BC: NudBindingsContext<I, D, CC::DeviceId>,
2307    CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2308{
2309    enum Action<L, A, M> {
2310        TransmitProbe { probe: TransmitProbe<L>, to: A },
2311        SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2312    }
2313    let action = core_ctx.with_nud_state_mut(
2314        &device_id,
2315        |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2316            let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2317            let num_entries = neighbors.len();
2318            let mut entry = match neighbors.entry(lookup_addr) {
2319                Entry::Occupied(entry) => entry,
2320                Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2321            };
2322
2323            match entry.get_mut() {
2324                NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2325                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2326
2327                    if incomplete.schedule_timer_if_should_retransmit(
2328                        core_ctx,
2329                        bindings_ctx,
2330                        timer_heap,
2331                        lookup_addr,
2332                    ) {
2333                        Some(Action::TransmitProbe {
2334                            probe: TransmitProbe::Multicast,
2335                            to: lookup_addr,
2336                        })
2337                    } else {
2338                        // Failed to complete neighbor resolution and no more probes to send.
2339                        // Subsequent traffic to this neighbor will recreate the entry and restart
2340                        // address resolution.
2341                        //
2342                        // TODO(https://fxbug.dev/42082448): consider retaining this neighbor entry in
2343                        // a sentinel `Failed` state, equivalent to its having been discarded except
2344                        // for debugging/observability purposes.
2345                        debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2346                        let Incomplete {
2347                            transmit_counter: _,
2348                            ref mut pending_frames,
2349                            notifiers: _,
2350                            _marker,
2351                        } = assert_matches!(
2352                            entry.remove(),
2353                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2354                                => incomplete
2355                        );
2356                        let pending_frames = core::mem::take(pending_frames);
2357                        bindings_ctx.on_event(Event::removed(
2358                            &device_id,
2359                            lookup_addr,
2360                            bindings_ctx.now(),
2361                        ));
2362                        Some(Action::SendIcmpDestUnreachable(pending_frames))
2363                    }
2364                }
2365                NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2366                    assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2367
2368                    let Probe { link_address, transmit_counter: _ } = probe;
2369                    let link_address = *link_address;
2370                    if probe.schedule_timer_if_should_retransmit(
2371                        core_ctx,
2372                        bindings_ctx,
2373                        timer_heap,
2374                        lookup_addr,
2375                    ) {
2376                        Some(Action::TransmitProbe {
2377                            probe: TransmitProbe::Unicast(link_address),
2378                            to: lookup_addr,
2379                        })
2380                    } else {
2381                        let unreachable =
2382                            probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2383                        *entry.get_mut() =
2384                            NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2385                        let event_state = entry.get_mut().to_event_state();
2386                        let event = Event::changed(
2387                            &device_id,
2388                            event_state,
2389                            lookup_addr,
2390                            bindings_ctx.now(),
2391                        );
2392                        bindings_ctx.on_event(event);
2393                        None
2394                    }
2395                }
2396                NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2397                    assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2398                    unreachable
2399                        .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2400                        .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2401                }
2402                NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2403                    link_address,
2404                    last_confirmed_at,
2405                })) => {
2406                    assert_eq!(event, NudEvent::ReachableTime);
2407                    let link_address = *link_address;
2408
2409                    let expiration =
2410                        last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2411                    if expiration > bindings_ctx.now() {
2412                        timer_heap.schedule_neighbor_at(
2413                            bindings_ctx,
2414                            expiration,
2415                            lookup_addr,
2416                            NudEvent::ReachableTime,
2417                        );
2418                    } else {
2419                        // Per [RFC 4861 section 7.3.3]:
2420                        //
2421                        //   When ReachableTime milliseconds have passed since receipt of the last
2422                        //   reachability confirmation for a neighbor, the Neighbor Cache entry's
2423                        //   state changes from REACHABLE to STALE.
2424                        //
2425                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2426                        *entry.get_mut() =
2427                            NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2428                                link_address,
2429                            }));
2430                        let event_state = entry.get_mut().to_event_state();
2431                        let event = Event::changed(
2432                            &device_id,
2433                            event_state,
2434                            lookup_addr,
2435                            bindings_ctx.now(),
2436                        );
2437                        bindings_ctx.on_event(event);
2438
2439                        // This entry is deemed discardable now that it is not in active use;
2440                        // schedule garbage collection for the neighbor table if we are currently
2441                        // over the maximum amount of entries.
2442                        timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2443                    }
2444
2445                    None
2446                }
2447                NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2448                    assert_eq!(event, NudEvent::DelayFirstProbe);
2449
2450                    // Per [RFC 4861 section 7.3.3]:
2451                    //
2452                    //   If the entry is still in the DELAY state when the timer expires, the
2453                    //   entry's state changes to PROBE.
2454                    //
2455                    // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2456                    let probe @ Probe { link_address, transmit_counter: _ } =
2457                        delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2458                    *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2459                    let event_state = entry.get_mut().to_event_state();
2460                    bindings_ctx.on_event(Event::changed(
2461                        &device_id,
2462                        event_state,
2463                        lookup_addr,
2464                        bindings_ctx.now(),
2465                    ));
2466
2467                    Some(Action::TransmitProbe {
2468                        probe: TransmitProbe::Unicast(link_address),
2469                        to: lookup_addr,
2470                    })
2471                }
2472                state @ (NeighborState::Static(_)
2473                | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2474                    panic!("timer unexpectedly fired in state {state:?}")
2475                }
2476            }
2477        },
2478    );
2479
2480    match action {
2481        Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2482            for (mut frame, meta) in pending_frames.drain(..) {
2483                // This frame is being dropped from the pending NUD queue, we
2484                // can release its tx metadata.
2485                core::mem::drop(meta);
2486
2487                // TODO(https://fxbug.dev/323585811): Avoid needing to parse the packet to get
2488                // IP header fields by extracting them from the serializer passed into the NUD
2489                // layer and storing them alongside the pending frames instead.
2490                let Some((packet, original_src_ip, original_dst_ip)) = frame
2491                    .parse_mut::<I::Packet<_>>()
2492                    .map_err(|e| {
2493                        warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2494                    })
2495                    .ok()
2496                    .and_then(|packet| {
2497                        let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2498                        let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2499                        Some((packet, original_src_ip, original_dst_ip))
2500                    })
2501                    .or_else(|| {
2502                        core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2503                        None
2504                    })
2505                else {
2506                    continue;
2507                };
2508                let header_metadata = I::extract_metadata(&packet);
2509                let header_len = packet.parse_metadata().header_len();
2510                let proto = packet.proto();
2511                let metadata = packet.parse_metadata();
2512                core::mem::drop(packet);
2513                frame.undo_parse(metadata);
2514                core_ctx.send_icmp_dest_unreachable(
2515                    bindings_ctx,
2516                    frame,
2517                    // Provide the device ID if `original_src_ip`, the address the ICMP error
2518                    // is destined for, is link-local. Note that if this address is link-local,
2519                    // it should be an address assigned to one of our own interfaces, because the
2520                    // link-local subnet should always be on-link according to RFC 5942 Section 3:
2521                    //
2522                    //   The link-local prefix is effectively considered a permanent entry on the
2523                    //   Prefix List.
2524                    //
2525                    // Even if the link-local subnet is off-link, passing the device ID is never
2526                    // incorrect because link-local traffic will never be forwarded, and
2527                    // there is only ever one link and thus interface involved.
2528                    original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2529                    original_src_ip,
2530                    original_dst_ip,
2531                    header_len,
2532                    proto,
2533                    header_metadata,
2534                );
2535            }
2536        }
2537        Some(Action::TransmitProbe { probe, to }) => {
2538            let remote_link_addr = match probe {
2539                TransmitProbe::Multicast => None,
2540                TransmitProbe::Unicast(link_addr) => Some(link_addr),
2541            };
2542            core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2543        }
2544        None => {}
2545    }
2546}
2547
2548impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2549    NudHandler<I, D, BC> for CC
2550{
2551    fn handle_neighbor_update(
2552        &mut self,
2553        bindings_ctx: &mut BC,
2554        device_id: &CC::DeviceId,
2555        neighbor: SpecifiedAddr<I::Addr>,
2556        source: DynamicNeighborUpdateSource<D::Address>,
2557    ) {
2558        debug!("received neighbor {:?} from {}", source, neighbor);
2559        self.with_nud_state_mut_and_sender_ctx(
2560            device_id,
2561            |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2562                let num_entries = neighbors.len();
2563                match neighbors.entry(neighbor) {
2564                    Entry::Vacant(e) => match source {
2565                        DynamicNeighborUpdateSource::Probe { link_address } => {
2566                            // Per [RFC 4861 section 7.2.3] ("Receipt of Neighbor Solicitations"):
2567                            //
2568                            //   If an entry does not already exist, the node SHOULD create a new
2569                            //   one and set its reachability state to STALE as specified in Section
2570                            //   7.3.3.
2571                            //
2572                            // [RFC 4861 section 7.2.3]: https://tools.ietf.org/html/rfc4861#section-7.2.3
2573                            insert_new_entry(
2574                                bindings_ctx,
2575                                device_id,
2576                                e,
2577                                NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2578                                    link_address,
2579                                })),
2580                            );
2581
2582                            // This entry is not currently in active use; if we are currently over
2583                            // the maximum amount of entries, schedule garbage collection.
2584                            timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2585                        }
2586                        // Per [RFC 4861 section 7.2.5] ("Receipt of Neighbor Advertisements"):
2587                        //
2588                        //   If no entry exists, the advertisement SHOULD be silently discarded.
2589                        //   There is no need to create an entry if none exists, since the
2590                        //   recipient has apparently not initiated any communication with the
2591                        //   target.
2592                        //
2593                        // [RFC 4861 section 7.2.5]: https://tools.ietf.org/html/rfc4861#section-7.2.5
2594                        DynamicNeighborUpdateSource::Confirmation { .. } => {}
2595                    },
2596                    Entry::Occupied(e) => match e.into_mut() {
2597                        NeighborState::Dynamic(e) => match source {
2598                            DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2599                                core_ctx,
2600                                bindings_ctx,
2601                                timer_heap,
2602                                device_id,
2603                                neighbor,
2604                                link_address,
2605                                num_entries,
2606                                last_gc,
2607                            ),
2608                            DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2609                                .handle_confirmation(
2610                                    core_ctx,
2611                                    bindings_ctx,
2612                                    timer_heap,
2613                                    device_id,
2614                                    neighbor,
2615                                    link_address,
2616                                    flags,
2617                                    num_entries,
2618                                    last_gc,
2619                                ),
2620                        },
2621                        NeighborState::Static(_) => {}
2622                    },
2623                }
2624            },
2625        );
2626    }
2627
2628    fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2629        self.with_nud_state_mut(
2630            device_id,
2631            |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2632                neighbors.drain().for_each(|(neighbor, state)| {
2633                    match state {
2634                        NeighborState::Dynamic(mut entry) => {
2635                            entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2636                        }
2637                        NeighborState::Static(_) => {}
2638                    }
2639                    bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2640                });
2641            },
2642        );
2643    }
2644
2645    fn send_ip_packet_to_neighbor<S>(
2646        &mut self,
2647        bindings_ctx: &mut BC,
2648        device_id: &Self::DeviceId,
2649        lookup_addr: SpecifiedAddr<I::Addr>,
2650        body: S,
2651        meta: BC::TxMetadata,
2652    ) -> Result<(), SendFrameError<S>>
2653    where
2654        S: NetworkSerializer,
2655        S::Buffer: BufferMut,
2656    {
2657        let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2658            device_id,
2659            |state, core_ctx| -> Result<_, SendFrameError<S>> {
2660                let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2661                match entry {
2662                    Entry::Vacant(e) => {
2663                        let incomplete = Incomplete::new_with_packet(
2664                            core_ctx,
2665                            bindings_ctx,
2666                            timer_heap,
2667                            lookup_addr,
2668                            body,
2669                            meta,
2670                        )
2671                        .map_err(|e| e.err_into())?;
2672                        insert_new_entry(
2673                            bindings_ctx,
2674                            device_id,
2675                            e,
2676                            NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2677                        );
2678                        Ok(true)
2679                    }
2680                    Entry::Occupied(e) => {
2681                        match e.into_mut() {
2682                            NeighborState::Static(link_address) => {
2683                                // Send the IP packet while holding the NUD lock to prevent a
2684                                // potential ordering violation.
2685                                //
2686                                // If we drop the NUD lock before sending out this packet, another
2687                                // thread could take the NUD lock and send a packet *before* this
2688                                // packet is sent out, resulting in out-of-order transmission to the
2689                                // device.
2690                                core_ctx.send_ip_packet_to_neighbor_link_addr(
2691                                    bindings_ctx,
2692                                    *link_address,
2693                                    body,
2694                                    meta,
2695                                )?;
2696
2697                                Ok(false)
2698                            }
2699                            NeighborState::Dynamic(e) => {
2700                                let do_multicast_solicit = e.handle_packet_queued_to_send(
2701                                    core_ctx,
2702                                    bindings_ctx,
2703                                    timer_heap,
2704                                    device_id,
2705                                    lookup_addr,
2706                                    body,
2707                                    meta,
2708                                )?;
2709
2710                                Ok(do_multicast_solicit)
2711                            }
2712                        }
2713                    }
2714                }
2715            },
2716        )?;
2717
2718        if do_multicast_solicit {
2719            self.send_neighbor_solicitation(
2720                bindings_ctx,
2721                &device_id,
2722                lookup_addr,
2723                /* multicast */ None,
2724            );
2725        }
2726
2727        Ok(())
2728    }
2729}
2730
2731fn insert_new_entry<
2732    I: Ip,
2733    D: LinkDevice,
2734    DeviceId: DeviceIdentifier,
2735    BC: NudBindingsContext<I, D, DeviceId>,
2736>(
2737    bindings_ctx: &mut BC,
2738    device_id: &DeviceId,
2739    vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2740    entry: NeighborState<D, BC>,
2741) {
2742    let lookup_addr = *vacant.key();
2743    let state = vacant.insert(entry);
2744    let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2745    bindings_ctx.on_event(event);
2746}
2747
2748/// Confirm upper-layer forward reachability to the specified neighbor through
2749/// the specified device.
2750pub fn confirm_reachable<I, D, CC, BC>(
2751    core_ctx: &mut CC,
2752    bindings_ctx: &mut BC,
2753    device_id: &CC::DeviceId,
2754    neighbor: SpecifiedAddr<I::Addr>,
2755) where
2756    I: Ip,
2757    D: LinkDevice,
2758    BC: NudBindingsContext<I, D, CC::DeviceId>,
2759    CC: NudContext<I, D, BC>,
2760{
2761    core_ctx.with_nud_state_mut_and_sender_ctx(
2762        device_id,
2763        |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2764            match neighbors.entry(neighbor) {
2765                Entry::Vacant(_) => {
2766                    debug!(
2767                        "got an upper-layer confirmation for non-existent neighbor entry {}",
2768                        neighbor
2769                    );
2770                }
2771                Entry::Occupied(e) => match e.into_mut() {
2772                    NeighborState::Static(_) => {}
2773                    NeighborState::Dynamic(e) => {
2774                        // Per [RFC 4861 section 7.3.3]:
2775                        //
2776                        //   When a reachability confirmation is received (either through upper-
2777                        //   layer advice or a solicited Neighbor Advertisement), an entry's state
2778                        //   changes to REACHABLE.  The one exception is that upper-layer advice has
2779                        //   no effect on entries in the INCOMPLETE state (e.g., for which no link-
2780                        //   layer address is cached).
2781                        //
2782                        // [RFC 4861 section 7.3.3]: https://tools.ietf.org/html/rfc4861#section-7.3.3
2783                        let link_address = match e {
2784                            DynamicNeighborState::Incomplete(_) => return,
2785                            DynamicNeighborState::Reachable(Reachable {
2786                                link_address,
2787                                last_confirmed_at: _,
2788                            })
2789                            | DynamicNeighborState::Stale(Stale { link_address })
2790                            | DynamicNeighborState::Delay(Delay { link_address })
2791                            | DynamicNeighborState::Probe(Probe {
2792                                link_address,
2793                                transmit_counter: _,
2794                            })
2795                            | DynamicNeighborState::Unreachable(Unreachable {
2796                                link_address,
2797                                mode: _,
2798                            }) => *link_address,
2799                        };
2800                        e.enter_reachable(
2801                            core_ctx,
2802                            bindings_ctx,
2803                            timer_heap,
2804                            device_id,
2805                            neighbor,
2806                            link_address,
2807                        );
2808                    }
2809                },
2810            }
2811        },
2812    );
2813}
2814
2815/// Performs a linear scan of the neighbor table, discarding enough entries to
2816/// bring the total size under `MAX_ENTRIES` if possible.
2817///
2818/// Static neighbor entries are never discarded, nor are any entries that are
2819/// considered to be in use, which is defined as an entry in REACHABLE,
2820/// INCOMPLETE, DELAY, or PROBE. In other words, the only entries eligible to be
2821/// discarded are those in STALE or UNREACHABLE. This is reasonable because all
2822/// other states represent entries to which we have either recently sent packets
2823/// (REACHABLE, DELAY, PROBE), or which we are actively trying to resolve and
2824/// for which we have recently queued outgoing packets (INCOMPLETE).
2825fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2826where
2827    I: Ip,
2828    D: LinkDevice,
2829    BC: NudBindingsContext<I, D, CC::DeviceId>,
2830    CC: NudContext<I, D, BC>,
2831{
2832    core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2833        let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2834        if max_to_remove == 0 {
2835            return;
2836        }
2837
2838        *last_gc = Some(bindings_ctx.now());
2839
2840        // Define an ordering by priority for garbage collection, such that lower
2841        // numbers correspond to higher usefulness and therefore lower likelihood of
2842        // being discarded.
2843        //
2844        // TODO(https://fxbug.dev/42075782): once neighbor entries hold a timestamp
2845        // tracking when they were last updated, consider using this timestamp to break
2846        // ties between entries in the same state, so that we discard less recently
2847        // updated entries before more recently updated ones.
2848        fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2849            state: &DynamicNeighborState<D, BT>,
2850        ) -> usize {
2851            match state {
2852                DynamicNeighborState::Incomplete(_)
2853                | DynamicNeighborState::Reachable(_)
2854                | DynamicNeighborState::Delay(_)
2855                | DynamicNeighborState::Probe(_) => unreachable!(
2856                    "the netstack should only ever discard STALE or UNREACHABLE entries; \
2857                        found {:?}",
2858                    state,
2859                ),
2860                DynamicNeighborState::Stale(_) => 0,
2861                DynamicNeighborState::Unreachable(Unreachable {
2862                    link_address: _,
2863                    mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2864                }) => 1,
2865                DynamicNeighborState::Unreachable(Unreachable {
2866                    link_address: _,
2867                    mode: UnreachableMode::WaitingForPacketSend,
2868                }) => 2,
2869            }
2870        }
2871
2872        struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2873            key: K,
2874            state: &'a mut DynamicNeighborState<D, BT>,
2875        }
2876
2877        impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2878            fn eq(&self, other: &Self) -> bool {
2879                self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2880            }
2881        }
2882        impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2883        impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2884            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2885                // Sort in reverse order so `BinaryHeap` will function as a min-heap rather than
2886                // a max-heap. This means it will maintain the minimum (i.e. most useful) entry
2887                // at the top of the heap.
2888                gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2889            }
2890        }
2891        impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2892            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2893                Some(self.cmp(&other))
2894            }
2895        }
2896
2897        let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2898        for (ip, neighbor) in neighbors.iter_mut() {
2899            match neighbor {
2900                NeighborState::Static(_) => {
2901                    // Don't discard static entries.
2902                    continue;
2903                }
2904                NeighborState::Dynamic(state) => {
2905                    match state {
2906                        DynamicNeighborState::Incomplete(_)
2907                        | DynamicNeighborState::Reachable(_)
2908                        | DynamicNeighborState::Delay(_)
2909                        | DynamicNeighborState::Probe(_) => {
2910                            // Don't discard in-use entries.
2911                            continue;
2912                        }
2913                        DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2914                            // Unconditionally insert the first `max_to_remove` entries.
2915                            if entries_to_remove.len() < max_to_remove {
2916                                entries_to_remove.push(SortEntry { key: ip, state });
2917                                continue;
2918                            }
2919                            // Check if this neighbor is greater than (i.e. less useful than) the
2920                            // minimum (i.e. most useful) entry that is currently set to be removed.
2921                            // If it is, replace that entry with this one.
2922                            let minimum = entries_to_remove
2923                                .peek()
2924                                .expect("heap should have at least 1 entry");
2925                            let candidate = SortEntry { key: ip, state };
2926                            if &candidate > minimum {
2927                                let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2928                                entries_to_remove.push(candidate);
2929                            }
2930                        }
2931                    }
2932                }
2933            }
2934        }
2935
2936        let entries_to_remove = entries_to_remove
2937            .into_iter()
2938            .map(|SortEntry { key: neighbor, state }| {
2939                state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2940                *neighbor
2941            })
2942            .collect::<Vec<_>>();
2943
2944        for neighbor in entries_to_remove {
2945            assert_matches!(neighbors.remove(&neighbor), Some(_));
2946            bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2947        }
2948    })
2949}
2950
2951#[cfg(test)]
2952mod tests {
2953    use alloc::vec;
2954
2955    use ip_test_macro::ip_test;
2956    use net_declare::{net_ip_v4, net_ip_v6};
2957    use net_types::ip::{Ipv4Addr, Ipv6Addr};
2958    use netstack3_base::testutil::{
2959        FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2960        FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2961    };
2962    use netstack3_base::{
2963        CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2964    };
2965    use netstack3_hashmap::HashSet;
2966    use test_case::test_case;
2967
2968    use super::*;
2969    use crate::internal::device::nud::api::NeighborApi;
2970    use packet::NestableSerializer as _;
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: NetworkSerializer,
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}