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    /// The initial address state for newly added addresses.
217    // TODO(https://fxbug.dev/42077260): This can probably be removed when we
218    // can do DAD over IPv4.
219    const INITIAL_ADDRESS_STATE: IpAddressState;
220
221    /// Starts duplicate address detection.
222    ///
223    /// # Panics
224    ///
225    /// Panics if tentative state for the address is not found.
226    fn start_duplicate_address_detection(
227        &mut self,
228        bindings_ctx: &mut BC,
229        device_id: &Self::DeviceId,
230        addr: &Self::AddressId,
231    );
232
233    /// Stops duplicate address detection.
234    ///
235    /// Does nothing if DAD is not being performed on the address.
236    fn stop_duplicate_address_detection(
237        &mut self,
238        bindings_ctx: &mut BC,
239        device_id: &Self::DeviceId,
240        addr: &Self::AddressId,
241    );
242
243    /// Handles an incoming neighbor solicitation that was determined to be sent
244    /// as part of a node (possibly ourselves) performing duplicate address
245    /// detection.
246    ///
247    /// If the incoming solicitation is determined to be a looped-back probe
248    /// that we ourselves sent, updates DAD state accordingly to send additional
249    /// probes.
250    fn handle_incoming_dad_neighbor_solicitation(
251        &mut self,
252        bindings_ctx: &mut BC,
253        device_id: &Self::DeviceId,
254        addr: &Self::AddressId,
255        nonce: Option<NdpNonce<&'_ [u8]>>,
256    ) -> DadAddressStateLookupResult;
257}
258
259enum DoDadVariation {
260    Start,
261    Continue,
262}
263
264fn do_duplicate_address_detection<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>>(
265    core_ctx: &mut CC,
266    bindings_ctx: &mut BC,
267    device_id: &CC::DeviceId,
268    addr: &CC::AddressId,
269    variation: DoDadVariation,
270) {
271    let nonce_if_should_send_message = core_ctx.with_dad_state(
272        device_id,
273        addr,
274        |DadStateRef { state, retrans_timer, max_dad_transmits }| {
275            let DadAddressStateRef { dad_state, core_ctx } = state;
276
277            match variation {
278                DoDadVariation::Start => {
279                    // Mark the address as tentative/unassigned before joining
280                    // the group so that the address is not used as the source
281                    // for any outgoing MLD message.
282                    *dad_state = Ipv6DadState::Tentative {
283                        dad_transmits_remaining: *max_dad_transmits,
284                        timer: CC::new_timer(
285                            bindings_ctx,
286                            DadTimerId { device_id: device_id.downgrade(), addr: addr.downgrade() },
287                        ),
288                        nonces: Default::default(),
289                        added_extra_transmits_after_detecting_looped_back_ns: false,
290                    };
291                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
292
293                    // As per RFC 4861 section 5.6.2,
294                    //
295                    //   Before sending a Neighbor Solicitation, an interface MUST join
296                    //   the all-nodes multicast address and the solicited-node
297                    //   multicast address of the tentative address.
298                    //
299                    // Note that we join the all-nodes multicast address on interface
300                    // enable.
301                    core_ctx.join_multicast_group(
302                        bindings_ctx,
303                        device_id,
304                        addr.addr().addr().to_solicited_node_address(),
305                    );
306                }
307                DoDadVariation::Continue => {}
308            }
309
310            let (remaining, timer, nonces) = match dad_state {
311                Ipv6DadState::Tentative {
312                    dad_transmits_remaining,
313                    timer,
314                    nonces,
315                    added_extra_transmits_after_detecting_looped_back_ns: _,
316                } => (dad_transmits_remaining, timer, nonces),
317                Ipv6DadState::Uninitialized | Ipv6DadState::Assigned => {
318                    panic!("expected address to be tentative; addr={addr:?}")
319                }
320            };
321
322            match remaining {
323                None => {
324                    *dad_state = Ipv6DadState::Assigned;
325                    core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
326                    CC::on_event(
327                        bindings_ctx,
328                        DadEvent::AddressAssigned {
329                            device: device_id.clone(),
330                            addr: addr.addr_sub().addr().get(),
331                        },
332                    );
333                    None
334                }
335                Some(non_zero_remaining) => {
336                    *remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
337
338                    // Per RFC 4862 section 5.1,
339                    //
340                    //   DupAddrDetectTransmits ...
341                    //      Autoconfiguration also assumes the presence of the variable
342                    //      RetransTimer as defined in [RFC4861]. For autoconfiguration
343                    //      purposes, RetransTimer specifies the delay between
344                    //      consecutive Neighbor Solicitation transmissions performed
345                    //      during Duplicate Address Detection (if
346                    //      DupAddrDetectTransmits is greater than 1), as well as the
347                    //      time a node waits after sending the last Neighbor
348                    //      Solicitation before ending the Duplicate Address Detection
349                    //      process.
350                    assert_eq!(
351                        bindings_ctx.schedule_timer(retrans_timer.get(), timer),
352                        None,
353                        "Unexpected DAD timer; addr={}, device_id={:?}",
354                        addr.addr(),
355                        device_id
356                    );
357                    debug!(
358                        "performing DAD for {}; {} tries left",
359                        addr.addr(),
360                        remaining.map_or(0, NonZeroU16::get)
361                    );
362                    Some(nonces.evicting_create_and_store_nonce(bindings_ctx.rng()))
363                }
364            }
365        },
366    );
367
368    let nonce = match nonce_if_should_send_message {
369        None => return,
370        Some(nonce) => nonce,
371    };
372
373    // Do not include the source link-layer option when the NS
374    // message as DAD messages are sent with the unspecified source
375    // address which must not hold a source link-layer option.
376    //
377    // As per RFC 4861 section 4.3,
378    //
379    //   Possible options:
380    //
381    //      Source link-layer address
382    //           The link-layer address for the sender. MUST NOT be
383    //           included when the source IP address is the
384    //           unspecified address. Otherwise, on link layers
385    //           that have addresses this option MUST be included in
386    //           multicast solicitations and SHOULD be included in
387    //           unicast solicitations.
388    //
389    // TODO(https://fxbug.dev/42165912): Either panic or guarantee that this error
390    // can't happen statically.
391    let dst_ip = addr.addr().addr().to_solicited_node_address();
392    let _: Result<(), _> = core_ctx.send_dad_packet(
393        bindings_ctx,
394        device_id,
395        dst_ip,
396        NeighborSolicitation::new(addr.addr().addr()),
397        NdpNonce::from(&nonce),
398    );
399}
400
401// TODO(https://fxbug.dev/42077260): Actually support DAD for IPv4.
402impl<BC, CC> DadHandler<Ipv4, BC> for CC
403where
404    CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
405{
406    const INITIAL_ADDRESS_STATE: IpAddressState = IpAddressState::Assigned;
407    fn start_duplicate_address_detection(
408        &mut self,
409        _bindings_ctx: &mut BC,
410        _device_id: &Self::DeviceId,
411        _addr: &Self::AddressId,
412    ) {
413    }
414
415    fn stop_duplicate_address_detection(
416        &mut self,
417        _bindings_ctx: &mut BC,
418        _device_id: &Self::DeviceId,
419        _addr: &Self::AddressId,
420    ) {
421    }
422
423    fn handle_incoming_dad_neighbor_solicitation(
424        &mut self,
425        _bindings_ctx: &mut BC,
426        _device_id: &Self::DeviceId,
427        _addr: &Self::AddressId,
428        _nonce: Option<NdpNonce<&'_ [u8]>>,
429    ) -> DadAddressStateLookupResult {
430        unimplemented!()
431    }
432}
433
434impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> DadHandler<Ipv6, BC> for CC {
435    const INITIAL_ADDRESS_STATE: IpAddressState = IpAddressState::Tentative;
436
437    fn start_duplicate_address_detection(
438        &mut self,
439        bindings_ctx: &mut BC,
440        device_id: &Self::DeviceId,
441        addr: &Self::AddressId,
442    ) {
443        do_duplicate_address_detection(self, bindings_ctx, device_id, addr, DoDadVariation::Start)
444    }
445
446    fn stop_duplicate_address_detection(
447        &mut self,
448        bindings_ctx: &mut BC,
449        device_id: &Self::DeviceId,
450        addr: &Self::AddressId,
451    ) {
452        self.with_dad_state(
453            device_id,
454            addr,
455            |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
456                let DadAddressStateRef { dad_state, core_ctx } = state;
457
458                let leave_group = match dad_state {
459                    Ipv6DadState::Assigned => true,
460                    Ipv6DadState::Tentative {
461                        dad_transmits_remaining: _,
462                        timer,
463                        nonces: _,
464                        added_extra_transmits_after_detecting_looped_back_ns: _,
465                    } => {
466                        // Generally we should have a timer installed in the
467                        // tentative state, but we could be racing with the
468                        // timer firing in bindings so we can't assert that it's
469                        // installed here.
470                        let _: Option<_> = bindings_ctx.cancel_timer(timer);
471                        true
472                    }
473                    Ipv6DadState::Uninitialized => false,
474                };
475
476                // Undo the work we did when starting/performing DAD by putting
477                // the address back into a tentative/unassigned state and
478                // leaving the solicited node multicast group. We mark the
479                // address as tentative/unassigned before leaving the group so
480                // that the address is not used as the source for any outgoing
481                // MLD message.
482                *dad_state = Ipv6DadState::Uninitialized;
483                core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
484                if leave_group {
485                    core_ctx.leave_multicast_group(
486                        bindings_ctx,
487                        device_id,
488                        addr.addr().addr().to_solicited_node_address(),
489                    );
490                }
491            },
492        )
493    }
494
495    /// Checks if the nonce matches stored nonces in dad state.
496    fn handle_incoming_dad_neighbor_solicitation(
497        &mut self,
498        _bindings_ctx: &mut BC,
499        device_id: &Self::DeviceId,
500        addr: &Self::AddressId,
501        nonce: Option<NdpNonce<&'_ [u8]>>,
502    ) -> DadAddressStateLookupResult {
503        self.with_dad_state(
504            device_id,
505            addr,
506            |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
507                let DadAddressStateRef { dad_state, core_ctx: _ } = state;
508                match dad_state {
509                    Ipv6DadState::Assigned => DadAddressStateLookupResult::Assigned,
510                    Ipv6DadState::Tentative {
511                        dad_transmits_remaining,
512                        nonces,
513                        added_extra_transmits_after_detecting_looped_back_ns,
514                        timer: _,
515                    } => {
516                        let matched_nonce =
517                            nonce.is_some_and(|nonce| nonces.contains(nonce.bytes()));
518                        if matched_nonce
519                            && !core::mem::replace(
520                                added_extra_transmits_after_detecting_looped_back_ns,
521                                true,
522                            )
523                        {
524                            // Detected a looped-back DAD neighbor solicitation.
525                            // Per RFC 7527, we should send MAX_MULTICAST_SOLICIT more DAD probes.
526                            *dad_transmits_remaining =
527                                Some(DEFAULT_MAX_MULTICAST_SOLICIT.saturating_add(
528                                    dad_transmits_remaining.map(NonZero::get).unwrap_or(0),
529                                ));
530                        }
531                        DadAddressStateLookupResult::Tentative { matched_nonce }
532                    }
533
534                    Ipv6DadState::Uninitialized => DadAddressStateLookupResult::Uninitialized,
535                }
536            },
537        )
538    }
539}
540
541impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> HandleableTimer<CC, BC>
542    for DadTimerId<CC::WeakDeviceId, CC::WeakAddressId>
543{
544    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
545        let Self { device_id, addr } = self;
546        let Some(device_id) = device_id.upgrade() else {
547            return;
548        };
549        let Some(addr_id) = addr.upgrade() else {
550            return;
551        };
552        do_duplicate_address_detection(
553            core_ctx,
554            bindings_ctx,
555            &device_id,
556            &addr_id,
557            DoDadVariation::Continue,
558        )
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use alloc::collections::hash_map::{Entry, HashMap};
565    use alloc::vec::Vec;
566    use core::time::Duration;
567
568    use assert_matches::assert_matches;
569    use net_types::ip::{AddrSubnet, IpAddress as _};
570    use net_types::Witness as _;
571    use netstack3_base::testutil::{
572        FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakAddressId,
573        FakeWeakDeviceId,
574    };
575    use netstack3_base::{CtxPair, InstantContext as _, SendFrameContext as _, TimerHandler};
576    use packet::EmptyBuf;
577    use packet_formats::icmp::ndp::Options;
578    use test_case::test_case;
579
580    use super::*;
581    use crate::internal::device::Ipv6DeviceAddr;
582
583    struct FakeDadAddressContext {
584        addr: UnicastAddr<Ipv6Addr>,
585        assigned: bool,
586        groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
587    }
588
589    type FakeAddressCtxImpl = FakeCoreCtx<FakeDadAddressContext, (), FakeDeviceId>;
590
591    impl DadAddressContext<FakeBindingsCtxImpl> for FakeAddressCtxImpl {
592        fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
593            &mut self,
594            &FakeDeviceId: &Self::DeviceId,
595            request_addr: &Self::AddressId,
596            cb: F,
597        ) -> O {
598            let FakeDadAddressContext { addr, assigned, .. } = &mut self.state;
599            assert_eq!(*request_addr.addr(), *addr);
600            cb(assigned)
601        }
602
603        fn join_multicast_group(
604            &mut self,
605            _bindings_ctx: &mut FakeBindingsCtxImpl,
606            &FakeDeviceId: &Self::DeviceId,
607            multicast_addr: MulticastAddr<Ipv6Addr>,
608        ) {
609            *self.state.groups.entry(multicast_addr).or_default() += 1;
610        }
611
612        fn leave_multicast_group(
613            &mut self,
614            _bindings_ctx: &mut FakeBindingsCtxImpl,
615            &FakeDeviceId: &Self::DeviceId,
616            multicast_addr: MulticastAddr<Ipv6Addr>,
617        ) {
618            match self.state.groups.entry(multicast_addr) {
619                Entry::Vacant(_) => {}
620                Entry::Occupied(mut e) => {
621                    let v = e.get_mut();
622                    const COUNT_BEFORE_REMOVE: usize = 1;
623                    if *v == COUNT_BEFORE_REMOVE {
624                        assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
625                    } else {
626                        *v -= 1
627                    }
628                }
629            }
630        }
631    }
632
633    struct FakeDadContext {
634        state: Ipv6DadState<FakeBindingsCtxImpl>,
635        retrans_timer: NonZeroDuration,
636        max_dad_transmits: Option<NonZeroU16>,
637        address_ctx: FakeAddressCtxImpl,
638    }
639
640    #[derive(Debug)]
641    struct DadMessageMeta {
642        dst_ip: MulticastAddr<Ipv6Addr>,
643        message: NeighborSolicitation,
644        nonce: Vec<u8>,
645    }
646
647    type TestDadTimerId = DadTimerId<
648        FakeWeakDeviceId<FakeDeviceId>,
649        FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>,
650    >;
651
652    type FakeBindingsCtxImpl = FakeBindingsCtx<TestDadTimerId, DadEvent<FakeDeviceId>, (), ()>;
653
654    type FakeCoreCtxImpl = FakeCoreCtx<FakeDadContext, DadMessageMeta, FakeDeviceId>;
655
656    fn get_address_id(addr: Ipv6Addr) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
657        AddrSubnet::new(addr, Ipv6Addr::BYTES * 8).unwrap()
658    }
659
660    impl CoreTimerContext<TestDadTimerId, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
661        fn convert_timer(dispatch_id: TestDadTimerId) -> TestDadTimerId {
662            dispatch_id
663        }
664    }
665
666    impl CoreEventContext<DadEvent<FakeDeviceId>> for FakeCoreCtxImpl {
667        type OuterEvent = DadEvent<FakeDeviceId>;
668        fn convert_event(event: DadEvent<FakeDeviceId>) -> DadEvent<FakeDeviceId> {
669            event
670        }
671    }
672
673    impl DadContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
674        type DadAddressCtx<'a> = FakeAddressCtxImpl;
675
676        fn with_dad_state<
677            O,
678            F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl>) -> O,
679        >(
680            &mut self,
681            &FakeDeviceId: &FakeDeviceId,
682            request_addr: &Self::AddressId,
683            cb: F,
684        ) -> O {
685            let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
686                &mut self.state;
687            let ctx_addr = address_ctx.state.addr;
688            let requested_addr = request_addr.addr().get();
689            assert!(
690                ctx_addr == requested_addr,
691                "invalid address {requested_addr} expected {ctx_addr}"
692            );
693            cb(DadStateRef {
694                state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
695                retrans_timer,
696                max_dad_transmits,
697            })
698        }
699
700        fn send_dad_packet(
701            &mut self,
702            bindings_ctx: &mut FakeBindingsCtxImpl,
703            &FakeDeviceId: &FakeDeviceId,
704            dst_ip: MulticastAddr<Ipv6Addr>,
705            message: NeighborSolicitation,
706            nonce: NdpNonce<&[u8]>,
707        ) -> Result<(), ()> {
708            Ok(self
709                .send_frame(
710                    bindings_ctx,
711                    DadMessageMeta { dst_ip, message, nonce: nonce.bytes().to_vec() },
712                    EmptyBuf,
713                )
714                .unwrap())
715        }
716    }
717
718    const RETRANS_TIMER: NonZeroDuration = NonZeroDuration::new(Duration::from_secs(1)).unwrap();
719
720    const DAD_ADDRESS: UnicastAddr<Ipv6Addr> =
721        unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 1])) };
722
723    type FakeCtx = CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl>;
724
725    #[test]
726    #[should_panic(expected = "expected address to be tentative")]
727    fn panic_non_tentative_address_handle_timer() {
728        let FakeCtx { mut core_ctx, mut bindings_ctx } =
729            FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
730                state: Ipv6DadState::Assigned,
731                retrans_timer: RETRANS_TIMER,
732                max_dad_transmits: None,
733                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
734                    addr: DAD_ADDRESS,
735                    assigned: false,
736                    groups: HashMap::default(),
737                }),
738            }));
739        TimerHandler::handle_timer(
740            &mut core_ctx,
741            &mut bindings_ctx,
742            dad_timer_id(),
743            Default::default(),
744        );
745    }
746
747    #[test]
748    fn dad_disabled() {
749        let FakeCtx { mut core_ctx, mut bindings_ctx } =
750            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
751                FakeCoreCtxImpl::with_state(FakeDadContext {
752                    state: Ipv6DadState::Tentative {
753                        dad_transmits_remaining: None,
754                        timer: bindings_ctx.new_timer(dad_timer_id()),
755                        nonces: Default::default(),
756                        added_extra_transmits_after_detecting_looped_back_ns: false,
757                    },
758                    retrans_timer: RETRANS_TIMER,
759                    max_dad_transmits: None,
760                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
761                        addr: DAD_ADDRESS,
762                        assigned: false,
763                        groups: HashMap::default(),
764                    }),
765                })
766            });
767        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
768            &mut core_ctx,
769            &mut bindings_ctx,
770            &FakeDeviceId,
771            &get_address_id(DAD_ADDRESS.get()),
772        );
773        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
774        assert_matches!(*state, Ipv6DadState::Assigned);
775        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
776        assert!(*assigned);
777        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
778        assert_eq!(
779            bindings_ctx.take_events(),
780            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
781        );
782    }
783
784    fn dad_timer_id() -> TestDadTimerId {
785        DadTimerId {
786            addr: FakeWeakAddressId(get_address_id(DAD_ADDRESS.get())),
787            device_id: FakeWeakDeviceId(FakeDeviceId),
788        }
789    }
790
791    fn check_dad(
792        core_ctx: &FakeCoreCtxImpl,
793        bindings_ctx: &FakeBindingsCtxImpl,
794        frames_len: usize,
795        dad_transmits_remaining: Option<NonZeroU16>,
796        retrans_timer: NonZeroDuration,
797    ) {
798        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
799        let nonces = assert_matches!(state, Ipv6DadState::Tentative {
800            dad_transmits_remaining: got,
801            timer: _,
802            nonces,
803            added_extra_transmits_after_detecting_looped_back_ns: _,
804        } => {
805            assert_eq!(
806                *got,
807                dad_transmits_remaining,
808                "got dad_transmits_remaining = {got:?}, \
809                 want dad_transmits_remaining = {dad_transmits_remaining:?}");
810            nonces
811        });
812        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
813        assert!(!*assigned);
814        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
815        let frames = core_ctx.frames();
816        assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
817        let (DadMessageMeta { dst_ip, message, nonce }, frame) =
818            frames.last().expect("should have transmitted a frame");
819
820        assert_eq!(*dst_ip, DAD_ADDRESS.to_solicited_node_address());
821        assert_eq!(*message, NeighborSolicitation::new(DAD_ADDRESS.get()));
822        assert!(nonces.contains(nonce), "should have stored nonce");
823
824        let options = Options::parse(&frame[..]).expect("parse NDP options");
825        assert_eq!(options.iter().count(), 0);
826        bindings_ctx
827            .timers
828            .assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
829    }
830
831    #[test]
832    fn perform_dad() {
833        const DAD_TRANSMITS_REQUIRED: u16 = 5;
834        const RETRANS_TIMER: NonZeroDuration =
835            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
836
837        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
838            FakeCoreCtxImpl::with_state(FakeDadContext {
839                state: Ipv6DadState::Tentative {
840                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
841                    timer: bindings_ctx.new_timer(dad_timer_id()),
842                    nonces: Default::default(),
843                    added_extra_transmits_after_detecting_looped_back_ns: false,
844                },
845                retrans_timer: RETRANS_TIMER,
846                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
847                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
848                    addr: DAD_ADDRESS,
849                    assigned: false,
850                    groups: HashMap::default(),
851                }),
852            })
853        });
854        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
855        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
856            core_ctx,
857            bindings_ctx,
858            &FakeDeviceId,
859            &get_address_id(DAD_ADDRESS.get()),
860        );
861
862        for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
863            check_dad(
864                core_ctx,
865                bindings_ctx,
866                usize::from(count + 1),
867                NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
868                RETRANS_TIMER,
869            );
870            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
871        }
872        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
873        assert_matches!(*state, Ipv6DadState::Assigned);
874        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
875        assert!(*assigned);
876        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
877        assert_eq!(
878            bindings_ctx.take_events(),
879            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
880        );
881    }
882
883    #[test]
884    fn stop_dad() {
885        const DAD_TRANSMITS_REQUIRED: u16 = 2;
886        const RETRANS_TIMER: NonZeroDuration =
887            NonZeroDuration::new(Duration::from_secs(2)).unwrap();
888
889        let FakeCtx { mut core_ctx, mut bindings_ctx } =
890            FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
891                FakeCoreCtxImpl::with_state(FakeDadContext {
892                    state: Ipv6DadState::Tentative {
893                        dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
894                        timer: bindings_ctx.new_timer(dad_timer_id()),
895                        nonces: Default::default(),
896                        added_extra_transmits_after_detecting_looped_back_ns: false,
897                    },
898                    retrans_timer: RETRANS_TIMER,
899                    max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
900                    address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
901                        addr: DAD_ADDRESS,
902                        assigned: false,
903                        groups: HashMap::default(),
904                    }),
905                })
906            });
907        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
908            &mut core_ctx,
909            &mut bindings_ctx,
910            &FakeDeviceId,
911            &get_address_id(DAD_ADDRESS.get()),
912        );
913        check_dad(
914            &core_ctx,
915            &bindings_ctx,
916            1,
917            NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
918            RETRANS_TIMER,
919        );
920
921        DadHandler::<Ipv6, _>::stop_duplicate_address_detection(
922            &mut core_ctx,
923            &mut bindings_ctx,
924            &FakeDeviceId,
925            &get_address_id(DAD_ADDRESS.get()),
926        );
927        bindings_ctx.timers.assert_no_timers_installed();
928        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
929        assert_matches!(*state, Ipv6DadState::Uninitialized);
930        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
931        assert!(!*assigned);
932        assert_eq!(groups, &HashMap::new());
933    }
934
935    #[test_case(true, None ; "assigned with no incoming nonce")]
936    #[test_case(true, Some([1u8; MIN_NONCE_LENGTH]) ; "assigned with incoming nonce")]
937    #[test_case(false, None ; "uninitialized with no incoming nonce")]
938    #[test_case(false, Some([1u8; MIN_NONCE_LENGTH]) ; "uninitialized with incoming nonce")]
939    fn handle_incoming_dad_neighbor_solicitation_while_not_tentative(
940        assigned: bool,
941        nonce: Option<[u8; MIN_NONCE_LENGTH]>,
942    ) {
943        const MAX_DAD_TRANSMITS: u16 = 1;
944        const RETRANS_TIMER: NonZeroDuration =
945            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
946
947        let mut ctx = FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
948            state: if assigned { Ipv6DadState::Assigned } else { Ipv6DadState::Uninitialized },
949            retrans_timer: RETRANS_TIMER,
950            max_dad_transmits: NonZeroU16::new(MAX_DAD_TRANSMITS),
951            address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
952                addr: DAD_ADDRESS,
953                assigned: false,
954                groups: HashMap::default(),
955            }),
956        }));
957        let addr = get_address_id(DAD_ADDRESS.get());
958
959        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
960
961        let want_lookup_result = if assigned {
962            DadAddressStateLookupResult::Assigned
963        } else {
964            DadAddressStateLookupResult::Uninitialized
965        };
966
967        assert_eq!(
968            DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
969                core_ctx,
970                bindings_ctx,
971                &FakeDeviceId,
972                &addr,
973                nonce.as_ref().map(NdpNonce::from),
974            ),
975            want_lookup_result
976        );
977    }
978
979    #[test_case(true ; "discards looped back NS")]
980    #[test_case(false ; "acts on non-looped-back NS")]
981    fn handle_incoming_dad_neighbor_solicitation_during_tentative(looped_back: bool) {
982        const DAD_TRANSMITS_REQUIRED: u16 = 1;
983        const RETRANS_TIMER: NonZeroDuration =
984            NonZeroDuration::new(Duration::from_secs(1)).unwrap();
985
986        let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
987            FakeCoreCtxImpl::with_state(FakeDadContext {
988                state: Ipv6DadState::Tentative {
989                    dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
990                    timer: bindings_ctx.new_timer(dad_timer_id()),
991                    nonces: Default::default(),
992                    added_extra_transmits_after_detecting_looped_back_ns: false,
993                },
994                retrans_timer: RETRANS_TIMER,
995                max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
996                address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
997                    addr: DAD_ADDRESS,
998                    assigned: false,
999                    groups: HashMap::default(),
1000                }),
1001            })
1002        });
1003        let addr = get_address_id(DAD_ADDRESS.get());
1004
1005        let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1006        DadHandler::<Ipv6, _>::start_duplicate_address_detection(
1007            core_ctx,
1008            bindings_ctx,
1009            &FakeDeviceId,
1010            &addr,
1011        );
1012
1013        check_dad(core_ctx, bindings_ctx, 1, None, RETRANS_TIMER);
1014
1015        let sent_nonce: [u8; MIN_NONCE_LENGTH] = {
1016            let (DadMessageMeta { dst_ip: _, message: _, nonce }, _frame) =
1017                core_ctx.frames().last().expect("should have transmitted a frame");
1018            nonce.clone().try_into().expect("should be nonce of MIN_NONCE_LENGTH")
1019        };
1020
1021        let alternative_nonce = {
1022            let mut nonce = sent_nonce.clone();
1023            nonce[0] = nonce[0].wrapping_add(1);
1024            nonce
1025        };
1026
1027        let incoming_nonce =
1028            NdpNonce::from(if looped_back { &sent_nonce } else { &alternative_nonce });
1029
1030        let matched_nonce = assert_matches!(
1031            DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
1032                core_ctx,
1033                bindings_ctx,
1034                &FakeDeviceId,
1035                &addr,
1036                Some(incoming_nonce),
1037            ),
1038            DadAddressStateLookupResult::Tentative { matched_nonce } => matched_nonce
1039        );
1040
1041        assert_eq!(matched_nonce, looped_back);
1042
1043        let frames_len_before_extra_transmits = core_ctx.frames().len();
1044        assert_eq!(frames_len_before_extra_transmits, 1);
1045
1046        let extra_dad_transmits_required =
1047            NonZero::new(if looped_back { DEFAULT_MAX_MULTICAST_SOLICIT.get() } else { 0 });
1048
1049        let (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns) = assert_matches!(
1050            &core_ctx.state.state,
1051            Ipv6DadState::Tentative {
1052                dad_transmits_remaining,
1053                timer: _,
1054                nonces: _,
1055                added_extra_transmits_after_detecting_looped_back_ns
1056            } => (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns),
1057            "DAD state should be Tentative"
1058        );
1059
1060        assert_eq!(dad_transmits_remaining, &extra_dad_transmits_required);
1061        assert_eq!(added_extra_transmits_after_detecting_looped_back_ns, &looped_back);
1062
1063        let extra_dad_transmits_required =
1064            extra_dad_transmits_required.map(|n| n.get()).unwrap_or(0);
1065
1066        // The retransmit timer should have been kicked when we observed the matching nonce.
1067        assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1068
1069        // Even though we originally required only 1 DAD transmit, MAX_MULTICAST_SOLICIT more
1070        // should be required as a result of the looped back solicitation.
1071        for count in 0..extra_dad_transmits_required {
1072            check_dad(
1073                core_ctx,
1074                bindings_ctx,
1075                usize::from(count) + frames_len_before_extra_transmits + 1,
1076                NonZeroU16::new(extra_dad_transmits_required - count - 1),
1077                RETRANS_TIMER,
1078            );
1079            assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1080        }
1081        let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1082        assert_matches!(*state, Ipv6DadState::Assigned);
1083        let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1084        assert!(*assigned);
1085        assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
1086        assert_eq!(
1087            bindings_ctx.take_events(),
1088            &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
1089        );
1090    }
1091}