1use 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#[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 #[cfg(any(test, feature = "testutils"))]
41 pub fn new(device_id: D, addr: A) -> Self {
42 Self { device_id, addr }
43 }
44}
45
46pub struct DadAddressStateRef<'a, CC, BT: DadBindingsTypes> {
48 pub dad_state: &'a mut Ipv6DadState<BT>,
52 pub core_ctx: &'a mut CC,
54}
55
56pub struct DadStateRef<'a, CC, BT: DadBindingsTypes> {
58 pub state: DadAddressStateRef<'a, CC, BT>,
60 pub retrans_timer: &'a NonZeroDuration,
62 pub max_dad_transmits: &'a Option<NonZeroU16>,
64}
65
66pub trait DadAddressContext<BC>: IpDeviceAddressIdContext<Ipv6> {
68 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 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 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
94pub 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 type DadAddressCtx<'a>: DadAddressContext<
103 BC,
104 DeviceId = Self::DeviceId,
105 AddressId = Self::AddressId,
106 >;
107
108 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 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
130const MAX_DAD_PROBE_NONCES_STORED: usize = 4;
134
135#[derive(Default, Debug)]
137pub struct NonceCollection {
138 nonces: ArrayVec<[u8; MIN_NONCE_LENGTH], MAX_DAD_PROBE_NONCES_STORED>,
139}
140
141impl NonceCollection {
142 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 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)]
175pub enum DadEvent<DeviceId> {
177 AddressAssigned {
179 device: DeviceId,
181 addr: UnicastAddr<Ipv6Addr>,
183 },
184}
185
186pub trait DadBindingsTypes: TimerBindingsTypes {}
188impl<BT> DadBindingsTypes for BT where BT: TimerBindingsTypes {}
189
190pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum DadAddressStateLookupResult {
207 Uninitialized,
208 Tentative { matched_nonce: bool },
209 Assigned,
210}
211
212pub trait DadHandler<I: IpDeviceIpExt, BC>:
214 DeviceIdContext<AnyDevice> + IpDeviceAddressIdContext<I>
215{
216 const INITIAL_ADDRESS_STATE: IpAddressState;
220
221 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 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 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 *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 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 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 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
401impl<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 let _: Option<_> = bindings_ctx.cancel_timer(timer);
471 true
472 }
473 Ipv6DadState::Uninitialized => false,
474 };
475
476 *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 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 *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 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1068
1069 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}