1use core::fmt::Debug;
8use core::num::{NonZero, NonZeroU16};
9
10use arrayvec::ArrayVec;
11use derivative::Derivative;
12use log::debug;
13use net_types::ip::{Ip, IpVersionMarker, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
14use net_types::MulticastAddr;
15use netstack3_base::{
16 AnyDevice, CoreEventContext, CoreTimerContext, DeviceIdContext, EventContext, HandleableTimer,
17 IpAddressId as _, IpDeviceAddressIdContext, RngContext, StrongDeviceIdentifier as _,
18 TimerBindingsTypes, TimerContext, WeakDeviceIdentifier,
19};
20use packet_formats::icmp::ndp::options::{NdpNonce, MIN_NONCE_LENGTH};
21use packet_formats::icmp::ndp::NeighborSolicitation;
22use packet_formats::utils::NonZeroDuration;
23
24use crate::internal::device::nud::DEFAULT_MAX_MULTICAST_SOLICIT;
25use crate::internal::device::{IpAddressState, IpDeviceIpExt, WeakIpAddressId};
26
27pub trait DadIpExt: Ip {
29 const DEFAULT_DAD_ENABLED: bool;
32
33 type SentProbeData: Debug;
35 type ReceivedProbeData<'a>;
37 type TentativeState: Debug + Default + Send + Sync;
39 type IncomingProbeResultMeta;
41
42 fn generate_sent_probe_data<BC: RngContext>(
44 state: &mut Self::TentativeState,
45 addr: Self::Addr,
46 bindings_ctx: &mut BC,
47 ) -> Self::SentProbeData;
48}
49
50impl DadIpExt for Ipv4 {
52 const DEFAULT_DAD_ENABLED: bool = false;
67
68 type SentProbeData = ();
69 type ReceivedProbeData<'a> = ();
70 type TentativeState = ();
71 type IncomingProbeResultMeta = ();
72
73 fn generate_sent_probe_data<BC: RngContext>(
74 _state: &mut (),
75 _addr: Ipv4Addr,
76 _bindings_ctx: &mut BC,
77 ) -> Self::SentProbeData {
78 ()
79 }
80}
81
82impl DadIpExt for Ipv6 {
83 const DEFAULT_DAD_ENABLED: bool = true;
89
90 type SentProbeData = Ipv6DadSentProbeData;
91 type ReceivedProbeData<'a> = Option<NdpNonce<&'a [u8]>>;
92 type TentativeState = Ipv6TentativeDadState;
93 type IncomingProbeResultMeta = Ipv6ProbeResultMetadata;
94
95 fn generate_sent_probe_data<BC: RngContext>(
96 state: &mut Ipv6TentativeDadState,
97 addr: Ipv6Addr,
98 bindings_ctx: &mut BC,
99 ) -> Self::SentProbeData {
100 let Ipv6TentativeDadState {
101 nonces,
102 added_extra_transmits_after_detecting_looped_back_ns: _,
103 } = state;
104 Ipv6DadSentProbeData {
105 dst_ip: addr.to_solicited_node_address(),
106 message: NeighborSolicitation::new(addr),
107 nonce: nonces.evicting_create_and_store_nonce(bindings_ctx.rng()),
108 }
109 }
110}
111
112#[derive(Debug)]
114pub struct Ipv6DadSentProbeData {
115 pub dst_ip: MulticastAddr<Ipv6Addr>,
117 pub message: NeighborSolicitation,
119 pub nonce: OwnedNdpNonce,
121}
122
123#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
125pub struct DadTimerId<I: Ip, D: WeakDeviceIdentifier, A: WeakIpAddressId<I::Addr>> {
126 pub(crate) device_id: D,
127 pub(crate) addr: A,
128 _marker: IpVersionMarker<I>,
129}
130
131impl<I: Ip, D: WeakDeviceIdentifier, A: WeakIpAddressId<I::Addr>> DadTimerId<I, D, A> {
132 pub(super) fn device_id(&self) -> &D {
133 let Self { device_id, addr: _, _marker } = self;
134 device_id
135 }
136
137 #[cfg(any(test, feature = "testutils"))]
139 pub fn new(device_id: D, addr: A) -> Self {
140 Self { device_id, addr, _marker: IpVersionMarker::new() }
141 }
142}
143
144pub struct DadAddressStateRef<'a, I: DadIpExt, CC, BT: DadBindingsTypes> {
146 pub dad_state: &'a mut DadState<I, BT>,
148 pub core_ctx: &'a mut CC,
150}
151
152pub struct DadStateRef<'a, I: DadIpExt, CC, BT: DadBindingsTypes> {
154 pub state: DadAddressStateRef<'a, I, CC, BT>,
156 pub retrans_timer: &'a NonZeroDuration,
158 pub max_dad_transmits: &'a Option<NonZeroU16>,
160}
161
162pub trait DadAddressContext<I: Ip, BC>: IpDeviceAddressIdContext<I> {
164 fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
167 &mut self,
168 device_id: &Self::DeviceId,
169 addr: &Self::AddressId,
170 cb: F,
171 ) -> O;
172
173 fn should_perform_dad(&mut self, device_id: &Self::DeviceId, addr: &Self::AddressId) -> bool;
175}
176
177pub trait Ipv6DadAddressContext<BC>: DadAddressContext<Ipv6, BC> {
179 fn join_multicast_group(
181 &mut self,
182 bindings_ctx: &mut BC,
183 device_id: &Self::DeviceId,
184 multicast_addr: MulticastAddr<Ipv6Addr>,
185 );
186
187 fn leave_multicast_group(
189 &mut self,
190 bindings_ctx: &mut BC,
191 device_id: &Self::DeviceId,
192 multicast_addr: MulticastAddr<Ipv6Addr>,
193 );
194}
195
196pub trait DadContext<I: IpDeviceIpExt, BC: DadBindingsTypes>:
198 IpDeviceAddressIdContext<I>
199 + DeviceIdContext<AnyDevice>
200 + CoreTimerContext<DadTimerId<I, Self::WeakDeviceId, Self::WeakAddressId>, BC>
201 + CoreEventContext<DadEvent<I, Self::DeviceId>>
202{
203 type DadAddressCtx<'a>: DadAddressContext<
205 I,
206 BC,
207 DeviceId = Self::DeviceId,
208 AddressId = Self::AddressId,
209 >;
210
211 fn with_dad_state<O, F: FnOnce(DadStateRef<'_, I, Self::DadAddressCtx<'_>, BC>) -> O>(
213 &mut self,
214 device_id: &Self::DeviceId,
215 addr: &Self::AddressId,
216 cb: F,
217 ) -> O;
218
219 fn send_dad_probe(
221 &mut self,
222 bindings_ctx: &mut BC,
223 device_id: &Self::DeviceId,
224 data: I::SentProbeData,
225 );
226}
227
228#[derive(Derivative)]
230#[derivative(Debug(bound = ""))]
231pub enum DadState<I: DadIpExt, BT: DadBindingsTypes> {
232 Assigned,
235
236 #[allow(missing_docs)]
243 Tentative {
244 dad_transmits_remaining: Option<NonZeroU16>,
245 timer: BT::Timer,
246 ip_specific_state: I::TentativeState,
247 },
248
249 Uninitialized,
251}
252
253#[derive(Debug, Default)]
255pub struct Ipv6TentativeDadState {
256 pub nonces: NonceCollection,
260 pub added_extra_transmits_after_detecting_looped_back_ns: bool,
264}
265
266const MAX_DAD_PROBE_NONCES_STORED: usize = 4;
270
271pub type OwnedNdpNonce = [u8; MIN_NONCE_LENGTH];
273
274#[derive(Default, Debug)]
276pub struct NonceCollection {
277 nonces: ArrayVec<OwnedNdpNonce, MAX_DAD_PROBE_NONCES_STORED>,
278}
279
280impl NonceCollection {
281 pub fn evicting_create_and_store_nonce(&mut self, mut rng: impl rand::Rng) -> OwnedNdpNonce {
284 let Self { nonces } = self;
285 loop {
286 let nonce: OwnedNdpNonce = rng.gen();
287 if nonces.iter().any(|stored_nonce| stored_nonce == &nonce) {
288 continue;
289 }
290
291 if nonces.remaining_capacity() == 0 {
292 let _: OwnedNdpNonce = nonces.remove(0);
293 }
294 nonces.push(nonce.clone());
295 break nonce;
296 }
297 }
298
299 pub fn contains(&self, nonce: &[u8]) -> bool {
301 if nonce.len() != MIN_NONCE_LENGTH {
302 return false;
303 }
304
305 let Self { nonces } = self;
306 nonces.iter().any(|stored_nonce| stored_nonce == &nonce)
307 }
308}
309
310#[derive(Debug, Eq, Hash, PartialEq)]
311pub enum DadEvent<I: IpDeviceIpExt, DeviceId> {
313 AddressAssigned {
315 device: DeviceId,
317 addr: I::AssignedWitness,
319 },
320}
321
322pub trait DadBindingsTypes: TimerBindingsTypes {}
324impl<BT> DadBindingsTypes for BT where BT: TimerBindingsTypes {}
325
326pub trait DadBindingsContext<E>:
331 DadBindingsTypes + TimerContext + EventContext<E> + RngContext
332{
333}
334impl<E, BC> DadBindingsContext<E> for BC where
335 BC: DadBindingsTypes + TimerContext + EventContext<E> + RngContext
336{
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum DadIncomingProbeResult<I: DadIpExt> {
342 Uninitialized,
344 Tentative { meta: I::IncomingProbeResultMeta },
348 Assigned,
350}
351
352#[derive(Debug, PartialEq)]
354pub struct Ipv6ProbeResultMetadata {
355 pub(crate) matched_nonce: bool,
359}
360
361pub trait DadHandler<I: IpDeviceIpExt, BC>:
363 DeviceIdContext<AnyDevice> + IpDeviceAddressIdContext<I>
364{
365 fn initialize_duplicate_address_detection<'a>(
370 &mut self,
371 bindings_ctx: &mut BC,
372 device_id: &'a Self::DeviceId,
373 addr: &'a Self::AddressId,
374 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId>;
375
376 fn start_duplicate_address_detection<'a>(
381 &mut self,
382 bindings_ctx: &mut BC,
383 start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
384 );
385
386 fn stop_duplicate_address_detection(
390 &mut self,
391 bindings_ctx: &mut BC,
392 device_id: &Self::DeviceId,
393 addr: &Self::AddressId,
394 );
395
396 fn handle_incoming_probe(
403 &mut self,
404 bindings_ctx: &mut BC,
405 device_id: &Self::DeviceId,
406 addr: &Self::AddressId,
407 data: I::ReceivedProbeData<'_>,
408 ) -> DadIncomingProbeResult<I>;
409}
410
411#[derive(Debug)]
413pub enum NeedsDad<'a, A, D> {
414 No,
415 Yes(StartDad<'a, A, D>),
416}
417
418impl<'a, A, D> NeedsDad<'a, A, D> {
419 pub(crate) fn into_address_state_and_start_dad(
421 self,
422 ) -> (IpAddressState, Option<StartDad<'a, A, D>>) {
423 match self {
424 NeedsDad::No => (IpAddressState::Assigned, None),
426 NeedsDad::Yes(start_dad) => (IpAddressState::Tentative, Some(start_dad)),
427 }
428 }
429}
430
431#[derive(Debug)]
437pub struct StartDad<'a, A, D> {
438 address_id: &'a A,
439 device_id: &'a D,
440}
441
442fn initialize_duplicate_address_detection<
444 'a,
445 I: IpDeviceIpExt,
446 BC: DadBindingsContext<CC::OuterEvent>,
447 CC: DadContext<I, BC>,
448 F: FnOnce(&mut CC::DadAddressCtx<'_>, &mut BC, &CC::DeviceId, &CC::AddressId),
449>(
450 core_ctx: &mut CC,
451 bindings_ctx: &mut BC,
452 device_id: &'a CC::DeviceId,
453 addr: &'a CC::AddressId,
454 on_initialized_cb: F,
455) -> NeedsDad<'a, CC::AddressId, CC::DeviceId> {
456 core_ctx.with_dad_state(
457 device_id,
458 addr,
459 |DadStateRef { state, retrans_timer: _, max_dad_transmits }| {
460 let DadAddressStateRef { dad_state, core_ctx } = state;
461 let needs_dad = match (core_ctx.should_perform_dad(device_id, addr), max_dad_transmits)
462 {
463 (false, _) | (true, None) => {
468 *dad_state = DadState::Assigned;
469 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
470 NeedsDad::No
471 }
472 (true, Some(max_dad_transmits)) => {
473 *dad_state = DadState::Tentative {
474 dad_transmits_remaining: Some(*max_dad_transmits),
475 timer: CC::new_timer(
476 bindings_ctx,
477 DadTimerId {
478 device_id: device_id.downgrade(),
479 addr: addr.downgrade(),
480 _marker: IpVersionMarker::new(),
481 },
482 ),
483 ip_specific_state: Default::default(),
484 };
485 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
486 NeedsDad::Yes(StartDad { device_id, address_id: addr })
487 }
488 };
489
490 on_initialized_cb(core_ctx, bindings_ctx, device_id, addr);
493
494 needs_dad
495 },
496 )
497}
498
499fn do_duplicate_address_detection<
500 I: IpDeviceIpExt,
501 BC: DadBindingsContext<CC::OuterEvent>,
502 CC: DadContext<I, BC>,
503>(
504 core_ctx: &mut CC,
505 bindings_ctx: &mut BC,
506 device_id: &CC::DeviceId,
507 addr: &CC::AddressId,
508) {
509 let should_send_probe = core_ctx.with_dad_state(
510 device_id,
511 addr,
512 |DadStateRef { state, retrans_timer, max_dad_transmits: _ }| {
513 let DadAddressStateRef { dad_state, core_ctx } = state;
514
515 let (remaining, timer, ip_specific_state) = match dad_state {
516 DadState::Tentative { dad_transmits_remaining, timer, ip_specific_state } => {
517 (dad_transmits_remaining, timer, ip_specific_state)
518 }
519 DadState::Uninitialized | DadState::Assigned => {
520 panic!("expected address to be tentative; addr={addr:?}")
521 }
522 };
523
524 match remaining {
525 None => {
526 *dad_state = DadState::Assigned;
527 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
528 CC::on_event(
529 bindings_ctx,
530 DadEvent::AddressAssigned {
531 device: device_id.clone(),
532 addr: addr.addr_sub().addr(),
533 },
534 );
535 None
536 }
537 Some(non_zero_remaining) => {
538 *remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
539
540 assert_eq!(
558 bindings_ctx.schedule_timer(retrans_timer.get(), timer),
559 None,
560 "Unexpected DAD timer; addr={}, device_id={:?}",
561 addr.addr(),
562 device_id
563 );
564 debug!(
565 "performing DAD for {}; {} tries left",
566 addr.addr(),
567 remaining.map_or(0, NonZeroU16::get)
568 );
569 Some(I::generate_sent_probe_data(
570 ip_specific_state,
571 addr.addr().addr(),
572 bindings_ctx,
573 ))
574 }
575 }
576 },
577 );
578
579 if let Some(probe_send_data) = should_send_probe {
580 core_ctx.send_dad_probe(bindings_ctx, device_id, probe_send_data);
581 }
582}
583
584fn stop_duplicate_address_detection<
586 'a,
587 I: IpDeviceIpExt,
588 BC: DadBindingsContext<CC::OuterEvent>,
589 CC: DadContext<I, BC>,
590 F: FnOnce(&mut CC::DadAddressCtx<'_>, &mut BC, &CC::DeviceId, &CC::AddressId),
591>(
592 core_ctx: &mut CC,
593 bindings_ctx: &mut BC,
594 device_id: &'a CC::DeviceId,
595 addr: &'a CC::AddressId,
596 on_stopped_cb: F,
597) {
598 core_ctx.with_dad_state(
599 device_id,
600 addr,
601 |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
602 let DadAddressStateRef { dad_state, core_ctx } = state;
603
604 match dad_state {
605 DadState::Assigned => {}
606 DadState::Tentative { dad_transmits_remaining: _, timer, ip_specific_state: _ } => {
607 let _: Option<_> = bindings_ctx.cancel_timer(timer);
612 }
613 DadState::Uninitialized => return,
615 };
616
617 *dad_state = DadState::Uninitialized;
621 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
622
623 on_stopped_cb(core_ctx, bindings_ctx, device_id, addr)
626 },
627 )
628}
629
630impl<BC, CC> DadHandler<Ipv4, BC> for CC
632where
633 CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
634{
635 fn initialize_duplicate_address_detection<'a>(
636 &mut self,
637 _bindings_ctx: &mut BC,
638 _device_id: &'a Self::DeviceId,
639 _addr: &'a Self::AddressId,
640 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
641 NeedsDad::No
642 }
643
644 fn start_duplicate_address_detection<'a>(
645 &mut self,
646 _bindings_ctx: &mut BC,
647 _start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
648 ) {
649 }
650
651 fn stop_duplicate_address_detection(
652 &mut self,
653 _bindings_ctx: &mut BC,
654 _device_id: &Self::DeviceId,
655 _addr: &Self::AddressId,
656 ) {
657 }
658
659 fn handle_incoming_probe(
660 &mut self,
661 _bindings_ctx: &mut BC,
662 _device_id: &Self::DeviceId,
663 _addr: &Self::AddressId,
664 _data: (),
665 ) -> DadIncomingProbeResult<Ipv4> {
666 unimplemented!()
667 }
668}
669
670impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<Ipv6, BC>> DadHandler<Ipv6, BC> for CC
671where
672 for<'a> CC::DadAddressCtx<'a>: Ipv6DadAddressContext<BC>,
673{
674 fn initialize_duplicate_address_detection<'a>(
675 &mut self,
676 bindings_ctx: &mut BC,
677 device_id: &'a Self::DeviceId,
678 addr: &'a Self::AddressId,
679 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
680 initialize_duplicate_address_detection(
681 self,
682 bindings_ctx,
683 device_id,
684 addr,
685 |core_ctx, bindings_ctx, device_id, addr| {
687 core_ctx.join_multicast_group(
703 bindings_ctx,
704 device_id,
705 addr.addr().addr().to_solicited_node_address(),
706 );
707 },
708 )
709 }
710
711 fn start_duplicate_address_detection<'a>(
712 &mut self,
713 bindings_ctx: &mut BC,
714 start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
715 ) {
716 let StartDad { device_id, address_id } = start_dad;
717 do_duplicate_address_detection(self, bindings_ctx, device_id, address_id)
718 }
719
720 fn stop_duplicate_address_detection(
721 &mut self,
722 bindings_ctx: &mut BC,
723 device_id: &Self::DeviceId,
724 addr: &Self::AddressId,
725 ) {
726 stop_duplicate_address_detection(
727 self,
728 bindings_ctx,
729 device_id,
730 addr,
731 |core_ctx, bindings_ctx, device_id, addr| {
733 core_ctx.leave_multicast_group(
739 bindings_ctx,
740 device_id,
741 addr.addr().addr().to_solicited_node_address(),
742 );
743 },
744 )
745 }
746
747 fn handle_incoming_probe(
751 &mut self,
752 _bindings_ctx: &mut BC,
753 device_id: &Self::DeviceId,
754 addr: &Self::AddressId,
755 data: Option<NdpNonce<&[u8]>>,
756 ) -> DadIncomingProbeResult<Ipv6> {
757 self.with_dad_state(
758 device_id,
759 addr,
760 |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
761 let DadAddressStateRef { dad_state, core_ctx: _ } = state;
762 match dad_state {
763 DadState::Assigned => DadIncomingProbeResult::Assigned,
764 DadState::Tentative {
765 dad_transmits_remaining,
766 timer: _,
767 ip_specific_state:
768 Ipv6TentativeDadState {
769 nonces,
770 added_extra_transmits_after_detecting_looped_back_ns,
771 },
772 } => {
773 let matched_nonce =
774 data.is_some_and(|nonce| nonces.contains(nonce.bytes()));
775 if matched_nonce
776 && !core::mem::replace(
777 added_extra_transmits_after_detecting_looped_back_ns,
778 true,
779 )
780 {
781 *dad_transmits_remaining =
784 Some(DEFAULT_MAX_MULTICAST_SOLICIT.saturating_add(
785 dad_transmits_remaining.map(NonZero::get).unwrap_or(0),
786 ));
787 }
788 DadIncomingProbeResult::Tentative {
789 meta: Ipv6ProbeResultMetadata { matched_nonce },
790 }
791 }
792
793 DadState::Uninitialized => DadIncomingProbeResult::Uninitialized,
794 }
795 },
796 )
797 }
798}
799
800impl<I: IpDeviceIpExt, BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<I, BC>>
801 HandleableTimer<CC, BC> for DadTimerId<I, CC::WeakDeviceId, CC::WeakAddressId>
802{
803 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
804 let Self { device_id, addr, _marker } = self;
805 let Some(device_id) = device_id.upgrade() else {
806 return;
807 };
808 let Some(addr_id) = addr.upgrade() else {
809 return;
810 };
811 do_duplicate_address_detection(core_ctx, bindings_ctx, &device_id, &addr_id)
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use alloc::collections::hash_map::{Entry, HashMap};
818 use core::time::Duration;
819
820 use assert_matches::assert_matches;
821 use ip_test_macro::ip_test;
822 use net_types::ip::{AddrSubnet, IpAddress as _, Ipv4Addr};
823 use net_types::{NonMappedAddr, NonMulticastAddr, SpecifiedAddr, UnicastAddr, Witness as _};
824 use netstack3_base::testutil::{
825 FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakAddressId,
826 FakeWeakDeviceId,
827 };
828 use netstack3_base::{
829 AssignedAddrIpExt, CtxPair, InstantContext as _, Ipv4DeviceAddr, Ipv6DeviceAddr,
830 SendFrameContext as _, TimerHandler,
831 };
832 use packet::EmptyBuf;
833 use packet_formats::icmp::ndp::Options;
834 use test_case::test_case;
835
836 use super::*;
837
838 struct FakeDadAddressContext<I: IpDeviceIpExt> {
839 addr: I::AssignedWitness,
840 assigned: bool,
841 groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
843 should_perform_dad: bool,
844 }
845
846 trait TestDadIpExt: IpDeviceIpExt {
847 const DAD_ADDRESS: Self::AssignedWitness;
848 }
849
850 impl TestDadIpExt for Ipv4 {
851 const DAD_ADDRESS: Ipv4DeviceAddr = unsafe {
852 NonMulticastAddr::new_unchecked(NonMappedAddr::new_unchecked(
853 SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 1])),
854 ))
855 };
856 }
857
858 impl TestDadIpExt for Ipv6 {
859 const DAD_ADDRESS: Ipv6DeviceAddr = unsafe {
860 NonMappedAddr::new_unchecked(UnicastAddr::new_unchecked(Ipv6Addr::new([
861 0xa, 0, 0, 0, 0, 0, 0, 1,
862 ])))
863 };
864 }
865
866 impl<I: TestDadIpExt> Default for FakeDadAddressContext<I> {
867 fn default() -> Self {
868 Self {
869 addr: I::DAD_ADDRESS,
870 assigned: false,
871 groups: Default::default(),
872 should_perform_dad: true,
873 }
874 }
875 }
876
877 type FakeAddressCtxImpl<I> = FakeCoreCtx<FakeDadAddressContext<I>, (), FakeDeviceId>;
878
879 impl<I: IpDeviceIpExt> DadAddressContext<I, FakeBindingsCtxImpl<I>> for FakeAddressCtxImpl<I> {
880 fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
881 &mut self,
882 &FakeDeviceId: &Self::DeviceId,
883 request_addr: &Self::AddressId,
884 cb: F,
885 ) -> O {
886 let FakeDadAddressContext { addr, assigned, .. } = &mut self.state;
887 assert_eq!(request_addr.addr(), *addr);
888 cb(assigned)
889 }
890
891 fn should_perform_dad(
892 &mut self,
893 &FakeDeviceId: &Self::DeviceId,
894 request_addr: &Self::AddressId,
895 ) -> bool {
896 let FakeDadAddressContext { addr, should_perform_dad, .. } = &mut self.state;
897 assert_eq!(request_addr.addr(), *addr);
898 *should_perform_dad
899 }
900 }
901
902 impl Ipv6DadAddressContext<FakeBindingsCtxImpl<Ipv6>> for FakeAddressCtxImpl<Ipv6> {
903 fn join_multicast_group(
904 &mut self,
905 _bindings_ctx: &mut FakeBindingsCtxImpl<Ipv6>,
906 &FakeDeviceId: &Self::DeviceId,
907 multicast_addr: MulticastAddr<Ipv6Addr>,
908 ) {
909 *self.state.groups.entry(multicast_addr).or_default() += 1;
910 }
911
912 fn leave_multicast_group(
913 &mut self,
914 _bindings_ctx: &mut FakeBindingsCtxImpl<Ipv6>,
915 &FakeDeviceId: &Self::DeviceId,
916 multicast_addr: MulticastAddr<Ipv6Addr>,
917 ) {
918 match self.state.groups.entry(multicast_addr) {
919 Entry::Vacant(_) => {}
920 Entry::Occupied(mut e) => {
921 let v = e.get_mut();
922 const COUNT_BEFORE_REMOVE: usize = 1;
923 if *v == COUNT_BEFORE_REMOVE {
924 assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
925 } else {
926 *v -= 1
927 }
928 }
929 }
930 }
931 }
932
933 struct FakeDadContext<I: IpDeviceIpExt> {
934 state: DadState<I, FakeBindingsCtxImpl<I>>,
935 retrans_timer: NonZeroDuration,
936 max_dad_transmits: Option<NonZeroU16>,
937 address_ctx: FakeAddressCtxImpl<I>,
938 }
939
940 type TestDadTimerId<I> = DadTimerId<
941 I,
942 FakeWeakDeviceId<FakeDeviceId>,
943 FakeWeakAddressId<AddrSubnet<<I as Ip>::Addr, <I as AssignedAddrIpExt>::AssignedWitness>>,
944 >;
945
946 type FakeBindingsCtxImpl<I> =
947 FakeBindingsCtx<TestDadTimerId<I>, DadEvent<I, FakeDeviceId>, (), ()>;
948
949 type FakeCoreCtxImpl<I> =
950 FakeCoreCtx<FakeDadContext<I>, <I as DadIpExt>::SentProbeData, FakeDeviceId>;
951
952 fn get_address_id<I: IpDeviceIpExt>(
953 addr: I::AssignedWitness,
954 ) -> AddrSubnet<I::Addr, I::AssignedWitness> {
955 AddrSubnet::from_witness(addr, I::Addr::BYTES * 8).unwrap()
956 }
957
958 impl<I: IpDeviceIpExt> CoreTimerContext<TestDadTimerId<I>, FakeBindingsCtxImpl<I>>
959 for FakeCoreCtxImpl<I>
960 {
961 fn convert_timer(dispatch_id: TestDadTimerId<I>) -> TestDadTimerId<I> {
962 dispatch_id
963 }
964 }
965
966 impl<I: IpDeviceIpExt> CoreEventContext<DadEvent<I, FakeDeviceId>> for FakeCoreCtxImpl<I> {
967 type OuterEvent = DadEvent<I, FakeDeviceId>;
968 fn convert_event(event: DadEvent<I, FakeDeviceId>) -> DadEvent<I, FakeDeviceId> {
969 event
970 }
971 }
972
973 impl<I: IpDeviceIpExt> DadContext<I, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
974 type DadAddressCtx<'a> = FakeAddressCtxImpl<I>;
975
976 fn with_dad_state<
977 O,
978 F: FnOnce(DadStateRef<'_, I, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl<I>>) -> O,
979 >(
980 &mut self,
981 &FakeDeviceId: &FakeDeviceId,
982 request_addr: &Self::AddressId,
983 cb: F,
984 ) -> O {
985 let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
986 &mut self.state;
987 let ctx_addr = address_ctx.state.addr;
988 let requested_addr = request_addr.addr();
989 assert!(
990 ctx_addr == requested_addr,
991 "invalid address {requested_addr} expected {ctx_addr}"
992 );
993 cb(DadStateRef {
994 state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
995 retrans_timer,
996 max_dad_transmits,
997 })
998 }
999
1000 fn send_dad_probe(
1001 &mut self,
1002 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
1003 &FakeDeviceId: &FakeDeviceId,
1004 data: I::SentProbeData,
1005 ) {
1006 self.send_frame(bindings_ctx, data, EmptyBuf).unwrap()
1007 }
1008 }
1009
1010 const RETRANS_TIMER: NonZeroDuration = NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1011
1012 type FakeCtx<I> = CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>>;
1013
1014 #[ip_test(I)]
1015 #[should_panic(expected = "expected address to be tentative")]
1016 fn panic_non_tentative_address_handle_timer<I: TestDadIpExt>() {
1017 let FakeCtx::<I> { mut core_ctx, mut bindings_ctx } =
1018 FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1019 state: DadState::Assigned,
1020 retrans_timer: RETRANS_TIMER,
1021 max_dad_transmits: None,
1022 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1023 }));
1024 TimerHandler::handle_timer(
1025 &mut core_ctx,
1026 &mut bindings_ctx,
1027 dad_timer_id(),
1028 Default::default(),
1029 );
1030 }
1031
1032 #[test]
1034 fn dad_disabled() {
1035 let FakeCtx { mut core_ctx, mut bindings_ctx } =
1036 FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1037 FakeCoreCtxImpl::with_state(FakeDadContext {
1038 state: DadState::Tentative {
1039 dad_transmits_remaining: None,
1040 timer: bindings_ctx.new_timer(dad_timer_id()),
1041 ip_specific_state: Default::default(),
1042 },
1043 retrans_timer: RETRANS_TIMER,
1044 max_dad_transmits: None,
1045 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1046 })
1047 });
1048 let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1049 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1050 &mut core_ctx,
1051 &mut bindings_ctx,
1052 &FakeDeviceId,
1053 &address_id,
1054 );
1055 assert_matches!(start_dad, NeedsDad::No);
1056 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1057 assert_matches!(*state, DadState::Assigned);
1058 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1059 assert!(*assigned);
1060 assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1061 assert_eq!(bindings_ctx.take_events(), &[][..]);
1062 }
1063
1064 fn dad_timer_id<I: TestDadIpExt>() -> TestDadTimerId<I> {
1065 DadTimerId {
1066 addr: FakeWeakAddressId(get_address_id::<I>(I::DAD_ADDRESS)),
1067 device_id: FakeWeakDeviceId(FakeDeviceId),
1068 _marker: IpVersionMarker::new(),
1069 }
1070 }
1071
1072 fn check_dad(
1073 core_ctx: &FakeCoreCtxImpl<Ipv6>,
1074 bindings_ctx: &FakeBindingsCtxImpl<Ipv6>,
1075 frames_len: usize,
1076 dad_transmits_remaining: Option<NonZeroU16>,
1077 retrans_timer: NonZeroDuration,
1078 ) {
1079 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1080 let nonces = assert_matches!(state, DadState::Tentative {
1081 dad_transmits_remaining: got,
1082 timer: _,
1083 ip_specific_state: Ipv6TentativeDadState {
1084 nonces,
1085 added_extra_transmits_after_detecting_looped_back_ns: _,
1086 },
1087 } => {
1088 assert_eq!(
1089 *got,
1090 dad_transmits_remaining,
1091 "got dad_transmits_remaining = {got:?}, \
1092 want dad_transmits_remaining = {dad_transmits_remaining:?}");
1093 nonces
1094 });
1095 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1096 assert!(!*assigned);
1097 assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1098
1099 let frames = core_ctx.frames();
1100 assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
1101 let (Ipv6DadSentProbeData { dst_ip, message, nonce }, frame) =
1102 frames.last().expect("should have transmitted a frame");
1103 assert_eq!(*dst_ip, Ipv6::DAD_ADDRESS.to_solicited_node_address());
1104 assert_eq!(*message, NeighborSolicitation::new(Ipv6::DAD_ADDRESS.get()));
1105 assert!(nonces.contains(nonce), "should have stored nonce");
1106
1107 let options = Options::parse(&frame[..]).expect("parse NDP options");
1108 assert_eq!(options.iter().count(), 0);
1109 bindings_ctx
1110 .timers
1111 .assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
1112 }
1113
1114 #[test]
1116 fn perform_dad() {
1117 const DAD_TRANSMITS_REQUIRED: u16 = 5;
1118 const RETRANS_TIMER: NonZeroDuration =
1119 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1120
1121 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1122 FakeCoreCtxImpl::with_state(FakeDadContext {
1123 state: DadState::Tentative {
1124 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1125 timer: bindings_ctx.new_timer(dad_timer_id()),
1126 ip_specific_state: Default::default(),
1127 },
1128 retrans_timer: RETRANS_TIMER,
1129 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1130 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1131 })
1132 });
1133 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1134 let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1135 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1136 core_ctx,
1137 bindings_ctx,
1138 &FakeDeviceId,
1139 &address_id,
1140 );
1141 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1142 DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1143
1144 for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
1145 check_dad(
1146 core_ctx,
1147 bindings_ctx,
1148 usize::from(count + 1),
1149 NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
1150 RETRANS_TIMER,
1151 );
1152 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1153 }
1154 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1155 assert_matches!(*state, DadState::Assigned);
1156 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1157 assert!(*assigned);
1158 assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1159 assert_eq!(
1160 bindings_ctx.take_events(),
1161 &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: Ipv6::DAD_ADDRESS }][..]
1162 );
1163 }
1164
1165 #[test]
1167 fn stop_dad() {
1168 const DAD_TRANSMITS_REQUIRED: u16 = 2;
1169 const RETRANS_TIMER: NonZeroDuration =
1170 NonZeroDuration::new(Duration::from_secs(2)).unwrap();
1171
1172 let FakeCtx { mut core_ctx, mut bindings_ctx } =
1173 FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1174 FakeCoreCtxImpl::with_state(FakeDadContext {
1175 state: DadState::Tentative {
1176 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1177 timer: bindings_ctx.new_timer(dad_timer_id()),
1178 ip_specific_state: Default::default(),
1179 },
1180 retrans_timer: RETRANS_TIMER,
1181 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1182 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1183 })
1184 });
1185 let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1186 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1187 &mut core_ctx,
1188 &mut bindings_ctx,
1189 &FakeDeviceId,
1190 &address_id,
1191 );
1192 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1193 DadHandler::<Ipv6, _>::start_duplicate_address_detection(
1194 &mut core_ctx,
1195 &mut bindings_ctx,
1196 token,
1197 );
1198
1199 check_dad(
1200 &core_ctx,
1201 &bindings_ctx,
1202 1,
1203 NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
1204 RETRANS_TIMER,
1205 );
1206
1207 DadHandler::<Ipv6, _>::stop_duplicate_address_detection(
1208 &mut core_ctx,
1209 &mut bindings_ctx,
1210 &FakeDeviceId,
1211 &get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS),
1212 );
1213 bindings_ctx.timers.assert_no_timers_installed();
1214 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1215 assert_matches!(*state, DadState::Uninitialized);
1216 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1217 assert!(!*assigned);
1218 assert_eq!(groups, &HashMap::new());
1219 }
1220
1221 #[test_case(true, None ; "assigned with no incoming nonce")]
1223 #[test_case(true, Some([1u8; MIN_NONCE_LENGTH]) ; "assigned with incoming nonce")]
1224 #[test_case(false, None ; "uninitialized with no incoming nonce")]
1225 #[test_case(false, Some([1u8; MIN_NONCE_LENGTH]) ; "uninitialized with incoming nonce")]
1226 fn handle_incoming_dad_neighbor_solicitation_while_not_tentative(
1227 assigned: bool,
1228 nonce: Option<OwnedNdpNonce>,
1229 ) {
1230 const MAX_DAD_TRANSMITS: u16 = 1;
1231 const RETRANS_TIMER: NonZeroDuration =
1232 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1233
1234 let mut ctx = FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1235 state: if assigned { DadState::Assigned } else { DadState::Uninitialized },
1236 retrans_timer: RETRANS_TIMER,
1237 max_dad_transmits: NonZeroU16::new(MAX_DAD_TRANSMITS),
1238 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1239 }));
1240 let addr = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1241
1242 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1243
1244 let want_lookup_result = if assigned {
1245 DadIncomingProbeResult::Assigned
1246 } else {
1247 DadIncomingProbeResult::Uninitialized
1248 };
1249
1250 assert_eq!(
1251 DadHandler::<Ipv6, _>::handle_incoming_probe(
1252 core_ctx,
1253 bindings_ctx,
1254 &FakeDeviceId,
1255 &addr,
1256 nonce.as_ref().map(NdpNonce::from),
1257 ),
1258 want_lookup_result
1259 );
1260 }
1261
1262 #[test_case(true ; "discards looped back NS")]
1264 #[test_case(false ; "acts on non-looped-back NS")]
1265 fn handle_incoming_dad_neighbor_solicitation_during_tentative(looped_back: bool) {
1266 const DAD_TRANSMITS_REQUIRED: u16 = 1;
1267 const RETRANS_TIMER: NonZeroDuration =
1268 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1269
1270 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1271 FakeCoreCtxImpl::with_state(FakeDadContext {
1272 state: DadState::Tentative {
1273 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1274 timer: bindings_ctx.new_timer(dad_timer_id()),
1275 ip_specific_state: Default::default(),
1276 },
1277 retrans_timer: RETRANS_TIMER,
1278 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1279 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext::default()),
1280 })
1281 });
1282 let addr = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1283
1284 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1285 let address_id = get_address_id::<Ipv6>(Ipv6::DAD_ADDRESS);
1286 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1287 core_ctx,
1288 bindings_ctx,
1289 &FakeDeviceId,
1290 &address_id,
1291 );
1292 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1293 DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1294
1295 check_dad(core_ctx, bindings_ctx, 1, None, RETRANS_TIMER);
1296
1297 let sent_nonce: OwnedNdpNonce = {
1298 let (Ipv6DadSentProbeData { dst_ip: _, message: _, nonce }, _frame) =
1299 core_ctx.frames().last().expect("should have transmitted a frame");
1300 *nonce
1301 };
1302
1303 let alternative_nonce = {
1304 let mut nonce = sent_nonce.clone();
1305 nonce[0] = nonce[0].wrapping_add(1);
1306 nonce
1307 };
1308
1309 let incoming_nonce =
1310 NdpNonce::from(if looped_back { &sent_nonce } else { &alternative_nonce });
1311
1312 let matched_nonce = assert_matches!(
1313 DadHandler::<Ipv6, _>::handle_incoming_probe(
1314 core_ctx,
1315 bindings_ctx,
1316 &FakeDeviceId,
1317 &addr,
1318 Some(incoming_nonce),
1319 ),
1320 DadIncomingProbeResult::Tentative {
1321 meta: Ipv6ProbeResultMetadata {matched_nonce}
1322 } => matched_nonce
1323 );
1324
1325 assert_eq!(matched_nonce, looped_back);
1326
1327 let frames_len_before_extra_transmits = core_ctx.frames().len();
1328 assert_eq!(frames_len_before_extra_transmits, 1);
1329
1330 let extra_dad_transmits_required =
1331 NonZero::new(if looped_back { DEFAULT_MAX_MULTICAST_SOLICIT.get() } else { 0 });
1332
1333 let (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns) = assert_matches!(
1334 &core_ctx.state.state,
1335 DadState::Tentative {
1336 dad_transmits_remaining,
1337 timer: _,
1338 ip_specific_state: Ipv6TentativeDadState {
1339 nonces: _,
1340 added_extra_transmits_after_detecting_looped_back_ns
1341 },
1342 } => (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns),
1343 "DAD state should be Tentative"
1344 );
1345
1346 assert_eq!(dad_transmits_remaining, &extra_dad_transmits_required);
1347 assert_eq!(added_extra_transmits_after_detecting_looped_back_ns, &matched_nonce);
1348
1349 let extra_dad_transmits_required =
1350 extra_dad_transmits_required.map(|n| n.get()).unwrap_or(0);
1351
1352 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1354
1355 for count in 0..extra_dad_transmits_required {
1358 check_dad(
1359 core_ctx,
1360 bindings_ctx,
1361 usize::from(count) + frames_len_before_extra_transmits + 1,
1362 NonZeroU16::new(extra_dad_transmits_required - count - 1),
1363 RETRANS_TIMER,
1364 );
1365 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1366 }
1367 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1368 assert_matches!(*state, DadState::Assigned);
1369 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1370 assert!(*assigned);
1371 assert_eq!(groups, &HashMap::from([(Ipv6::DAD_ADDRESS.to_solicited_node_address(), 1)]));
1372 assert_eq!(
1373 bindings_ctx.take_events(),
1374 &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: Ipv6::DAD_ADDRESS }][..]
1375 );
1376 }
1377}