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