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