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