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