netstack3_ip/device/
dad.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//! Duplicate Address Detection.
6
7use core::fmt::Debug;
8use core::num::{NonZero, NonZeroU16};
9
10use arrayvec::ArrayVec;
11use derivative::Derivative;
12use log::debug;
13use net_types::ip::{Ip, IpVersionMarker, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
14use net_types::MulticastAddr;
15use netstack3_base::{
16    AnyDevice, CoreEventContext, CoreTimerContext, DeviceIdContext, EventContext, HandleableTimer,
17    IpAddressId as _, IpDeviceAddressIdContext, RngContext, StrongDeviceIdentifier as _,
18    TimerBindingsTypes, TimerContext, WeakDeviceIdentifier,
19};
20use packet_formats::icmp::ndp::options::{NdpNonce, MIN_NONCE_LENGTH};
21use packet_formats::icmp::ndp::NeighborSolicitation;
22use packet_formats::utils::NonZeroDuration;
23
24use crate::internal::device::nud::DEFAULT_MAX_MULTICAST_SOLICIT;
25use crate::internal::device::{IpAddressState, IpDeviceIpExt, WeakIpAddressId};
26
27/// An IP Extension trait supporting Duplicate Address Detection.
28pub trait DadIpExt: Ip {
29    /// Whether or not DAD should be performed by default of a newly installed
30    /// address.
31    const DEFAULT_DAD_ENABLED: bool;
32
33    /// Data that accompanies a sent DAD probe.
34    type SentProbeData: Debug;
35    /// Data that accompanies a received DAD probe.
36    type ReceivedProbeData<'a>;
37    /// State held for tentative addresses.
38    type TentativeState: Debug + Default + Send + Sync;
39    /// Metadata associated with the result of handling and incoming DAD probe.
40    type IncomingProbeResultMeta;
41
42    /// Generate data to accompany a sent probe from the tentative state.
43    fn generate_sent_probe_data<BC: RngContext>(
44        state: &mut Self::TentativeState,
45        addr: Self::Addr,
46        bindings_ctx: &mut BC,
47    ) -> Self::SentProbeData;
48}
49
50// TODO(https://fxbug.dev/42077260): Actually support DAD for IPv4.
51impl DadIpExt for Ipv4 {
52    /// In the context of IPv4 addresses, DAD refers to Address Conflict Detection
53    /// (ACD) as specified in RFC 5227.
54    ///
55    /// This value is set to false, which is out of compliance with RFC 5227. As per
56    /// section 2.1:
57    ///   Before beginning to use an IPv4 address (whether received from manual
58    ///   configuration, DHCP, or some other means), a host implementing this
59    ///   specification MUST test to see if the address is already in use.
60    ///
61    /// However, we believe that disabling DAD for IPv4 addresses by default is more
62    /// inline with industry expectations. For example, Linux does not implement
63    /// DAD for IPv4 addresses at all: applications that want to prevent duplicate
64    /// IPv4 addresses must implement the ACD specification themselves (e.g.
65    /// dhclient, a common DHCP client on Linux).
66    const DEFAULT_DAD_ENABLED: bool = false;
67
68    type SentProbeData = ();
69    type ReceivedProbeData<'a> = ();
70    type TentativeState = ();
71    type IncomingProbeResultMeta = ();
72
73    fn generate_sent_probe_data<BC: RngContext>(
74        _state: &mut (),
75        _addr: Ipv4Addr,
76        _bindings_ctx: &mut BC,
77    ) -> Self::SentProbeData {
78        ()
79    }
80}
81
82impl DadIpExt for Ipv6 {
83    /// True, as per RFC 4862, Section 5.4:
84    ///   Duplicate Address Detection MUST be performed on all unicast
85    ///   addresses prior to assigning them to an interface, regardless of
86    ///   whether they are obtained through stateless autoconfiguration,
87    ///   DHCPv6, or manual configuration.
88    const DEFAULT_DAD_ENABLED: bool = true;
89
90    type SentProbeData = Ipv6DadSentProbeData;
91    type ReceivedProbeData<'a> = Option<NdpNonce<&'a [u8]>>;
92    type TentativeState = Ipv6TentativeDadState;
93    type IncomingProbeResultMeta = Ipv6ProbeResultMetadata;
94
95    fn generate_sent_probe_data<BC: RngContext>(
96        state: &mut Ipv6TentativeDadState,
97        addr: Ipv6Addr,
98        bindings_ctx: &mut BC,
99    ) -> Self::SentProbeData {
100        let Ipv6TentativeDadState {
101            nonces,
102            added_extra_transmits_after_detecting_looped_back_ns: _,
103        } = state;
104        Ipv6DadSentProbeData {
105            dst_ip: addr.to_solicited_node_address(),
106            message: NeighborSolicitation::new(addr),
107            nonce: nonces.evicting_create_and_store_nonce(bindings_ctx.rng()),
108        }
109    }
110}
111
112/// The data needed to send an IPv6 DAD probe.
113#[derive(Debug)]
114pub struct Ipv6DadSentProbeData {
115    /// The destination address of the probe.
116    pub dst_ip: MulticastAddr<Ipv6Addr>,
117    /// The Neighbor Solicication to send.
118    pub message: NeighborSolicitation,
119    /// The nonce to accompany the neighbor solicitation.
120    pub nonce: OwnedNdpNonce,
121}
122
123/// A timer ID for duplicate address detection.
124#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
125pub struct DadTimerId<I: Ip, D: WeakDeviceIdentifier, A: WeakIpAddressId<I::Addr>> {
126    pub(crate) device_id: D,
127    pub(crate) addr: A,
128    _marker: IpVersionMarker<I>,
129}
130
131impl<I: Ip, D: WeakDeviceIdentifier, A: WeakIpAddressId<I::Addr>> DadTimerId<I, D, A> {
132    pub(super) fn device_id(&self) -> &D {
133        let Self { device_id, addr: _, _marker } = self;
134        device_id
135    }
136
137    /// Creates a new [`DadTimerId`]  for `device_id` and `addr`.
138    #[cfg(any(test, feature = "testutils"))]
139    pub fn new(device_id: D, addr: A) -> Self {
140        Self { device_id, addr, _marker: IpVersionMarker::new() }
141    }
142}
143
144/// A reference to the DAD address state.
145pub struct DadAddressStateRef<'a, I: DadIpExt, CC, BT: DadBindingsTypes> {
146    /// A mutable reference to an address' state.
147    pub dad_state: &'a mut DadState<I, BT>,
148    /// The execution context available with the address's DAD state.
149    pub core_ctx: &'a mut CC,
150}
151
152/// Holds references to state associated with duplicate address detection.
153pub struct DadStateRef<'a, I: DadIpExt, CC, BT: DadBindingsTypes> {
154    /// A reference to the DAD address state.
155    pub state: DadAddressStateRef<'a, I, CC, BT>,
156    /// The time between DAD message retransmissions.
157    pub retrans_timer: &'a NonZeroDuration,
158    /// The maximum number of DAD messages to send.
159    pub max_dad_transmits: &'a Option<NonZeroU16>,
160}
161
162/// The execution context while performing DAD.
163pub trait DadAddressContext<I: Ip, BC>: IpDeviceAddressIdContext<I> {
164    /// Calls the function with a mutable reference to the address's assigned
165    /// flag.
166    fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
167        &mut self,
168        device_id: &Self::DeviceId,
169        addr: &Self::AddressId,
170        cb: F,
171    ) -> O;
172
173    /// Returns whether or not DAD should be performed for the given address.
174    fn should_perform_dad(&mut self, device_id: &Self::DeviceId, addr: &Self::AddressId) -> bool;
175}
176
177/// Like [`DadAddressContext`], with additional IPv6 specific functionality.
178pub trait Ipv6DadAddressContext<BC>: DadAddressContext<Ipv6, BC> {
179    /// Joins the multicast group on the device.
180    fn join_multicast_group(
181        &mut self,
182        bindings_ctx: &mut BC,
183        device_id: &Self::DeviceId,
184        multicast_addr: MulticastAddr<Ipv6Addr>,
185    );
186
187    /// Leaves the multicast group on the device.
188    fn leave_multicast_group(
189        &mut self,
190        bindings_ctx: &mut BC,
191        device_id: &Self::DeviceId,
192        multicast_addr: MulticastAddr<Ipv6Addr>,
193    );
194}
195
196/// The execution context for DAD.
197pub trait DadContext<I: IpDeviceIpExt, BC: DadBindingsTypes>:
198    IpDeviceAddressIdContext<I>
199    + DeviceIdContext<AnyDevice>
200    + CoreTimerContext<DadTimerId<I, Self::WeakDeviceId, Self::WeakAddressId>, BC>
201    + CoreEventContext<DadEvent<I, Self::DeviceId>>
202{
203    /// The inner address context.
204    type DadAddressCtx<'a>: DadAddressContext<
205        I,
206        BC,
207        DeviceId = Self::DeviceId,
208        AddressId = Self::AddressId,
209    >;
210
211    /// Calls the function with the DAD state associated with the address.
212    fn with_dad_state<O, F: FnOnce(DadStateRef<'_, I, Self::DadAddressCtx<'_>, BC>) -> O>(
213        &mut self,
214        device_id: &Self::DeviceId,
215        addr: &Self::AddressId,
216        cb: F,
217    ) -> O;
218
219    /// Sends a DAD probe.
220    fn send_dad_probe(
221        &mut self,
222        bindings_ctx: &mut BC,
223        device_id: &Self::DeviceId,
224        data: I::SentProbeData,
225    );
226}
227
228/// The various states DAD may be in for an address.
229#[derive(Derivative)]
230#[derivative(Debug(bound = ""))]
231pub enum DadState<I: DadIpExt, BT: DadBindingsTypes> {
232    /// The address is assigned to an interface and can be considered bound to
233    /// it (all packets destined to the address will be accepted).
234    Assigned,
235
236    /// The address is considered unassigned to an interface for normal
237    /// operations, but has the intention of being assigned in the future (e.g.
238    /// once Duplicate Address Detection is completed).
239    ///
240    /// When `dad_transmits_remaining` is `None`, then no more DAD messages need
241    /// to be sent and DAD may be resolved.
242    #[allow(missing_docs)]
243    Tentative {
244        dad_transmits_remaining: Option<NonZeroU16>,
245        timer: BT::Timer,
246        ip_specific_state: I::TentativeState,
247    },
248
249    /// The address has not yet been initialized.
250    Uninitialized,
251}
252
253/// DAD state specific to tentative IPv6 addresses.
254#[derive(Debug, Default)]
255pub struct Ipv6TentativeDadState {
256    /// The collection of observed nonces.
257    ///
258    /// Used to detect looped back Neighbor Solicitations.
259    pub nonces: NonceCollection,
260    /// Initialized to false, and exists as a sentinel so that extra
261    /// transmits are added only after the first looped-back probe is
262    /// detected.
263    pub added_extra_transmits_after_detecting_looped_back_ns: bool,
264}
265
266// Chosen somewhat arbitrarily. It's unlikely we need to store many
267// previously-used nonces given that we'll probably only ever see the most
268// recently used nonce looped back at us.
269const MAX_DAD_PROBE_NONCES_STORED: usize = 4;
270
271/// Like [`NdpNonce`], but owns the underlying data.
272pub type OwnedNdpNonce = [u8; MIN_NONCE_LENGTH];
273
274/// A data structure storing a limited number of `NdpNonce`s.
275#[derive(Default, Debug)]
276pub struct NonceCollection {
277    nonces: ArrayVec<OwnedNdpNonce, MAX_DAD_PROBE_NONCES_STORED>,
278}
279
280impl NonceCollection {
281    /// Given an `rng` source, generates a new unique nonce and stores it,
282    /// deleting the oldest nonce if there is no space remaining.
283    pub fn evicting_create_and_store_nonce(&mut self, mut rng: impl rand::Rng) -> OwnedNdpNonce {
284        let Self { nonces } = self;
285        loop {
286            let nonce: OwnedNdpNonce = rng.gen();
287            if nonces.iter().any(|stored_nonce| stored_nonce == &nonce) {
288                continue;
289            }
290
291            if nonces.remaining_capacity() == 0 {
292                let _: OwnedNdpNonce = nonces.remove(0);
293            }
294            nonces.push(nonce.clone());
295            break nonce;
296        }
297    }
298
299    /// Checks if `nonce` is in the collection.
300    pub fn contains(&self, nonce: &[u8]) -> bool {
301        if nonce.len() != MIN_NONCE_LENGTH {
302            return false;
303        }
304
305        let Self { nonces } = self;
306        nonces.iter().any(|stored_nonce| stored_nonce == &nonce)
307    }
308}
309
310#[derive(Debug, Eq, Hash, PartialEq)]
311/// Events generated by duplicate address detection.
312pub enum DadEvent<I: IpDeviceIpExt, DeviceId> {
313    /// Duplicate address detection completed and the address is assigned.
314    AddressAssigned {
315        /// Device the address belongs to.
316        device: DeviceId,
317        /// The address that moved to the assigned state.
318        addr: I::AssignedWitness,
319    },
320}
321
322/// The bindings types for DAD.
323pub trait DadBindingsTypes: TimerBindingsTypes {}
324impl<BT> DadBindingsTypes for BT where BT: TimerBindingsTypes {}
325
326/// The bindings execution context for DAD.
327///
328/// The type parameter `E` is tied by [`DadContext`] so that [`DadEvent`] can be
329/// transformed into an event that is more meaningful to bindings.
330pub trait DadBindingsContext<E>:
331    DadBindingsTypes + TimerContext + EventContext<E> + RngContext
332{
333}
334impl<E, BC> DadBindingsContext<E> for BC where
335    BC: DadBindingsTypes + TimerContext + EventContext<E> + RngContext
336{
337}
338
339/// The result of handling an incoming DAD probe.
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum DadIncomingProbeResult<I: DadIpExt> {
342    /// The probe's address is not assigned to ourself.
343    Uninitialized,
344    /// The probe's address is tentatively assigned to ourself.
345    ///
346    /// Includes IP specific `meta` related to handling this probe.
347    Tentative { meta: I::IncomingProbeResultMeta },
348    /// The probe's address is assigned to ourself.
349    Assigned,
350}
351
352/// IPv6 specific metadata held by [`DadIncomingProbeResult`].
353#[derive(Debug, PartialEq)]
354pub struct Ipv6ProbeResultMetadata {
355    /// True if the incoming Neighbor Solicitation contained a nonce that
356    /// matched a nonce previously sent by ourself. This indicates that the
357    /// network looped back a Neighbor Solicitation from ourself.
358    pub(crate) matched_nonce: bool,
359}
360
361/// An implementation for Duplicate Address Detection.
362pub trait DadHandler<I: IpDeviceIpExt, BC>:
363    DeviceIdContext<AnyDevice> + IpDeviceAddressIdContext<I>
364{
365    /// Initializes the DAD state for the given device and address.
366    ///
367    /// If DAD is required, the return value holds a [`StartDad`] token that
368    /// can be used to start the DAD algorithm.
369    fn initialize_duplicate_address_detection<'a>(
370        &mut self,
371        bindings_ctx: &mut BC,
372        device_id: &'a Self::DeviceId,
373        addr: &'a Self::AddressId,
374    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId>;
375
376    /// Starts duplicate address detection.
377    ///
378    /// The provided [`StartDad`] token is proof that DAD is required for the
379    /// address & device.
380    fn start_duplicate_address_detection<'a>(
381        &mut self,
382        bindings_ctx: &mut BC,
383        start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
384    );
385
386    /// Stops duplicate address detection.
387    ///
388    /// Does nothing if DAD is not being performed on the address.
389    fn stop_duplicate_address_detection(
390        &mut self,
391        bindings_ctx: &mut BC,
392        device_id: &Self::DeviceId,
393        addr: &Self::AddressId,
394    );
395
396    /// Handles an incoming duplicate address detection probe.
397    ///
398    /// This probe is indicative of the sender (possibly ourselves, if the
399    /// probe was looped back) performing duplicate address detection.
400    ///
401    /// The returned state indicates the address' state on ourself.
402    fn handle_incoming_probe(
403        &mut self,
404        bindings_ctx: &mut BC,
405        device_id: &Self::DeviceId,
406        addr: &Self::AddressId,
407        data: I::ReceivedProbeData<'_>,
408    ) -> DadIncomingProbeResult<I>;
409}
410
411/// Indicates whether DAD is needed for a given address on a given device.
412#[derive(Debug)]
413pub enum NeedsDad<'a, A, D> {
414    No,
415    Yes(StartDad<'a, A, D>),
416}
417
418impl<'a, A, D> NeedsDad<'a, A, D> {
419    // Returns the address's current state, and whether DAD needs to be started.
420    pub(crate) fn into_address_state_and_start_dad(
421        self,
422    ) -> (IpAddressState, Option<StartDad<'a, A, D>>) {
423        match self {
424            // Addresses proceed directly to assigned when DAD is disabled.
425            NeedsDad::No => (IpAddressState::Assigned, None),
426            NeedsDad::Yes(start_dad) => (IpAddressState::Tentative, Some(start_dad)),
427        }
428    }
429}
430
431/// Signals that DAD is allowed to run for the given address & device.
432///
433/// Inner members are private to ensure the type can only be constructed in the
434/// current module, which ensures that duplicate address detection can only be
435/// started after having checked that it's necessary.
436#[derive(Debug)]
437pub struct StartDad<'a, A, D> {
438    address_id: &'a A,
439    device_id: &'a D,
440}
441
442/// Initializes the DAD state for the given device and address.
443fn initialize_duplicate_address_detection<
444    'a,
445    I: IpDeviceIpExt,
446    BC: DadBindingsContext<CC::OuterEvent>,
447    CC: DadContext<I, BC>,
448    F: FnOnce(&mut CC::DadAddressCtx<'_>, &mut BC, &CC::DeviceId, &CC::AddressId),
449>(
450    core_ctx: &mut CC,
451    bindings_ctx: &mut BC,
452    device_id: &'a CC::DeviceId,
453    addr: &'a CC::AddressId,
454    on_initialized_cb: F,
455) -> NeedsDad<'a, CC::AddressId, CC::DeviceId> {
456    core_ctx.with_dad_state(
457        device_id,
458        addr,
459        |DadStateRef { state, retrans_timer: _, max_dad_transmits }| {
460            let DadAddressStateRef { dad_state, core_ctx } = state;
461            let needs_dad = match (core_ctx.should_perform_dad(device_id, addr), max_dad_transmits)
462            {
463                // There are two mechanisms by which DAD may be disabled:
464                //   1) The address has opted out of DAD, or
465                //   2) the interface's `max_dad_transmits` is `None`.
466                // In either case, the address immediately enters `Assigned`.
467                (false, _) | (true, None) => {
468                    *dad_state = DadState::Assigned;
469                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
470                    NeedsDad::No
471                }
472                (true, Some(max_dad_transmits)) => {
473                    *dad_state = DadState::Tentative {
474                        dad_transmits_remaining: Some(*max_dad_transmits),
475                        timer: CC::new_timer(
476                            bindings_ctx,
477                            DadTimerId {
478                                device_id: device_id.downgrade(),
479                                addr: addr.downgrade(),
480                                _marker: IpVersionMarker::new(),
481                            },
482                        ),
483                        ip_specific_state: Default::default(),
484                    };
485                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
486                    NeedsDad::Yes(StartDad { device_id, address_id: addr })
487                }
488            };
489
490            // Run IP specific "on initialized" actions while holding the dad
491            // state lock.
492            on_initialized_cb(core_ctx, bindings_ctx, device_id, addr);
493
494            needs_dad
495        },
496    )
497}
498
499fn do_duplicate_address_detection<
500    I: IpDeviceIpExt,
501    BC: DadBindingsContext<CC::OuterEvent>,
502    CC: DadContext<I, BC>,
503>(
504    core_ctx: &mut CC,
505    bindings_ctx: &mut BC,
506    device_id: &CC::DeviceId,
507    addr: &CC::AddressId,
508) {
509    let should_send_probe = core_ctx.with_dad_state(
510        device_id,
511        addr,
512        |DadStateRef { state, retrans_timer, max_dad_transmits: _ }| {
513            let DadAddressStateRef { dad_state, core_ctx } = state;
514
515            let (remaining, timer, ip_specific_state) = match dad_state {
516                DadState::Tentative { dad_transmits_remaining, timer, ip_specific_state } => {
517                    (dad_transmits_remaining, timer, ip_specific_state)
518                }
519                DadState::Uninitialized | DadState::Assigned => {
520                    panic!("expected address to be tentative; addr={addr:?}")
521                }
522            };
523
524            match remaining {
525                None => {
526                    *dad_state = DadState::Assigned;
527                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
528                    CC::on_event(
529                        bindings_ctx,
530                        DadEvent::AddressAssigned {
531                            device: device_id.clone(),
532                            addr: addr.addr_sub().addr(),
533                        },
534                    );
535                    None
536                }
537                Some(non_zero_remaining) => {
538                    *remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
539
540                    // Delay sending subsequent DAD probes.
541                    //
542                    // For IPv4, per RFC 5227 Section 2.1.1
543                    //   each of these probe packets spaced randomly and
544                    //   uniformly, PROBE_MIN to PROBE_MAX seconds apart.
545                    //
546                    // And for IPv6, per RFC 4862 section 5.1,
547                    //   DupAddrDetectTransmits ...
548                    //      Autoconfiguration also assumes the presence of the variable
549                    //      RetransTimer as defined in [RFC4861]. For autoconfiguration
550                    //      purposes, RetransTimer specifies the delay between
551                    //      consecutive Neighbor Solicitation transmissions performed
552                    //      during Duplicate Address Detection (if
553                    //      DupAddrDetectTransmits is greater than 1), as well as the
554                    //      time a node waits after sending the last Neighbor
555                    //      Solicitation before ending the Duplicate Address Detection
556                    //      process.
557                    assert_eq!(
558                        bindings_ctx.schedule_timer(retrans_timer.get(), timer),
559                        None,
560                        "Unexpected DAD timer; addr={}, device_id={:?}",
561                        addr.addr(),
562                        device_id
563                    );
564                    debug!(
565                        "performing DAD for {}; {} tries left",
566                        addr.addr(),
567                        remaining.map_or(0, NonZeroU16::get)
568                    );
569                    Some(I::generate_sent_probe_data(
570                        ip_specific_state,
571                        addr.addr().addr(),
572                        bindings_ctx,
573                    ))
574                }
575            }
576        },
577    );
578
579    if let Some(probe_send_data) = should_send_probe {
580        core_ctx.send_dad_probe(bindings_ctx, device_id, probe_send_data);
581    }
582}
583
584/// Stop DAD for the given device and address.
585fn stop_duplicate_address_detection<
586    'a,
587    I: IpDeviceIpExt,
588    BC: DadBindingsContext<CC::OuterEvent>,
589    CC: DadContext<I, BC>,
590    F: FnOnce(&mut CC::DadAddressCtx<'_>, &mut BC, &CC::DeviceId, &CC::AddressId),
591>(
592    core_ctx: &mut CC,
593    bindings_ctx: &mut BC,
594    device_id: &'a CC::DeviceId,
595    addr: &'a CC::AddressId,
596    on_stopped_cb: F,
597) {
598    core_ctx.with_dad_state(
599        device_id,
600        addr,
601        |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
602            let DadAddressStateRef { dad_state, core_ctx } = state;
603
604            match dad_state {
605                DadState::Assigned => {}
606                DadState::Tentative { dad_transmits_remaining: _, timer, ip_specific_state: _ } => {
607                    // Generally we should have a timer installed in the
608                    // tentative state, but we could be racing with the
609                    // timer firing in bindings so we can't assert that it's
610                    // installed here.
611                    let _: Option<_> = bindings_ctx.cancel_timer(timer);
612                }
613                // No actions are needed to stop DAD from `Uninitialized`.
614                DadState::Uninitialized => return,
615            };
616
617            // Undo the work we did when starting/performing DAD by putting
618            // the address back into unassigned state.
619
620            *dad_state = DadState::Uninitialized;
621            core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
622
623            // Run IP specific "on stopped" actions while holding the dad state
624            // lock.
625            on_stopped_cb(core_ctx, bindings_ctx, device_id, addr)
626        },
627    )
628}
629
630// TODO(https://fxbug.dev/42077260): Actually support DAD for IPv4.
631impl<BC, CC> DadHandler<Ipv4, BC> for CC
632where
633    CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
634{
635    fn initialize_duplicate_address_detection<'a>(
636        &mut self,
637        _bindings_ctx: &mut BC,
638        _device_id: &'a Self::DeviceId,
639        _addr: &'a Self::AddressId,
640    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
641        NeedsDad::No
642    }
643
644    fn start_duplicate_address_detection<'a>(
645        &mut self,
646        _bindings_ctx: &mut BC,
647        _start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
648    ) {
649    }
650
651    fn stop_duplicate_address_detection(
652        &mut self,
653        _bindings_ctx: &mut BC,
654        _device_id: &Self::DeviceId,
655        _addr: &Self::AddressId,
656    ) {
657    }
658
659    fn handle_incoming_probe(
660        &mut self,
661        _bindings_ctx: &mut BC,
662        _device_id: &Self::DeviceId,
663        _addr: &Self::AddressId,
664        _data: (),
665    ) -> DadIncomingProbeResult<Ipv4> {
666        unimplemented!()
667    }
668}
669
670impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<Ipv6, BC>> DadHandler<Ipv6, BC> for CC
671where
672    for<'a> CC::DadAddressCtx<'a>: Ipv6DadAddressContext<BC>,
673{
674    fn initialize_duplicate_address_detection<'a>(
675        &mut self,
676        bindings_ctx: &mut BC,
677        device_id: &'a Self::DeviceId,
678        addr: &'a Self::AddressId,
679    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
680        initialize_duplicate_address_detection(
681            self,
682            bindings_ctx,
683            device_id,
684            addr,
685            /*on_initialized_cb */
686            |core_ctx, bindings_ctx, device_id, addr| {
687                // As per RFC 4862 section 5.4.2,
688                //
689                //   Before sending a Neighbor Solicitation, an interface MUST
690                //   join the all-nodes multicast address and the solicited-node
691                //   multicast address of the tentative address.
692                //
693                // Note that:
694                // * We join the all-nodes multicast address on interface
695                //   enable.
696                // * We join the solicited-node multicast address, even if the
697                //   address is skipping DAD (and therefore, the tentative
698                //   state).
699                // * We join the solicited-node multicast address *after*
700                //   initializing the address. If the address is tentative, it
701                //   won't be used as the source for any outgoing MLD message.
702                core_ctx.join_multicast_group(
703                    bindings_ctx,
704                    device_id,
705                    addr.addr().addr().to_solicited_node_address(),
706                );
707            },
708        )
709    }
710
711    fn start_duplicate_address_detection<'a>(
712        &mut self,
713        bindings_ctx: &mut BC,
714        start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
715    ) {
716        let StartDad { device_id, address_id } = start_dad;
717        do_duplicate_address_detection(self, bindings_ctx, device_id, address_id)
718    }
719
720    fn stop_duplicate_address_detection(
721        &mut self,
722        bindings_ctx: &mut BC,
723        device_id: &Self::DeviceId,
724        addr: &Self::AddressId,
725    ) {
726        stop_duplicate_address_detection(
727            self,
728            bindings_ctx,
729            device_id,
730            addr,
731            /*on_stopped_cb */
732            |core_ctx, bindings_ctx, device_id, addr| {
733                // Undo the steps taken when DAD was initialized and leave the
734                // solicited node multicast group. Note that we leave the
735                // solicited-node multicast address *after* stopping dad. The
736                // address will no longer be assigned and won't be used as the
737                // source for any outgoing MLD message.
738                core_ctx.leave_multicast_group(
739                    bindings_ctx,
740                    device_id,
741                    addr.addr().addr().to_solicited_node_address(),
742                );
743            },
744        )
745    }
746
747    /// Handles an incoming Neighbor Solicitation.
748    ///
749    /// Checks if the incoming nonce matches stored nonces in DAD state.
750    fn handle_incoming_probe(
751        &mut self,
752        _bindings_ctx: &mut BC,
753        device_id: &Self::DeviceId,
754        addr: &Self::AddressId,
755        data: Option<NdpNonce<&[u8]>>,
756    ) -> DadIncomingProbeResult<Ipv6> {
757        self.with_dad_state(
758            device_id,
759            addr,
760            |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
761                let DadAddressStateRef { dad_state, core_ctx: _ } = state;
762                match dad_state {
763                    DadState::Assigned => DadIncomingProbeResult::Assigned,
764                    DadState::Tentative {
765                        dad_transmits_remaining,
766                        timer: _,
767                        ip_specific_state:
768                            Ipv6TentativeDadState {
769                                nonces,
770                                added_extra_transmits_after_detecting_looped_back_ns,
771                            },
772                    } => {
773                        let matched_nonce =
774                            data.is_some_and(|nonce| nonces.contains(nonce.bytes()));
775                        if matched_nonce
776                            && !core::mem::replace(
777                                added_extra_transmits_after_detecting_looped_back_ns,
778                                true,
779                            )
780                        {
781                            // Detected a looped-back DAD neighbor solicitation.
782                            // Per RFC 7527, we should send MAX_MULTICAST_SOLICIT more DAD probes.
783                            *dad_transmits_remaining =
784                                Some(DEFAULT_MAX_MULTICAST_SOLICIT.saturating_add(
785                                    dad_transmits_remaining.map(NonZero::get).unwrap_or(0),
786                                ));
787                        }
788                        DadIncomingProbeResult::Tentative {
789                            meta: Ipv6ProbeResultMetadata { matched_nonce },
790                        }
791                    }
792
793                    DadState::Uninitialized => DadIncomingProbeResult::Uninitialized,
794                }
795            },
796        )
797    }
798}
799
800impl<I: IpDeviceIpExt, BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<I, BC>>
801    HandleableTimer<CC, BC> for DadTimerId<I, CC::WeakDeviceId, CC::WeakAddressId>
802{
803    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
804        let Self { device_id, addr, _marker } = self;
805        let Some(device_id) = device_id.upgrade() else {
806            return;
807        };
808        let Some(addr_id) = addr.upgrade() else {
809            return;
810        };
811        do_duplicate_address_detection(core_ctx, bindings_ctx, &device_id, &addr_id)
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use alloc::collections::hash_map::{Entry, HashMap};
818    use core::time::Duration;
819
820    use assert_matches::assert_matches;
821    use ip_test_macro::ip_test;
822    use net_types::ip::{AddrSubnet, IpAddress as _, Ipv4Addr};
823    use net_types::{NonMappedAddr, NonMulticastAddr, SpecifiedAddr, UnicastAddr, Witness as _};
824    use netstack3_base::testutil::{
825        FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakAddressId,
826        FakeWeakDeviceId,
827    };
828    use netstack3_base::{
829        AssignedAddrIpExt, CtxPair, InstantContext as _, Ipv4DeviceAddr, Ipv6DeviceAddr,
830        SendFrameContext as _, TimerHandler,
831    };
832    use packet::EmptyBuf;
833    use packet_formats::icmp::ndp::Options;
834    use test_case::test_case;
835
836    use super::*;
837
838    struct FakeDadAddressContext<I: IpDeviceIpExt> {
839        addr: I::AssignedWitness,
840        assigned: bool,
841        // NB: DAD only joins multicast groups for IPv6.
842        groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
843        should_perform_dad: bool,
844    }
845
846    trait TestDadIpExt: IpDeviceIpExt {
847        const DAD_ADDRESS: Self::AssignedWitness;
848    }
849
850    impl TestDadIpExt for Ipv4 {
851        const DAD_ADDRESS: Ipv4DeviceAddr = unsafe {
852            NonMulticastAddr::new_unchecked(NonMappedAddr::new_unchecked(
853                SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 1])),
854            ))
855        };
856    }
857
858    impl TestDadIpExt for Ipv6 {
859        const DAD_ADDRESS: Ipv6DeviceAddr = unsafe {
860            NonMappedAddr::new_unchecked(UnicastAddr::new_unchecked(Ipv6Addr::new([
861                0xa, 0, 0, 0, 0, 0, 0, 1,
862            ])))
863        };
864    }
865
866    impl<I: TestDadIpExt> Default for FakeDadAddressContext<I> {
867        fn default() -> Self {
868            Self {
869                addr: I::DAD_ADDRESS,
870                assigned: false,
871                groups: Default::default(),
872                should_perform_dad: true,
873            }
874        }
875    }
876
877    type FakeAddressCtxImpl<I> = FakeCoreCtx<FakeDadAddressContext<I>, (), FakeDeviceId>;
878
879    impl<I: IpDeviceIpExt> DadAddressContext<I, FakeBindingsCtxImpl<I>> for FakeAddressCtxImpl<I> {
880        fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
881            &mut self,
882            &FakeDeviceId: &Self::DeviceId,
883            request_addr: &Self::AddressId,
884            cb: F,
885        ) -> O {
886            let FakeDadAddressContext { addr, assigned, .. } = &mut self.state;
887            assert_eq!(request_addr.addr(), *addr);
888            cb(assigned)
889        }
890
891        fn should_perform_dad(
892            &mut self,
893            &FakeDeviceId: &Self::DeviceId,
894            request_addr: &Self::AddressId,
895        ) -> bool {
896            let FakeDadAddressContext { addr, should_perform_dad, .. } = &mut self.state;
897            assert_eq!(request_addr.addr(), *addr);
898            *should_perform_dad
899        }
900    }
901
902    impl Ipv6DadAddressContext<FakeBindingsCtxImpl<Ipv6>> for FakeAddressCtxImpl<Ipv6> {
903        fn join_multicast_group(
904            &mut self,
905            _bindings_ctx: &mut FakeBindingsCtxImpl<Ipv6>,
906            &FakeDeviceId: &Self::DeviceId,
907            multicast_addr: MulticastAddr<Ipv6Addr>,
908        ) {
909            *self.state.groups.entry(multicast_addr).or_default() += 1;
910        }
911
912        fn leave_multicast_group(
913            &mut self,
914            _bindings_ctx: &mut FakeBindingsCtxImpl<Ipv6>,
915            &FakeDeviceId: &Self::DeviceId,
916            multicast_addr: MulticastAddr<Ipv6Addr>,
917        ) {
918            match self.state.groups.entry(multicast_addr) {
919                Entry::Vacant(_) => {}
920                Entry::Occupied(mut e) => {
921                    let v = e.get_mut();
922                    const COUNT_BEFORE_REMOVE: usize = 1;
923                    if *v == COUNT_BEFORE_REMOVE {
924                        assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
925                    } else {
926                        *v -= 1
927                    }
928                }
929            }
930        }
931    }
932
933    struct FakeDadContext<I: IpDeviceIpExt> {
934        state: DadState<I, FakeBindingsCtxImpl<I>>,
935        retrans_timer: NonZeroDuration,
936        max_dad_transmits: Option<NonZeroU16>,
937        address_ctx: FakeAddressCtxImpl<I>,
938    }
939
940    type TestDadTimerId<I> = DadTimerId<
941        I,
942        FakeWeakDeviceId<FakeDeviceId>,
943        FakeWeakAddressId<AddrSubnet<<I as Ip>::Addr, <I as AssignedAddrIpExt>::AssignedWitness>>,
944    >;
945
946    type FakeBindingsCtxImpl<I> =
947        FakeBindingsCtx<TestDadTimerId<I>, DadEvent<I, FakeDeviceId>, (), ()>;
948
949    type FakeCoreCtxImpl<I> =
950        FakeCoreCtx<FakeDadContext<I>, <I as DadIpExt>::SentProbeData, FakeDeviceId>;
951
952    fn get_address_id<I: IpDeviceIpExt>(
953        addr: I::AssignedWitness,
954    ) -> AddrSubnet<I::Addr, I::AssignedWitness> {
955        AddrSubnet::from_witness(addr, I::Addr::BYTES * 8).unwrap()
956    }
957
958    impl<I: IpDeviceIpExt> CoreTimerContext<TestDadTimerId<I>, FakeBindingsCtxImpl<I>>
959        for FakeCoreCtxImpl<I>
960    {
961        fn convert_timer(dispatch_id: TestDadTimerId<I>) -> TestDadTimerId<I> {
962            dispatch_id
963        }
964    }
965
966    impl<I: IpDeviceIpExt> CoreEventContext<DadEvent<I, FakeDeviceId>> for FakeCoreCtxImpl<I> {
967        type OuterEvent = DadEvent<I, FakeDeviceId>;
968        fn convert_event(event: DadEvent<I, FakeDeviceId>) -> DadEvent<I, FakeDeviceId> {
969            event
970        }
971    }
972
973    impl<I: IpDeviceIpExt> DadContext<I, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
974        type DadAddressCtx<'a> = FakeAddressCtxImpl<I>;
975
976        fn with_dad_state<
977            O,
978            F: FnOnce(DadStateRef<'_, I, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl<I>>) -> O,
979        >(
980            &mut self,
981            &FakeDeviceId: &FakeDeviceId,
982            request_addr: &Self::AddressId,
983            cb: F,
984        ) -> O {
985            let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
986                &mut self.state;
987            let ctx_addr = address_ctx.state.addr;
988            let requested_addr = request_addr.addr();
989            assert!(
990                ctx_addr == requested_addr,
991                "invalid address {requested_addr} expected {ctx_addr}"
992            );
993            cb(DadStateRef {
994                state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
995                retrans_timer,
996                max_dad_transmits,
997            })
998        }
999
1000        fn send_dad_probe(
1001            &mut self,
1002            bindings_ctx: &mut FakeBindingsCtxImpl<I>,
1003            &FakeDeviceId: &FakeDeviceId,
1004            data: I::SentProbeData,
1005        ) {
1006            self.send_frame(bindings_ctx, data, EmptyBuf).unwrap()
1007        }
1008    }
1009
1010    const RETRANS_TIMER: NonZeroDuration = NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1011
1012    type FakeCtx<I> = CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>>;
1013
1014    #[ip_test(I)]
1015    #[should_panic(expected = "expected address to be tentative")]
1016    fn panic_non_tentative_address_handle_timer<I: TestDadIpExt>() {
1017        let FakeCtx::<I> { mut core_ctx, mut bindings_ctx } =
1018            FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1019                state: DadState::Assigned,
1020                retrans_timer: RETRANS_TIMER,
1021                max_dad_transmits: None,
1022                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1023            }));
1024        TimerHandler::handle_timer(
1025            &mut core_ctx,
1026            &mut bindings_ctx,
1027            dad_timer_id(),
1028            Default::default(),
1029        );
1030    }
1031
1032    // TODO(https://fxbug.dev/42077260): Run this test against IPv4.
1033    #[test]
1034    fn dad_disabled() {
1035        let FakeCtx { mut core_ctx, mut bindings_ctx } =
1036            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1037                FakeCoreCtxImpl::with_state(FakeDadContext {
1038                    state: DadState::Tentative {
1039                        dad_transmits_remaining: None,
1040                        timer: bindings_ctx.new_timer(dad_timer_id()),
1041                        ip_specific_state: Default::default(),
1042                    },
1043                    retrans_timer: RETRANS_TIMER,
1044                    max_dad_transmits: None,
1045                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1046                })
1047            });
1048        let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1049        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1050            &mut core_ctx,
1051            &mut bindings_ctx,
1052            &FakeDeviceId,
1053            &address_id,
1054        );
1055        assert_matches!(start_dad, NeedsDad::No);
1056        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1057        assert_matches!(*state, DadState::Assigned);
1058        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1059        assert!(*assigned);
1060        assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1061        assert_eq!(bindings_ctx.take_events(), &[][..]);
1062    }
1063
1064    fn dad_timer_id<I: TestDadIpExt>() -> TestDadTimerId<I> {
1065        DadTimerId {
1066            addr: FakeWeakAddressId(get_address_id::<I>(I::DAD_ADDRESS)),
1067            device_id: FakeWeakDeviceId(FakeDeviceId),
1068            _marker: IpVersionMarker::new(),
1069        }
1070    }
1071
1072    fn check_dad(
1073        core_ctx: &FakeCoreCtxImpl<Ipv6>,
1074        bindings_ctx: &FakeBindingsCtxImpl<Ipv6>,
1075        frames_len: usize,
1076        dad_transmits_remaining: Option<NonZeroU16>,
1077        retrans_timer: NonZeroDuration,
1078    ) {
1079        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1080        let nonces = assert_matches!(state, DadState::Tentative {
1081            dad_transmits_remaining: got,
1082            timer: _,
1083            ip_specific_state: Ipv6TentativeDadState {
1084                nonces,
1085                added_extra_transmits_after_detecting_looped_back_ns: _,
1086            },
1087        } => {
1088            assert_eq!(
1089                *got,
1090                dad_transmits_remaining,
1091                "got dad_transmits_remaining = {got:?}, \
1092                 want dad_transmits_remaining = {dad_transmits_remaining:?}");
1093            nonces
1094        });
1095        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1096        assert!(!*assigned);
1097        assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1098
1099        let frames = core_ctx.frames();
1100        assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
1101        let (Ipv6DadSentProbeData { dst_ip, message, nonce }, frame) =
1102            frames.last().expect("should have transmitted a frame");
1103        assert_eq!(*dst_ip, Ipv6::DAD_ADDRESS.to_solicited_node_address());
1104        assert_eq!(*message, NeighborSolicitation::new(Ipv6::DAD_ADDRESS.get()));
1105        assert!(nonces.contains(nonce), "should have stored nonce");
1106
1107        let options = Options::parse(&frame[..]).expect("parse NDP options");
1108        assert_eq!(options.iter().count(), 0);
1109        bindings_ctx
1110            .timers
1111            .assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
1112    }
1113
1114    // TODO(https://fxbug.dev/42077260): Run this test against IPv4.
1115    #[test]
1116    fn perform_dad() {
1117        const DAD_TRANSMITS_REQUIRED: u16 = 5;
1118        const RETRANS_TIMER: NonZeroDuration =
1119            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1120
1121        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1122            FakeCoreCtxImpl::with_state(FakeDadContext {
1123                state: DadState::Tentative {
1124                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1125                    timer: bindings_ctx.new_timer(dad_timer_id()),
1126                    ip_specific_state: Default::default(),
1127                },
1128                retrans_timer: RETRANS_TIMER,
1129                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1130                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1131            })
1132        });
1133        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1134        let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1135        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1136            core_ctx,
1137            bindings_ctx,
1138            &FakeDeviceId,
1139            &address_id,
1140        );
1141        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1142        DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1143
1144        for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
1145            check_dad(
1146                core_ctx,
1147                bindings_ctx,
1148                usize::from(count + 1),
1149                NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
1150                RETRANS_TIMER,
1151            );
1152            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1153        }
1154        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1155        assert_matches!(*state, DadState::Assigned);
1156        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1157        assert!(*assigned);
1158        assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1159        assert_eq!(
1160            bindings_ctx.take_events(),
1161            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: Ipv6::DAD_ADDRESS }][..]
1162        );
1163    }
1164
1165    // TODO(https://fxbug.dev/42077260): Run this test against IPv4.
1166    #[test]
1167    fn stop_dad() {
1168        const DAD_TRANSMITS_REQUIRED: u16 = 2;
1169        const RETRANS_TIMER: NonZeroDuration =
1170            NonZeroDuration::new(Duration::from_secs(2)).unwrap();
1171
1172        let FakeCtx { mut core_ctx, mut bindings_ctx } =
1173            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1174                FakeCoreCtxImpl::with_state(FakeDadContext {
1175                    state: DadState::Tentative {
1176                        dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1177                        timer: bindings_ctx.new_timer(dad_timer_id()),
1178                        ip_specific_state: Default::default(),
1179                    },
1180                    retrans_timer: RETRANS_TIMER,
1181                    max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1182                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1183                })
1184            });
1185        let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1186        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1187            &mut core_ctx,
1188            &mut bindings_ctx,
1189            &FakeDeviceId,
1190            &address_id,
1191        );
1192        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1193        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
1194            &mut core_ctx,
1195            &mut bindings_ctx,
1196            token,
1197        );
1198
1199        check_dad(
1200            &core_ctx,
1201            &bindings_ctx,
1202            1,
1203            NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
1204            RETRANS_TIMER,
1205        );
1206
1207        DadHandler::<Ipv6, _>::stop_duplicate_address_detection(
1208            &mut core_ctx,
1209            &mut bindings_ctx,
1210            &FakeDeviceId,
1211            &get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS),
1212        );
1213        bindings_ctx.timers.assert_no_timers_installed();
1214        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1215        assert_matches!(*state, DadState::Uninitialized);
1216        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1217        assert!(!*assigned);
1218        assert_eq!(groups, &HashMap::new());
1219    }
1220
1221    // TODO(https://fxbug.dev/42077260): Ensure similar test coverage for IPv4.
1222    #[test_case(true, None ; "assigned with no incoming nonce")]
1223    #[test_case(true, Some([1u8; MIN_NONCE_LENGTH]) ; "assigned with incoming nonce")]
1224    #[test_case(false, None ; "uninitialized with no incoming nonce")]
1225    #[test_case(false, Some([1u8; MIN_NONCE_LENGTH]) ; "uninitialized with incoming nonce")]
1226    fn handle_incoming_dad_neighbor_solicitation_while_not_tentative(
1227        assigned: bool,
1228        nonce: Option<OwnedNdpNonce>,
1229    ) {
1230        const MAX_DAD_TRANSMITS: u16 = 1;
1231        const RETRANS_TIMER: NonZeroDuration =
1232            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1233
1234        let mut ctx = FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1235            state: if assigned { DadState::Assigned } else { DadState::Uninitialized },
1236            retrans_timer: RETRANS_TIMER,
1237            max_dad_transmits: NonZeroU16::new(MAX_DAD_TRANSMITS),
1238            address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1239        }));
1240        let addr = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1241
1242        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1243
1244        let want_lookup_result = if assigned {
1245            DadIncomingProbeResult::Assigned
1246        } else {
1247            DadIncomingProbeResult::Uninitialized
1248        };
1249
1250        assert_eq!(
1251            DadHandler::<Ipv6, _>::handle_incoming_probe(
1252                core_ctx,
1253                bindings_ctx,
1254                &FakeDeviceId,
1255                &addr,
1256                nonce.as_ref().map(NdpNonce::from),
1257            ),
1258            want_lookup_result
1259        );
1260    }
1261
1262    // TODO(https://fxbug.dev/42077260): Ensure similar test coverage for IPv4.
1263    #[test_case(true ; "discards looped back NS")]
1264    #[test_case(false ; "acts on non-looped-back NS")]
1265    fn handle_incoming_dad_neighbor_solicitation_during_tentative(looped_back: bool) {
1266        const DAD_TRANSMITS_REQUIRED: u16 = 1;
1267        const RETRANS_TIMER: NonZeroDuration =
1268            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1269
1270        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1271            FakeCoreCtxImpl::with_state(FakeDadContext {
1272                state: DadState::Tentative {
1273                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1274                    timer: bindings_ctx.new_timer(dad_timer_id()),
1275                    ip_specific_state: Default::default(),
1276                },
1277                retrans_timer: RETRANS_TIMER,
1278                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1279                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1280            })
1281        });
1282        let addr = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1283
1284        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1285        let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1286        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1287            core_ctx,
1288            bindings_ctx,
1289            &FakeDeviceId,
1290            &address_id,
1291        );
1292        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1293        DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1294
1295        check_dad(core_ctx, bindings_ctx, 1, None, RETRANS_TIMER);
1296
1297        let sent_nonce: OwnedNdpNonce = {
1298            let (Ipv6DadSentProbeData { dst_ip: _, message: _, nonce }, _frame) =
1299                core_ctx.frames().last().expect("should have transmitted a frame");
1300            *nonce
1301        };
1302
1303        let alternative_nonce = {
1304            let mut nonce = sent_nonce.clone();
1305            nonce[0] = nonce[0].wrapping_add(1);
1306            nonce
1307        };
1308
1309        let incoming_nonce =
1310            NdpNonce::from(if looped_back { &sent_nonce } else { &alternative_nonce });
1311
1312        let matched_nonce = assert_matches!(
1313            DadHandler::<Ipv6, _>::handle_incoming_probe(
1314                core_ctx,
1315                bindings_ctx,
1316                &FakeDeviceId,
1317                &addr,
1318                Some(incoming_nonce),
1319            ),
1320            DadIncomingProbeResult::Tentative {
1321                meta: Ipv6ProbeResultMetadata {matched_nonce}
1322            } => matched_nonce
1323        );
1324
1325        assert_eq!(matched_nonce, looped_back);
1326
1327        let frames_len_before_extra_transmits = core_ctx.frames().len();
1328        assert_eq!(frames_len_before_extra_transmits, 1);
1329
1330        let extra_dad_transmits_required =
1331            NonZero::new(if looped_back { DEFAULT_MAX_MULTICAST_SOLICIT.get() } else { 0 });
1332
1333        let (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns) = assert_matches!(
1334            &core_ctx.state.state,
1335            DadState::Tentative {
1336                dad_transmits_remaining,
1337                timer: _,
1338                ip_specific_state: Ipv6TentativeDadState {
1339                    nonces: _,
1340                    added_extra_transmits_after_detecting_looped_back_ns
1341                },
1342            } => (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns),
1343            "DAD state should be Tentative"
1344        );
1345
1346        assert_eq!(dad_transmits_remaining, &extra_dad_transmits_required);
1347        assert_eq!(added_extra_transmits_after_detecting_looped_back_ns, &matched_nonce);
1348
1349        let extra_dad_transmits_required =
1350            extra_dad_transmits_required.map(|n| n.get()).unwrap_or(0);
1351
1352        // The retransmit timer should have been kicked when we observed the matching nonce.
1353        assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1354
1355        // Even though we originally required only 1 DAD transmit, MAX_MULTICAST_SOLICIT more
1356        // should be required as a result of the looped back solicitation.
1357        for count in 0..extra_dad_transmits_required {
1358            check_dad(
1359                core_ctx,
1360                bindings_ctx,
1361                usize::from(count) + frames_len_before_extra_transmits + 1,
1362                NonZeroU16::new(extra_dad_transmits_required - count - 1),
1363                RETRANS_TIMER,
1364            );
1365            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1366        }
1367        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1368        assert_matches!(*state, DadState::Assigned);
1369        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1370        assert!(*assigned);
1371        assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1372        assert_eq!(
1373            bindings_ctx.take_events(),
1374            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: Ipv6::DAD_ADDRESS }][..]
1375        );
1376    }
1377}