Skip to main content

netstack3_ip/device/
nud.rs

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