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::num::{NonZero, NonZeroU16};
8
9use arrayvec::ArrayVec;
10use log::debug;
11use net_types::ip::{Ipv4, Ipv6, Ipv6Addr};
12use net_types::{MulticastAddr, UnicastAddr, Witness as _};
13use netstack3_base::{
14    AnyDevice, CoreEventContext, CoreTimerContext, DeviceIdContext, EventContext, HandleableTimer,
15    IpAddressId as _, IpDeviceAddressIdContext, RngContext, StrongDeviceIdentifier as _,
16    TimerBindingsTypes, TimerContext, WeakDeviceIdentifier,
17};
18use packet_formats::icmp::ndp::options::{NdpNonce, MIN_NONCE_LENGTH};
19use packet_formats::icmp::ndp::NeighborSolicitation;
20use packet_formats::utils::NonZeroDuration;
21
22use crate::internal::device::nud::DEFAULT_MAX_MULTICAST_SOLICIT;
23use crate::internal::device::state::Ipv6DadState;
24use crate::internal::device::{IpAddressState, IpDeviceIpExt, WeakIpAddressId};
25
26/// A timer ID for duplicate address detection.
27#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
28pub struct DadTimerId<D: WeakDeviceIdentifier, A: WeakIpAddressId<Ipv6Addr>> {
29    pub(crate) device_id: D,
30    pub(crate) addr: A,
31}
32
33impl<D: WeakDeviceIdentifier, A: WeakIpAddressId<Ipv6Addr>> DadTimerId<D, A> {
34    pub(super) fn device_id(&self) -> &D {
35        let Self { device_id, addr: _ } = self;
36        device_id
37    }
38
39    /// Creates a new [`DadTimerId`]  for `device_id` and `addr`.
40    #[cfg(any(test, feature = "testutils"))]
41    pub fn new(device_id: D, addr: A) -> Self {
42        Self { device_id, addr }
43    }
44}
45
46/// A reference to the DAD address state.
47pub struct DadAddressStateRef<'a, CC, BT: DadBindingsTypes> {
48    /// A mutable reference to an address' state.
49    ///
50    /// `None` if the address is not recognized.
51    pub dad_state: &'a mut Ipv6DadState<BT>,
52    /// The execution context available with the address's DAD state.
53    pub core_ctx: &'a mut CC,
54}
55
56/// Holds references to state associated with duplicate address detection.
57pub struct DadStateRef<'a, CC, BT: DadBindingsTypes> {
58    /// A reference to the DAD address state.
59    pub state: DadAddressStateRef<'a, CC, BT>,
60    /// The time between DAD message retransmissions.
61    pub retrans_timer: &'a NonZeroDuration,
62    /// The maximum number of DAD messages to send.
63    pub max_dad_transmits: &'a Option<NonZeroU16>,
64}
65
66/// The execution context while performing DAD.
67pub trait DadAddressContext<BC>: IpDeviceAddressIdContext<Ipv6> {
68    /// Calls the function with a mutable reference to the address's assigned
69    /// flag.
70    fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
71        &mut self,
72        device_id: &Self::DeviceId,
73        addr: &Self::AddressId,
74        cb: F,
75    ) -> O;
76
77    /// Joins the multicast group on the device.
78    fn join_multicast_group(
79        &mut self,
80        bindings_ctx: &mut BC,
81        device_id: &Self::DeviceId,
82        multicast_addr: MulticastAddr<Ipv6Addr>,
83    );
84
85    /// Leaves the multicast group on the device.
86    fn leave_multicast_group(
87        &mut self,
88        bindings_ctx: &mut BC,
89        device_id: &Self::DeviceId,
90        multicast_addr: MulticastAddr<Ipv6Addr>,
91    );
92}
93
94/// The execution context for DAD.
95pub trait DadContext<BC: DadBindingsTypes>:
96    IpDeviceAddressIdContext<Ipv6>
97    + DeviceIdContext<AnyDevice>
98    + CoreTimerContext<DadTimerId<Self::WeakDeviceId, Self::WeakAddressId>, BC>
99    + CoreEventContext<DadEvent<Self::DeviceId>>
100{
101    /// The inner address context.
102    type DadAddressCtx<'a>: DadAddressContext<
103        BC,
104        DeviceId = Self::DeviceId,
105        AddressId = Self::AddressId,
106    >;
107
108    /// Calls the function with the DAD state associated with the address.
109    fn with_dad_state<O, F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, BC>) -> O>(
110        &mut self,
111        device_id: &Self::DeviceId,
112        addr: &Self::AddressId,
113        cb: F,
114    ) -> O;
115
116    /// Sends an NDP Neighbor Solicitation message for DAD to the local-link.
117    ///
118    /// The message will be sent with the unspecified (all-zeroes) source
119    /// address.
120    fn send_dad_packet(
121        &mut self,
122        bindings_ctx: &mut BC,
123        device_id: &Self::DeviceId,
124        dst_ip: MulticastAddr<Ipv6Addr>,
125        message: NeighborSolicitation,
126        nonce: NdpNonce<&[u8]>,
127    ) -> Result<(), ()>;
128}
129
130// Chosen somewhat arbitrarily. It's unlikely we need to store many
131// previously-used nonces given that we'll probably only ever see the most
132// recently used nonce looped back at us.
133const MAX_DAD_PROBE_NONCES_STORED: usize = 4;
134
135/// A data structure storing a limited number of `NdpNonce`s.
136#[derive(Default, Debug)]
137pub struct NonceCollection {
138    nonces: ArrayVec<[u8; MIN_NONCE_LENGTH], MAX_DAD_PROBE_NONCES_STORED>,
139}
140
141impl NonceCollection {
142    /// Given an `rng` source, generates a new unique nonce and stores it,
143    /// deleting the oldest nonce if there is no space remaining.
144    pub fn evicting_create_and_store_nonce(
145        &mut self,
146        mut rng: impl rand::Rng,
147    ) -> [u8; MIN_NONCE_LENGTH] {
148        let Self { nonces } = self;
149        loop {
150            let nonce: [u8; MIN_NONCE_LENGTH] = rng.gen();
151            if nonces.iter().any(|stored_nonce| stored_nonce == &nonce) {
152                continue;
153            }
154
155            if nonces.remaining_capacity() == 0 {
156                let _: [u8; MIN_NONCE_LENGTH] = nonces.remove(0);
157            }
158            nonces.push(nonce.clone());
159            break nonce;
160        }
161    }
162
163    /// Checks if `nonce` is in the collection.
164    pub fn contains(&self, nonce: &[u8]) -> bool {
165        if nonce.len() != MIN_NONCE_LENGTH {
166            return false;
167        }
168
169        let Self { nonces } = self;
170        nonces.iter().any(|stored_nonce| stored_nonce == &nonce)
171    }
172}
173
174#[derive(Debug, Eq, Hash, PartialEq)]
175/// Events generated by duplicate address detection.
176pub enum DadEvent<DeviceId> {
177    /// Duplicate address detection completed and the address is assigned.
178    AddressAssigned {
179        /// Device the address belongs to.
180        device: DeviceId,
181        /// The address that moved to the assigned state.
182        addr: UnicastAddr<Ipv6Addr>,
183    },
184}
185
186/// The bindings types for DAD.
187pub trait DadBindingsTypes: TimerBindingsTypes {}
188impl<BT> DadBindingsTypes for BT where BT: TimerBindingsTypes {}
189
190/// The bindings execution context for DAD.
191///
192/// The type parameter `E` is tied by [`DadContext`] so that [`DadEvent`] can be
193/// transformed into an event that is more meaningful to bindings.
194pub trait DadBindingsContext<E>:
195    DadBindingsTypes + TimerContext + EventContext<E> + RngContext
196{
197}
198impl<E, BC> DadBindingsContext<E> for BC where
199    BC: DadBindingsTypes + TimerContext + EventContext<E> + RngContext
200{
201}
202
203/// The result of looking up an address and nonce from a neighbor solicitation
204/// in the DAD state.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum DadAddressStateLookupResult {
207    Uninitialized,
208    Tentative { matched_nonce: bool },
209    Assigned,
210}
211
212/// An implementation for Duplicate Address Detection.
213pub trait DadHandler<I: IpDeviceIpExt, BC>:
214    DeviceIdContext<AnyDevice> + IpDeviceAddressIdContext<I>
215{
216    /// Initializes the DAD state for the given device and address.
217    ///
218    /// If DAD is required, the return value holds a [`StartDad`] token that
219    /// can be used to start the DAD algorithm.
220    fn initialize_duplicate_address_detection<'a>(
221        &mut self,
222        bindings_ctx: &mut BC,
223        device_id: &'a Self::DeviceId,
224        addr: &'a Self::AddressId,
225    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId>;
226
227    /// Starts duplicate address detection.
228    ///
229    /// The provided [`StartDad`] token is proof that DAD is required for the
230    /// address & device.
231    fn start_duplicate_address_detection<'a>(
232        &mut self,
233        bindings_ctx: &mut BC,
234        start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
235    );
236
237    /// Stops duplicate address detection.
238    ///
239    /// Does nothing if DAD is not being performed on the address.
240    fn stop_duplicate_address_detection(
241        &mut self,
242        bindings_ctx: &mut BC,
243        device_id: &Self::DeviceId,
244        addr: &Self::AddressId,
245    );
246
247    /// Handles an incoming neighbor solicitation that was determined to be sent
248    /// as part of a node (possibly ourselves) performing duplicate address
249    /// detection.
250    ///
251    /// If the incoming solicitation is determined to be a looped-back probe
252    /// that we ourselves sent, updates DAD state accordingly to send additional
253    /// probes.
254    fn handle_incoming_dad_neighbor_solicitation(
255        &mut self,
256        bindings_ctx: &mut BC,
257        device_id: &Self::DeviceId,
258        addr: &Self::AddressId,
259        nonce: Option<NdpNonce<&'_ [u8]>>,
260    ) -> DadAddressStateLookupResult;
261}
262
263/// Indicates whether DAD is needed for a given address on a given device.
264#[derive(Debug)]
265pub enum NeedsDad<'a, A, D> {
266    No,
267    Yes(StartDad<'a, A, D>),
268}
269
270impl<'a, A, D> NeedsDad<'a, A, D> {
271    // Returns the address's current state, and whether DAD needs to be started.
272    pub(crate) fn into_address_state_and_start_dad(
273        self,
274    ) -> (IpAddressState, Option<StartDad<'a, A, D>>) {
275        match self {
276            // Addresses proceed directly to assigned when DAD is disabled.
277            NeedsDad::No => (IpAddressState::Assigned, None),
278            NeedsDad::Yes(start_dad) => (IpAddressState::Tentative, Some(start_dad)),
279        }
280    }
281}
282
283/// Signals that DAD is allowed to run for the given address & device.
284///
285/// Inner members are private to ensure the type can only be constructed in the
286/// current module, which ensures that duplicate address detection can only be
287/// started after having checked that it's necessary.
288#[derive(Debug)]
289pub struct StartDad<'a, A, D> {
290    address_id: &'a A,
291    device_id: &'a D,
292}
293
294/// Initializes the DAD state for the given device and address.
295fn initialize_duplicate_address_detection<
296    'a,
297    BC: DadBindingsContext<CC::OuterEvent>,
298    CC: DadContext<BC>,
299>(
300    core_ctx: &mut CC,
301    bindings_ctx: &mut BC,
302    device_id: &'a CC::DeviceId,
303    addr: &'a CC::AddressId,
304) -> NeedsDad<'a, CC::AddressId, CC::DeviceId> {
305    core_ctx.with_dad_state(
306        device_id,
307        addr,
308        |DadStateRef { state, retrans_timer: _, max_dad_transmits }| {
309            let DadAddressStateRef { dad_state, core_ctx } = state;
310
311            let needs_dad = match max_dad_transmits {
312                None => {
313                    *dad_state = Ipv6DadState::Assigned;
314                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
315                    NeedsDad::No
316                }
317                Some(max_dad_transmits) => {
318                    // Mark the address as tentative before joining the multicast group
319                    // so that the address is not used as the source for any outgoing
320                    // MLD message.
321                    *dad_state = Ipv6DadState::Tentative {
322                        dad_transmits_remaining: Some(*max_dad_transmits),
323                        timer: CC::new_timer(
324                            bindings_ctx,
325                            DadTimerId { device_id: device_id.downgrade(), addr: addr.downgrade() },
326                        ),
327                        nonces: Default::default(),
328                        added_extra_transmits_after_detecting_looped_back_ns: false,
329                    };
330                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
331                    NeedsDad::Yes(StartDad { device_id, address_id: addr })
332                }
333            };
334
335            // As per RFC 4862 section 5.4.2,
336            //
337            //   Before sending a Neighbor Solicitation, an interface MUST join
338            //   the all-nodes multicast address and the solicited-node
339            //   multicast address of the tentative address.
340            //
341            // Note that:
342            // * We join the all-nodes multicast address on interface enable.
343            // * We join the solicited-node multicast address, even if the
344            //   address is skipping DAD (and therefore, the tentative state).
345            core_ctx.join_multicast_group(
346                bindings_ctx,
347                device_id,
348                addr.addr().addr().to_solicited_node_address(),
349            );
350
351            needs_dad
352        },
353    )
354}
355
356fn do_duplicate_address_detection<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>>(
357    core_ctx: &mut CC,
358    bindings_ctx: &mut BC,
359    device_id: &CC::DeviceId,
360    addr: &CC::AddressId,
361) {
362    let nonce_if_should_send_message = core_ctx.with_dad_state(
363        device_id,
364        addr,
365        |DadStateRef { state, retrans_timer, max_dad_transmits: _ }| {
366            let DadAddressStateRef { dad_state, core_ctx } = state;
367
368            let (remaining, timer, nonces) = match dad_state {
369                Ipv6DadState::Tentative {
370                    dad_transmits_remaining,
371                    timer,
372                    nonces,
373                    added_extra_transmits_after_detecting_looped_back_ns: _,
374                } => (dad_transmits_remaining, timer, nonces),
375                Ipv6DadState::Uninitialized | Ipv6DadState::Assigned => {
376                    panic!("expected address to be tentative; addr={addr:?}")
377                }
378            };
379
380            match remaining {
381                None => {
382                    *dad_state = Ipv6DadState::Assigned;
383                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
384                    CC::on_event(
385                        bindings_ctx,
386                        DadEvent::AddressAssigned {
387                            device: device_id.clone(),
388                            addr: addr.addr_sub().addr().get(),
389                        },
390                    );
391                    None
392                }
393                Some(non_zero_remaining) => {
394                    *remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
395
396                    // Per RFC 4862 section 5.1,
397                    //
398                    //   DupAddrDetectTransmits ...
399                    //      Autoconfiguration also assumes the presence of the variable
400                    //      RetransTimer as defined in [RFC4861]. For autoconfiguration
401                    //      purposes, RetransTimer specifies the delay between
402                    //      consecutive Neighbor Solicitation transmissions performed
403                    //      during Duplicate Address Detection (if
404                    //      DupAddrDetectTransmits is greater than 1), as well as the
405                    //      time a node waits after sending the last Neighbor
406                    //      Solicitation before ending the Duplicate Address Detection
407                    //      process.
408                    assert_eq!(
409                        bindings_ctx.schedule_timer(retrans_timer.get(), timer),
410                        None,
411                        "Unexpected DAD timer; addr={}, device_id={:?}",
412                        addr.addr(),
413                        device_id
414                    );
415                    debug!(
416                        "performing DAD for {}; {} tries left",
417                        addr.addr(),
418                        remaining.map_or(0, NonZeroU16::get)
419                    );
420                    Some(nonces.evicting_create_and_store_nonce(bindings_ctx.rng()))
421                }
422            }
423        },
424    );
425
426    let nonce = match nonce_if_should_send_message {
427        None => return,
428        Some(nonce) => nonce,
429    };
430
431    // Do not include the source link-layer option when the NS
432    // message as DAD messages are sent with the unspecified source
433    // address which must not hold a source link-layer option.
434    //
435    // As per RFC 4861 section 4.3,
436    //
437    //   Possible options:
438    //
439    //      Source link-layer address
440    //           The link-layer address for the sender. MUST NOT be
441    //           included when the source IP address is the
442    //           unspecified address. Otherwise, on link layers
443    //           that have addresses this option MUST be included in
444    //           multicast solicitations and SHOULD be included in
445    //           unicast solicitations.
446    //
447    // TODO(https://fxbug.dev/42165912): Either panic or guarantee that this error
448    // can't happen statically.
449    let dst_ip = addr.addr().addr().to_solicited_node_address();
450    let _: Result<(), _> = core_ctx.send_dad_packet(
451        bindings_ctx,
452        device_id,
453        dst_ip,
454        NeighborSolicitation::new(addr.addr().addr()),
455        NdpNonce::from(&nonce),
456    );
457}
458
459// TODO(https://fxbug.dev/42077260): Actually support DAD for IPv4.
460impl<BC, CC> DadHandler<Ipv4, BC> for CC
461where
462    CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
463{
464    fn initialize_duplicate_address_detection<'a>(
465        &mut self,
466        _bindings_ctx: &mut BC,
467        _device_id: &'a Self::DeviceId,
468        _addr: &'a Self::AddressId,
469    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
470        NeedsDad::No
471    }
472
473    fn start_duplicate_address_detection<'a>(
474        &mut self,
475        _bindings_ctx: &mut BC,
476        _start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
477    ) {
478    }
479
480    fn stop_duplicate_address_detection(
481        &mut self,
482        _bindings_ctx: &mut BC,
483        _device_id: &Self::DeviceId,
484        _addr: &Self::AddressId,
485    ) {
486    }
487
488    fn handle_incoming_dad_neighbor_solicitation(
489        &mut self,
490        _bindings_ctx: &mut BC,
491        _device_id: &Self::DeviceId,
492        _addr: &Self::AddressId,
493        _nonce: Option<NdpNonce<&'_ [u8]>>,
494    ) -> DadAddressStateLookupResult {
495        unimplemented!()
496    }
497}
498
499impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> DadHandler<Ipv6, BC> for CC {
500    fn initialize_duplicate_address_detection<'a>(
501        &mut self,
502        bindings_ctx: &mut BC,
503        device_id: &'a Self::DeviceId,
504        addr: &'a Self::AddressId,
505    ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
506        initialize_duplicate_address_detection(self, bindings_ctx, device_id, addr)
507    }
508
509    fn start_duplicate_address_detection<'a>(
510        &mut self,
511        bindings_ctx: &mut BC,
512        start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
513    ) {
514        let StartDad { device_id, address_id } = start_dad;
515        do_duplicate_address_detection(self, bindings_ctx, device_id, address_id)
516    }
517
518    fn stop_duplicate_address_detection(
519        &mut self,
520        bindings_ctx: &mut BC,
521        device_id: &Self::DeviceId,
522        addr: &Self::AddressId,
523    ) {
524        self.with_dad_state(
525            device_id,
526            addr,
527            |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
528                let DadAddressStateRef { dad_state, core_ctx } = state;
529
530                let leave_group = match dad_state {
531                    Ipv6DadState::Assigned => true,
532                    Ipv6DadState::Tentative {
533                        dad_transmits_remaining: _,
534                        timer,
535                        nonces: _,
536                        added_extra_transmits_after_detecting_looped_back_ns: _,
537                    } => {
538                        // Generally we should have a timer installed in the
539                        // tentative state, but we could be racing with the
540                        // timer firing in bindings so we can't assert that it's
541                        // installed here.
542                        let _: Option<_> = bindings_ctx.cancel_timer(timer);
543                        true
544                    }
545                    Ipv6DadState::Uninitialized => false,
546                };
547
548                // Undo the work we did when starting/performing DAD by putting
549                // the address back into a tentative/unassigned state and
550                // leaving the solicited node multicast group. We mark the
551                // address as tentative/unassigned before leaving the group so
552                // that the address is not used as the source for any outgoing
553                // MLD message.
554                *dad_state = Ipv6DadState::Uninitialized;
555                core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
556                if leave_group {
557                    core_ctx.leave_multicast_group(
558                        bindings_ctx,
559                        device_id,
560                        addr.addr().addr().to_solicited_node_address(),
561                    );
562                }
563            },
564        )
565    }
566
567    /// Checks if the nonce matches stored nonces in dad state.
568    fn handle_incoming_dad_neighbor_solicitation(
569        &mut self,
570        _bindings_ctx: &mut BC,
571        device_id: &Self::DeviceId,
572        addr: &Self::AddressId,
573        nonce: Option<NdpNonce<&'_ [u8]>>,
574    ) -> DadAddressStateLookupResult {
575        self.with_dad_state(
576            device_id,
577            addr,
578            |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
579                let DadAddressStateRef { dad_state, core_ctx: _ } = state;
580                match dad_state {
581                    Ipv6DadState::Assigned => DadAddressStateLookupResult::Assigned,
582                    Ipv6DadState::Tentative {
583                        dad_transmits_remaining,
584                        nonces,
585                        added_extra_transmits_after_detecting_looped_back_ns,
586                        timer: _,
587                    } => {
588                        let matched_nonce =
589                            nonce.is_some_and(|nonce| nonces.contains(nonce.bytes()));
590                        if matched_nonce
591                            && !core::mem::replace(
592                                added_extra_transmits_after_detecting_looped_back_ns,
593                                true,
594                            )
595                        {
596                            // Detected a looped-back DAD neighbor solicitation.
597                            // Per RFC 7527, we should send MAX_MULTICAST_SOLICIT more DAD probes.
598                            *dad_transmits_remaining =
599                                Some(DEFAULT_MAX_MULTICAST_SOLICIT.saturating_add(
600                                    dad_transmits_remaining.map(NonZero::get).unwrap_or(0),
601                                ));
602                        }
603                        DadAddressStateLookupResult::Tentative { matched_nonce }
604                    }
605
606                    Ipv6DadState::Uninitialized => DadAddressStateLookupResult::Uninitialized,
607                }
608            },
609        )
610    }
611}
612
613impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> HandleableTimer<CC, BC>
614    for DadTimerId<CC::WeakDeviceId, CC::WeakAddressId>
615{
616    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
617        let Self { device_id, addr } = self;
618        let Some(device_id) = device_id.upgrade() else {
619            return;
620        };
621        let Some(addr_id) = addr.upgrade() else {
622            return;
623        };
624        do_duplicate_address_detection(core_ctx, bindings_ctx, &device_id, &addr_id)
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use alloc::collections::hash_map::{Entry, HashMap};
631    use alloc::vec::Vec;
632    use core::time::Duration;
633
634    use assert_matches::assert_matches;
635    use net_types::ip::{AddrSubnet, IpAddress as _};
636    use net_types::Witness as _;
637    use netstack3_base::testutil::{
638        FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakAddressId,
639        FakeWeakDeviceId,
640    };
641    use netstack3_base::{CtxPair, InstantContext as _, SendFrameContext as _, TimerHandler};
642    use packet::EmptyBuf;
643    use packet_formats::icmp::ndp::Options;
644    use test_case::test_case;
645
646    use super::*;
647    use crate::internal::device::Ipv6DeviceAddr;
648
649    struct FakeDadAddressContext {
650        addr: UnicastAddr<Ipv6Addr>,
651        assigned: bool,
652        groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
653    }
654
655    type FakeAddressCtxImpl = FakeCoreCtx<FakeDadAddressContext, (), FakeDeviceId>;
656
657    impl DadAddressContext<FakeBindingsCtxImpl> for FakeAddressCtxImpl {
658        fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
659            &mut self,
660            &FakeDeviceId: &Self::DeviceId,
661            request_addr: &Self::AddressId,
662            cb: F,
663        ) -> O {
664            let FakeDadAddressContext { addr, assigned, .. } = &mut self.state;
665            assert_eq!(*request_addr.addr(), *addr);
666            cb(assigned)
667        }
668
669        fn join_multicast_group(
670            &mut self,
671            _bindings_ctx: &mut FakeBindingsCtxImpl,
672            &FakeDeviceId: &Self::DeviceId,
673            multicast_addr: MulticastAddr<Ipv6Addr>,
674        ) {
675            *self.state.groups.entry(multicast_addr).or_default() += 1;
676        }
677
678        fn leave_multicast_group(
679            &mut self,
680            _bindings_ctx: &mut FakeBindingsCtxImpl,
681            &FakeDeviceId: &Self::DeviceId,
682            multicast_addr: MulticastAddr<Ipv6Addr>,
683        ) {
684            match self.state.groups.entry(multicast_addr) {
685                Entry::Vacant(_) => {}
686                Entry::Occupied(mut e) => {
687                    let v = e.get_mut();
688                    const COUNT_BEFORE_REMOVE: usize = 1;
689                    if *v == COUNT_BEFORE_REMOVE {
690                        assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
691                    } else {
692                        *v -= 1
693                    }
694                }
695            }
696        }
697    }
698
699    struct FakeDadContext {
700        state: Ipv6DadState<FakeBindingsCtxImpl>,
701        retrans_timer: NonZeroDuration,
702        max_dad_transmits: Option<NonZeroU16>,
703        address_ctx: FakeAddressCtxImpl,
704    }
705
706    #[derive(Debug)]
707    struct DadMessageMeta {
708        dst_ip: MulticastAddr<Ipv6Addr>,
709        message: NeighborSolicitation,
710        nonce: Vec<u8>,
711    }
712
713    type TestDadTimerId = DadTimerId<
714        FakeWeakDeviceId<FakeDeviceId>,
715        FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>,
716    >;
717
718    type FakeBindingsCtxImpl = FakeBindingsCtx<TestDadTimerId, DadEvent<FakeDeviceId>, (), ()>;
719
720    type FakeCoreCtxImpl = FakeCoreCtx<FakeDadContext, DadMessageMeta, FakeDeviceId>;
721
722    fn get_address_id(addr: Ipv6Addr) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
723        AddrSubnet::new(addr, Ipv6Addr::BYTES * 8).unwrap()
724    }
725
726    impl CoreTimerContext<TestDadTimerId, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
727        fn convert_timer(dispatch_id: TestDadTimerId) -> TestDadTimerId {
728            dispatch_id
729        }
730    }
731
732    impl CoreEventContext<DadEvent<FakeDeviceId>> for FakeCoreCtxImpl {
733        type OuterEvent = DadEvent<FakeDeviceId>;
734        fn convert_event(event: DadEvent<FakeDeviceId>) -> DadEvent<FakeDeviceId> {
735            event
736        }
737    }
738
739    impl DadContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
740        type DadAddressCtx<'a> = FakeAddressCtxImpl;
741
742        fn with_dad_state<
743            O,
744            F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl>) -> O,
745        >(
746            &mut self,
747            &FakeDeviceId: &FakeDeviceId,
748            request_addr: &Self::AddressId,
749            cb: F,
750        ) -> O {
751            let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
752                &mut self.state;
753            let ctx_addr = address_ctx.state.addr;
754            let requested_addr = request_addr.addr().get();
755            assert!(
756                ctx_addr == requested_addr,
757                "invalid address {requested_addr} expected {ctx_addr}"
758            );
759            cb(DadStateRef {
760                state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
761                retrans_timer,
762                max_dad_transmits,
763            })
764        }
765
766        fn send_dad_packet(
767            &mut self,
768            bindings_ctx: &mut FakeBindingsCtxImpl,
769            &FakeDeviceId: &FakeDeviceId,
770            dst_ip: MulticastAddr<Ipv6Addr>,
771            message: NeighborSolicitation,
772            nonce: NdpNonce<&[u8]>,
773        ) -> Result<(), ()> {
774            Ok(self
775                .send_frame(
776                    bindings_ctx,
777                    DadMessageMeta { dst_ip, message, nonce: nonce.bytes().to_vec() },
778                    EmptyBuf,
779                )
780                .unwrap())
781        }
782    }
783
784    const RETRANS_TIMER: NonZeroDuration = NonZeroDuration::new(Duration::from_secs(1)).unwrap();
785
786    const DAD_ADDRESS: UnicastAddr<Ipv6Addr> =
787        unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 1])) };
788
789    type FakeCtx = CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl>;
790
791    #[test]
792    #[should_panic(expected = "expected address to be tentative")]
793    fn panic_non_tentative_address_handle_timer() {
794        let FakeCtx { mut core_ctx, mut bindings_ctx } =
795            FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
796                state: Ipv6DadState::Assigned,
797                retrans_timer: RETRANS_TIMER,
798                max_dad_transmits: None,
799                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
800                    addr: DAD_ADDRESS,
801                    assigned: false,
802                    groups: HashMap::default(),
803                }),
804            }));
805        TimerHandler::handle_timer(
806            &mut core_ctx,
807            &mut bindings_ctx,
808            dad_timer_id(),
809            Default::default(),
810        );
811    }
812
813    #[test]
814    fn dad_disabled() {
815        let FakeCtx { mut core_ctx, mut bindings_ctx } =
816            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
817                FakeCoreCtxImpl::with_state(FakeDadContext {
818                    state: Ipv6DadState::Tentative {
819                        dad_transmits_remaining: None,
820                        timer: bindings_ctx.new_timer(dad_timer_id()),
821                        nonces: Default::default(),
822                        added_extra_transmits_after_detecting_looped_back_ns: false,
823                    },
824                    retrans_timer: RETRANS_TIMER,
825                    max_dad_transmits: None,
826                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
827                        addr: DAD_ADDRESS,
828                        assigned: false,
829                        groups: HashMap::default(),
830                    }),
831                })
832            });
833        let address_id = get_address_id(DAD_ADDRESS.get());
834        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
835            &mut core_ctx,
836            &mut bindings_ctx,
837            &FakeDeviceId,
838            &address_id,
839        );
840        assert_matches!(start_dad, NeedsDad::No);
841        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
842        assert_matches!(*state, Ipv6DadState::Assigned);
843        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
844        assert!(*assigned);
845        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
846        assert_eq!(bindings_ctx.take_events(), &[][..]);
847    }
848
849    fn dad_timer_id() -> TestDadTimerId {
850        DadTimerId {
851            addr: FakeWeakAddressId(get_address_id(DAD_ADDRESS.get())),
852            device_id: FakeWeakDeviceId(FakeDeviceId),
853        }
854    }
855
856    fn check_dad(
857        core_ctx: &FakeCoreCtxImpl,
858        bindings_ctx: &FakeBindingsCtxImpl,
859        frames_len: usize,
860        dad_transmits_remaining: Option<NonZeroU16>,
861        retrans_timer: NonZeroDuration,
862    ) {
863        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
864        let nonces = assert_matches!(state, Ipv6DadState::Tentative {
865            dad_transmits_remaining: got,
866            timer: _,
867            nonces,
868            added_extra_transmits_after_detecting_looped_back_ns: _,
869        } => {
870            assert_eq!(
871                *got,
872                dad_transmits_remaining,
873                "got dad_transmits_remaining = {got:?}, \
874                 want dad_transmits_remaining = {dad_transmits_remaining:?}");
875            nonces
876        });
877        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
878        assert!(!*assigned);
879        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
880        let frames = core_ctx.frames();
881        assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
882        let (DadMessageMeta { dst_ip, message, nonce }, frame) =
883            frames.last().expect("should have transmitted a frame");
884
885        assert_eq!(*dst_ip, DAD_ADDRESS.to_solicited_node_address());
886        assert_eq!(*message, NeighborSolicitation::new(DAD_ADDRESS.get()));
887        assert!(nonces.contains(nonce), "should have stored nonce");
888
889        let options = Options::parse(&frame[..]).expect("parse NDP options");
890        assert_eq!(options.iter().count(), 0);
891        bindings_ctx
892            .timers
893            .assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
894    }
895
896    #[test]
897    fn perform_dad() {
898        const DAD_TRANSMITS_REQUIRED: u16 = 5;
899        const RETRANS_TIMER: NonZeroDuration =
900            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
901
902        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
903            FakeCoreCtxImpl::with_state(FakeDadContext {
904                state: Ipv6DadState::Tentative {
905                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
906                    timer: bindings_ctx.new_timer(dad_timer_id()),
907                    nonces: Default::default(),
908                    added_extra_transmits_after_detecting_looped_back_ns: false,
909                },
910                retrans_timer: RETRANS_TIMER,
911                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
912                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
913                    addr: DAD_ADDRESS,
914                    assigned: false,
915                    groups: HashMap::default(),
916                }),
917            })
918        });
919        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
920        let address_id = get_address_id(DAD_ADDRESS.get());
921        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
922            core_ctx,
923            bindings_ctx,
924            &FakeDeviceId,
925            &address_id,
926        );
927        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
928        DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
929
930        for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
931            check_dad(
932                core_ctx,
933                bindings_ctx,
934                usize::from(count + 1),
935                NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
936                RETRANS_TIMER,
937            );
938            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
939        }
940        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
941        assert_matches!(*state, Ipv6DadState::Assigned);
942        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
943        assert!(*assigned);
944        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
945        assert_eq!(
946            bindings_ctx.take_events(),
947            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
948        );
949    }
950
951    #[test]
952    fn stop_dad() {
953        const DAD_TRANSMITS_REQUIRED: u16 = 2;
954        const RETRANS_TIMER: NonZeroDuration =
955            NonZeroDuration::new(Duration::from_secs(2)).unwrap();
956
957        let FakeCtx { mut core_ctx, mut bindings_ctx } =
958            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
959                FakeCoreCtxImpl::with_state(FakeDadContext {
960                    state: Ipv6DadState::Tentative {
961                        dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
962                        timer: bindings_ctx.new_timer(dad_timer_id()),
963                        nonces: Default::default(),
964                        added_extra_transmits_after_detecting_looped_back_ns: false,
965                    },
966                    retrans_timer: RETRANS_TIMER,
967                    max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
968                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
969                        addr: DAD_ADDRESS,
970                        assigned: false,
971                        groups: HashMap::default(),
972                    }),
973                })
974            });
975        let address_id = get_address_id(DAD_ADDRESS.get());
976        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
977            &mut core_ctx,
978            &mut bindings_ctx,
979            &FakeDeviceId,
980            &address_id,
981        );
982        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
983        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
984            &mut core_ctx,
985            &mut bindings_ctx,
986            token,
987        );
988
989        check_dad(
990            &core_ctx,
991            &bindings_ctx,
992            1,
993            NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
994            RETRANS_TIMER,
995        );
996
997        DadHandler::<Ipv6, _>::stop_duplicate_address_detection(
998            &mut core_ctx,
999            &mut bindings_ctx,
1000            &FakeDeviceId,
1001            &get_address_id(DAD_ADDRESS.get()),
1002        );
1003        bindings_ctx.timers.assert_no_timers_installed();
1004        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1005        assert_matches!(*state, Ipv6DadState::Uninitialized);
1006        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1007        assert!(!*assigned);
1008        assert_eq!(groups, &HashMap::new());
1009    }
1010
1011    #[test_case(true, None ; "assigned with no incoming nonce")]
1012    #[test_case(true, Some([1u8; MIN_NONCE_LENGTH]) ; "assigned with incoming nonce")]
1013    #[test_case(false, None ; "uninitialized with no incoming nonce")]
1014    #[test_case(false, Some([1u8; MIN_NONCE_LENGTH]) ; "uninitialized with incoming nonce")]
1015    fn handle_incoming_dad_neighbor_solicitation_while_not_tentative(
1016        assigned: bool,
1017        nonce: Option<[u8; MIN_NONCE_LENGTH]>,
1018    ) {
1019        const MAX_DAD_TRANSMITS: u16 = 1;
1020        const RETRANS_TIMER: NonZeroDuration =
1021            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1022
1023        let mut ctx = FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1024            state: if assigned { Ipv6DadState::Assigned } else { Ipv6DadState::Uninitialized },
1025            retrans_timer: RETRANS_TIMER,
1026            max_dad_transmits: NonZeroU16::new(MAX_DAD_TRANSMITS),
1027            address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
1028                addr: DAD_ADDRESS,
1029                assigned: false,
1030                groups: HashMap::default(),
1031            }),
1032        }));
1033        let addr = get_address_id(DAD_ADDRESS.get());
1034
1035        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1036
1037        let want_lookup_result = if assigned {
1038            DadAddressStateLookupResult::Assigned
1039        } else {
1040            DadAddressStateLookupResult::Uninitialized
1041        };
1042
1043        assert_eq!(
1044            DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
1045                core_ctx,
1046                bindings_ctx,
1047                &FakeDeviceId,
1048                &addr,
1049                nonce.as_ref().map(NdpNonce::from),
1050            ),
1051            want_lookup_result
1052        );
1053    }
1054
1055    #[test_case(true ; "discards looped back NS")]
1056    #[test_case(false ; "acts on non-looped-back NS")]
1057    fn handle_incoming_dad_neighbor_solicitation_during_tentative(looped_back: bool) {
1058        const DAD_TRANSMITS_REQUIRED: u16 = 1;
1059        const RETRANS_TIMER: NonZeroDuration =
1060            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1061
1062        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1063            FakeCoreCtxImpl::with_state(FakeDadContext {
1064                state: Ipv6DadState::Tentative {
1065                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1066                    timer: bindings_ctx.new_timer(dad_timer_id()),
1067                    nonces: Default::default(),
1068                    added_extra_transmits_after_detecting_looped_back_ns: false,
1069                },
1070                retrans_timer: RETRANS_TIMER,
1071                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1072                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
1073                    addr: DAD_ADDRESS,
1074                    assigned: false,
1075                    groups: HashMap::default(),
1076                }),
1077            })
1078        });
1079        let addr = get_address_id(DAD_ADDRESS.get());
1080
1081        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1082        let address_id = get_address_id(DAD_ADDRESS.get());
1083        let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1084            core_ctx,
1085            bindings_ctx,
1086            &FakeDeviceId,
1087            &address_id,
1088        );
1089        let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1090        DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1091
1092        check_dad(core_ctx, bindings_ctx, 1, None, RETRANS_TIMER);
1093
1094        let sent_nonce: [u8; MIN_NONCE_LENGTH] = {
1095            let (DadMessageMeta { dst_ip: _, message: _, nonce }, _frame) =
1096                core_ctx.frames().last().expect("should have transmitted a frame");
1097            nonce.clone().try_into().expect("should be nonce of MIN_NONCE_LENGTH")
1098        };
1099
1100        let alternative_nonce = {
1101            let mut nonce = sent_nonce.clone();
1102            nonce[0] = nonce[0].wrapping_add(1);
1103            nonce
1104        };
1105
1106        let incoming_nonce =
1107            NdpNonce::from(if looped_back { &sent_nonce } else { &alternative_nonce });
1108
1109        let matched_nonce = assert_matches!(
1110            DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
1111                core_ctx,
1112                bindings_ctx,
1113                &FakeDeviceId,
1114                &addr,
1115                Some(incoming_nonce),
1116            ),
1117            DadAddressStateLookupResult::Tentative { matched_nonce } => matched_nonce
1118        );
1119
1120        assert_eq!(matched_nonce, looped_back);
1121
1122        let frames_len_before_extra_transmits = core_ctx.frames().len();
1123        assert_eq!(frames_len_before_extra_transmits, 1);
1124
1125        let extra_dad_transmits_required =
1126            NonZero::new(if looped_back { DEFAULT_MAX_MULTICAST_SOLICIT.get() } else { 0 });
1127
1128        let (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns) = assert_matches!(
1129            &core_ctx.state.state,
1130            Ipv6DadState::Tentative {
1131                dad_transmits_remaining,
1132                timer: _,
1133                nonces: _,
1134                added_extra_transmits_after_detecting_looped_back_ns
1135            } => (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns),
1136            "DAD state should be Tentative"
1137        );
1138
1139        assert_eq!(dad_transmits_remaining, &extra_dad_transmits_required);
1140        assert_eq!(added_extra_transmits_after_detecting_looped_back_ns, &looped_back);
1141
1142        let extra_dad_transmits_required =
1143            extra_dad_transmits_required.map(|n| n.get()).unwrap_or(0);
1144
1145        // The retransmit timer should have been kicked when we observed the matching nonce.
1146        assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1147
1148        // Even though we originally required only 1 DAD transmit, MAX_MULTICAST_SOLICIT more
1149        // should be required as a result of the looped back solicitation.
1150        for count in 0..extra_dad_transmits_required {
1151            check_dad(
1152                core_ctx,
1153                bindings_ctx,
1154                usize::from(count) + frames_len_before_extra_transmits + 1,
1155                NonZeroU16::new(extra_dad_transmits_required - count - 1),
1156                RETRANS_TIMER,
1157            );
1158            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1159        }
1160        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1161        assert_matches!(*state, Ipv6DadState::Assigned);
1162        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1163        assert!(*assigned);
1164        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
1165        assert_eq!(
1166            bindings_ctx.take_events(),
1167            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
1168        );
1169    }
1170}