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 fn initialize_duplicate_address_detection<'a>(
221 &mut self,
222 bindings_ctx: &mut BC,
223 device_id: &'a Self::DeviceId,
224 addr: &'a Self::AddressId,
225 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId>;
226
227 fn start_duplicate_address_detection<'a>(
232 &mut self,
233 bindings_ctx: &mut BC,
234 start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
235 );
236
237 fn stop_duplicate_address_detection(
241 &mut self,
242 bindings_ctx: &mut BC,
243 device_id: &Self::DeviceId,
244 addr: &Self::AddressId,
245 );
246
247 fn handle_incoming_dad_neighbor_solicitation(
255 &mut self,
256 bindings_ctx: &mut BC,
257 device_id: &Self::DeviceId,
258 addr: &Self::AddressId,
259 nonce: Option<NdpNonce<&'_ [u8]>>,
260 ) -> DadAddressStateLookupResult;
261}
262
263#[derive(Debug)]
265pub enum NeedsDad<'a, A, D> {
266 No,
267 Yes(StartDad<'a, A, D>),
268}
269
270impl<'a, A, D> NeedsDad<'a, A, D> {
271 pub(crate) fn into_address_state_and_start_dad(
273 self,
274 ) -> (IpAddressState, Option<StartDad<'a, A, D>>) {
275 match self {
276 NeedsDad::No => (IpAddressState::Assigned, None),
278 NeedsDad::Yes(start_dad) => (IpAddressState::Tentative, Some(start_dad)),
279 }
280 }
281}
282
283#[derive(Debug)]
289pub struct StartDad<'a, A, D> {
290 address_id: &'a A,
291 device_id: &'a D,
292}
293
294fn initialize_duplicate_address_detection<
296 'a,
297 BC: DadBindingsContext<CC::OuterEvent>,
298 CC: DadContext<BC>,
299>(
300 core_ctx: &mut CC,
301 bindings_ctx: &mut BC,
302 device_id: &'a CC::DeviceId,
303 addr: &'a CC::AddressId,
304) -> NeedsDad<'a, CC::AddressId, CC::DeviceId> {
305 core_ctx.with_dad_state(
306 device_id,
307 addr,
308 |DadStateRef { state, retrans_timer: _, max_dad_transmits }| {
309 let DadAddressStateRef { dad_state, core_ctx } = state;
310
311 let needs_dad = match max_dad_transmits {
312 None => {
313 *dad_state = Ipv6DadState::Assigned;
314 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
315 NeedsDad::No
316 }
317 Some(max_dad_transmits) => {
318 *dad_state = Ipv6DadState::Tentative {
322 dad_transmits_remaining: Some(*max_dad_transmits),
323 timer: CC::new_timer(
324 bindings_ctx,
325 DadTimerId { device_id: device_id.downgrade(), addr: addr.downgrade() },
326 ),
327 nonces: Default::default(),
328 added_extra_transmits_after_detecting_looped_back_ns: false,
329 };
330 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
331 NeedsDad::Yes(StartDad { device_id, address_id: addr })
332 }
333 };
334
335 core_ctx.join_multicast_group(
346 bindings_ctx,
347 device_id,
348 addr.addr().addr().to_solicited_node_address(),
349 );
350
351 needs_dad
352 },
353 )
354}
355
356fn do_duplicate_address_detection<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>>(
357 core_ctx: &mut CC,
358 bindings_ctx: &mut BC,
359 device_id: &CC::DeviceId,
360 addr: &CC::AddressId,
361) {
362 let nonce_if_should_send_message = core_ctx.with_dad_state(
363 device_id,
364 addr,
365 |DadStateRef { state, retrans_timer, max_dad_transmits: _ }| {
366 let DadAddressStateRef { dad_state, core_ctx } = state;
367
368 let (remaining, timer, nonces) = match dad_state {
369 Ipv6DadState::Tentative {
370 dad_transmits_remaining,
371 timer,
372 nonces,
373 added_extra_transmits_after_detecting_looped_back_ns: _,
374 } => (dad_transmits_remaining, timer, nonces),
375 Ipv6DadState::Uninitialized | Ipv6DadState::Assigned => {
376 panic!("expected address to be tentative; addr={addr:?}")
377 }
378 };
379
380 match remaining {
381 None => {
382 *dad_state = Ipv6DadState::Assigned;
383 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
384 CC::on_event(
385 bindings_ctx,
386 DadEvent::AddressAssigned {
387 device: device_id.clone(),
388 addr: addr.addr_sub().addr().get(),
389 },
390 );
391 None
392 }
393 Some(non_zero_remaining) => {
394 *remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
395
396 assert_eq!(
409 bindings_ctx.schedule_timer(retrans_timer.get(), timer),
410 None,
411 "Unexpected DAD timer; addr={}, device_id={:?}",
412 addr.addr(),
413 device_id
414 );
415 debug!(
416 "performing DAD for {}; {} tries left",
417 addr.addr(),
418 remaining.map_or(0, NonZeroU16::get)
419 );
420 Some(nonces.evicting_create_and_store_nonce(bindings_ctx.rng()))
421 }
422 }
423 },
424 );
425
426 let nonce = match nonce_if_should_send_message {
427 None => return,
428 Some(nonce) => nonce,
429 };
430
431 let dst_ip = addr.addr().addr().to_solicited_node_address();
450 let _: Result<(), _> = core_ctx.send_dad_packet(
451 bindings_ctx,
452 device_id,
453 dst_ip,
454 NeighborSolicitation::new(addr.addr().addr()),
455 NdpNonce::from(&nonce),
456 );
457}
458
459impl<BC, CC> DadHandler<Ipv4, BC> for CC
461where
462 CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
463{
464 fn initialize_duplicate_address_detection<'a>(
465 &mut self,
466 _bindings_ctx: &mut BC,
467 _device_id: &'a Self::DeviceId,
468 _addr: &'a Self::AddressId,
469 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
470 NeedsDad::No
471 }
472
473 fn start_duplicate_address_detection<'a>(
474 &mut self,
475 _bindings_ctx: &mut BC,
476 _start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
477 ) {
478 }
479
480 fn stop_duplicate_address_detection(
481 &mut self,
482 _bindings_ctx: &mut BC,
483 _device_id: &Self::DeviceId,
484 _addr: &Self::AddressId,
485 ) {
486 }
487
488 fn handle_incoming_dad_neighbor_solicitation(
489 &mut self,
490 _bindings_ctx: &mut BC,
491 _device_id: &Self::DeviceId,
492 _addr: &Self::AddressId,
493 _nonce: Option<NdpNonce<&'_ [u8]>>,
494 ) -> DadAddressStateLookupResult {
495 unimplemented!()
496 }
497}
498
499impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> DadHandler<Ipv6, BC> for CC {
500 fn initialize_duplicate_address_detection<'a>(
501 &mut self,
502 bindings_ctx: &mut BC,
503 device_id: &'a Self::DeviceId,
504 addr: &'a Self::AddressId,
505 ) -> NeedsDad<'a, Self::AddressId, Self::DeviceId> {
506 initialize_duplicate_address_detection(self, bindings_ctx, device_id, addr)
507 }
508
509 fn start_duplicate_address_detection<'a>(
510 &mut self,
511 bindings_ctx: &mut BC,
512 start_dad: StartDad<'_, Self::AddressId, Self::DeviceId>,
513 ) {
514 let StartDad { device_id, address_id } = start_dad;
515 do_duplicate_address_detection(self, bindings_ctx, device_id, address_id)
516 }
517
518 fn stop_duplicate_address_detection(
519 &mut self,
520 bindings_ctx: &mut BC,
521 device_id: &Self::DeviceId,
522 addr: &Self::AddressId,
523 ) {
524 self.with_dad_state(
525 device_id,
526 addr,
527 |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
528 let DadAddressStateRef { dad_state, core_ctx } = state;
529
530 let leave_group = match dad_state {
531 Ipv6DadState::Assigned => true,
532 Ipv6DadState::Tentative {
533 dad_transmits_remaining: _,
534 timer,
535 nonces: _,
536 added_extra_transmits_after_detecting_looped_back_ns: _,
537 } => {
538 let _: Option<_> = bindings_ctx.cancel_timer(timer);
543 true
544 }
545 Ipv6DadState::Uninitialized => false,
546 };
547
548 *dad_state = Ipv6DadState::Uninitialized;
555 core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
556 if leave_group {
557 core_ctx.leave_multicast_group(
558 bindings_ctx,
559 device_id,
560 addr.addr().addr().to_solicited_node_address(),
561 );
562 }
563 },
564 )
565 }
566
567 fn handle_incoming_dad_neighbor_solicitation(
569 &mut self,
570 _bindings_ctx: &mut BC,
571 device_id: &Self::DeviceId,
572 addr: &Self::AddressId,
573 nonce: Option<NdpNonce<&'_ [u8]>>,
574 ) -> DadAddressStateLookupResult {
575 self.with_dad_state(
576 device_id,
577 addr,
578 |DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
579 let DadAddressStateRef { dad_state, core_ctx: _ } = state;
580 match dad_state {
581 Ipv6DadState::Assigned => DadAddressStateLookupResult::Assigned,
582 Ipv6DadState::Tentative {
583 dad_transmits_remaining,
584 nonces,
585 added_extra_transmits_after_detecting_looped_back_ns,
586 timer: _,
587 } => {
588 let matched_nonce =
589 nonce.is_some_and(|nonce| nonces.contains(nonce.bytes()));
590 if matched_nonce
591 && !core::mem::replace(
592 added_extra_transmits_after_detecting_looped_back_ns,
593 true,
594 )
595 {
596 *dad_transmits_remaining =
599 Some(DEFAULT_MAX_MULTICAST_SOLICIT.saturating_add(
600 dad_transmits_remaining.map(NonZero::get).unwrap_or(0),
601 ));
602 }
603 DadAddressStateLookupResult::Tentative { matched_nonce }
604 }
605
606 Ipv6DadState::Uninitialized => DadAddressStateLookupResult::Uninitialized,
607 }
608 },
609 )
610 }
611}
612
613impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> HandleableTimer<CC, BC>
614 for DadTimerId<CC::WeakDeviceId, CC::WeakAddressId>
615{
616 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
617 let Self { device_id, addr } = self;
618 let Some(device_id) = device_id.upgrade() else {
619 return;
620 };
621 let Some(addr_id) = addr.upgrade() else {
622 return;
623 };
624 do_duplicate_address_detection(core_ctx, bindings_ctx, &device_id, &addr_id)
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use alloc::collections::hash_map::{Entry, HashMap};
631 use alloc::vec::Vec;
632 use core::time::Duration;
633
634 use assert_matches::assert_matches;
635 use net_types::ip::{AddrSubnet, IpAddress as _};
636 use net_types::Witness as _;
637 use netstack3_base::testutil::{
638 FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeTimerCtxExt as _, FakeWeakAddressId,
639 FakeWeakDeviceId,
640 };
641 use netstack3_base::{CtxPair, InstantContext as _, SendFrameContext as _, TimerHandler};
642 use packet::EmptyBuf;
643 use packet_formats::icmp::ndp::Options;
644 use test_case::test_case;
645
646 use super::*;
647 use crate::internal::device::Ipv6DeviceAddr;
648
649 struct FakeDadAddressContext {
650 addr: UnicastAddr<Ipv6Addr>,
651 assigned: bool,
652 groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
653 }
654
655 type FakeAddressCtxImpl = FakeCoreCtx<FakeDadAddressContext, (), FakeDeviceId>;
656
657 impl DadAddressContext<FakeBindingsCtxImpl> for FakeAddressCtxImpl {
658 fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
659 &mut self,
660 &FakeDeviceId: &Self::DeviceId,
661 request_addr: &Self::AddressId,
662 cb: F,
663 ) -> O {
664 let FakeDadAddressContext { addr, assigned, .. } = &mut self.state;
665 assert_eq!(*request_addr.addr(), *addr);
666 cb(assigned)
667 }
668
669 fn join_multicast_group(
670 &mut self,
671 _bindings_ctx: &mut FakeBindingsCtxImpl,
672 &FakeDeviceId: &Self::DeviceId,
673 multicast_addr: MulticastAddr<Ipv6Addr>,
674 ) {
675 *self.state.groups.entry(multicast_addr).or_default() += 1;
676 }
677
678 fn leave_multicast_group(
679 &mut self,
680 _bindings_ctx: &mut FakeBindingsCtxImpl,
681 &FakeDeviceId: &Self::DeviceId,
682 multicast_addr: MulticastAddr<Ipv6Addr>,
683 ) {
684 match self.state.groups.entry(multicast_addr) {
685 Entry::Vacant(_) => {}
686 Entry::Occupied(mut e) => {
687 let v = e.get_mut();
688 const COUNT_BEFORE_REMOVE: usize = 1;
689 if *v == COUNT_BEFORE_REMOVE {
690 assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
691 } else {
692 *v -= 1
693 }
694 }
695 }
696 }
697 }
698
699 struct FakeDadContext {
700 state: Ipv6DadState<FakeBindingsCtxImpl>,
701 retrans_timer: NonZeroDuration,
702 max_dad_transmits: Option<NonZeroU16>,
703 address_ctx: FakeAddressCtxImpl,
704 }
705
706 #[derive(Debug)]
707 struct DadMessageMeta {
708 dst_ip: MulticastAddr<Ipv6Addr>,
709 message: NeighborSolicitation,
710 nonce: Vec<u8>,
711 }
712
713 type TestDadTimerId = DadTimerId<
714 FakeWeakDeviceId<FakeDeviceId>,
715 FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>,
716 >;
717
718 type FakeBindingsCtxImpl = FakeBindingsCtx<TestDadTimerId, DadEvent<FakeDeviceId>, (), ()>;
719
720 type FakeCoreCtxImpl = FakeCoreCtx<FakeDadContext, DadMessageMeta, FakeDeviceId>;
721
722 fn get_address_id(addr: Ipv6Addr) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
723 AddrSubnet::new(addr, Ipv6Addr::BYTES * 8).unwrap()
724 }
725
726 impl CoreTimerContext<TestDadTimerId, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
727 fn convert_timer(dispatch_id: TestDadTimerId) -> TestDadTimerId {
728 dispatch_id
729 }
730 }
731
732 impl CoreEventContext<DadEvent<FakeDeviceId>> for FakeCoreCtxImpl {
733 type OuterEvent = DadEvent<FakeDeviceId>;
734 fn convert_event(event: DadEvent<FakeDeviceId>) -> DadEvent<FakeDeviceId> {
735 event
736 }
737 }
738
739 impl DadContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
740 type DadAddressCtx<'a> = FakeAddressCtxImpl;
741
742 fn with_dad_state<
743 O,
744 F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl>) -> O,
745 >(
746 &mut self,
747 &FakeDeviceId: &FakeDeviceId,
748 request_addr: &Self::AddressId,
749 cb: F,
750 ) -> O {
751 let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
752 &mut self.state;
753 let ctx_addr = address_ctx.state.addr;
754 let requested_addr = request_addr.addr().get();
755 assert!(
756 ctx_addr == requested_addr,
757 "invalid address {requested_addr} expected {ctx_addr}"
758 );
759 cb(DadStateRef {
760 state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
761 retrans_timer,
762 max_dad_transmits,
763 })
764 }
765
766 fn send_dad_packet(
767 &mut self,
768 bindings_ctx: &mut FakeBindingsCtxImpl,
769 &FakeDeviceId: &FakeDeviceId,
770 dst_ip: MulticastAddr<Ipv6Addr>,
771 message: NeighborSolicitation,
772 nonce: NdpNonce<&[u8]>,
773 ) -> Result<(), ()> {
774 Ok(self
775 .send_frame(
776 bindings_ctx,
777 DadMessageMeta { dst_ip, message, nonce: nonce.bytes().to_vec() },
778 EmptyBuf,
779 )
780 .unwrap())
781 }
782 }
783
784 const RETRANS_TIMER: NonZeroDuration = NonZeroDuration::new(Duration::from_secs(1)).unwrap();
785
786 const DAD_ADDRESS: UnicastAddr<Ipv6Addr> =
787 unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 1])) };
788
789 type FakeCtx = CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl>;
790
791 #[test]
792 #[should_panic(expected = "expected address to be tentative")]
793 fn panic_non_tentative_address_handle_timer() {
794 let FakeCtx { mut core_ctx, mut bindings_ctx } =
795 FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
796 state: Ipv6DadState::Assigned,
797 retrans_timer: RETRANS_TIMER,
798 max_dad_transmits: None,
799 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
800 addr: DAD_ADDRESS,
801 assigned: false,
802 groups: HashMap::default(),
803 }),
804 }));
805 TimerHandler::handle_timer(
806 &mut core_ctx,
807 &mut bindings_ctx,
808 dad_timer_id(),
809 Default::default(),
810 );
811 }
812
813 #[test]
814 fn dad_disabled() {
815 let FakeCtx { mut core_ctx, mut bindings_ctx } =
816 FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
817 FakeCoreCtxImpl::with_state(FakeDadContext {
818 state: Ipv6DadState::Tentative {
819 dad_transmits_remaining: None,
820 timer: bindings_ctx.new_timer(dad_timer_id()),
821 nonces: Default::default(),
822 added_extra_transmits_after_detecting_looped_back_ns: false,
823 },
824 retrans_timer: RETRANS_TIMER,
825 max_dad_transmits: None,
826 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
827 addr: DAD_ADDRESS,
828 assigned: false,
829 groups: HashMap::default(),
830 }),
831 })
832 });
833 let address_id = get_address_id(DAD_ADDRESS.get());
834 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
835 &mut core_ctx,
836 &mut bindings_ctx,
837 &FakeDeviceId,
838 &address_id,
839 );
840 assert_matches!(start_dad, NeedsDad::No);
841 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
842 assert_matches!(*state, Ipv6DadState::Assigned);
843 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
844 assert!(*assigned);
845 assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
846 assert_eq!(bindings_ctx.take_events(), &[][..]);
847 }
848
849 fn dad_timer_id() -> TestDadTimerId {
850 DadTimerId {
851 addr: FakeWeakAddressId(get_address_id(DAD_ADDRESS.get())),
852 device_id: FakeWeakDeviceId(FakeDeviceId),
853 }
854 }
855
856 fn check_dad(
857 core_ctx: &FakeCoreCtxImpl,
858 bindings_ctx: &FakeBindingsCtxImpl,
859 frames_len: usize,
860 dad_transmits_remaining: Option<NonZeroU16>,
861 retrans_timer: NonZeroDuration,
862 ) {
863 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
864 let nonces = assert_matches!(state, Ipv6DadState::Tentative {
865 dad_transmits_remaining: got,
866 timer: _,
867 nonces,
868 added_extra_transmits_after_detecting_looped_back_ns: _,
869 } => {
870 assert_eq!(
871 *got,
872 dad_transmits_remaining,
873 "got dad_transmits_remaining = {got:?}, \
874 want dad_transmits_remaining = {dad_transmits_remaining:?}");
875 nonces
876 });
877 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
878 assert!(!*assigned);
879 assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
880 let frames = core_ctx.frames();
881 assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
882 let (DadMessageMeta { dst_ip, message, nonce }, frame) =
883 frames.last().expect("should have transmitted a frame");
884
885 assert_eq!(*dst_ip, DAD_ADDRESS.to_solicited_node_address());
886 assert_eq!(*message, NeighborSolicitation::new(DAD_ADDRESS.get()));
887 assert!(nonces.contains(nonce), "should have stored nonce");
888
889 let options = Options::parse(&frame[..]).expect("parse NDP options");
890 assert_eq!(options.iter().count(), 0);
891 bindings_ctx
892 .timers
893 .assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
894 }
895
896 #[test]
897 fn perform_dad() {
898 const DAD_TRANSMITS_REQUIRED: u16 = 5;
899 const RETRANS_TIMER: NonZeroDuration =
900 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
901
902 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
903 FakeCoreCtxImpl::with_state(FakeDadContext {
904 state: Ipv6DadState::Tentative {
905 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
906 timer: bindings_ctx.new_timer(dad_timer_id()),
907 nonces: Default::default(),
908 added_extra_transmits_after_detecting_looped_back_ns: false,
909 },
910 retrans_timer: RETRANS_TIMER,
911 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
912 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
913 addr: DAD_ADDRESS,
914 assigned: false,
915 groups: HashMap::default(),
916 }),
917 })
918 });
919 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
920 let address_id = get_address_id(DAD_ADDRESS.get());
921 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
922 core_ctx,
923 bindings_ctx,
924 &FakeDeviceId,
925 &address_id,
926 );
927 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
928 DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
929
930 for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
931 check_dad(
932 core_ctx,
933 bindings_ctx,
934 usize::from(count + 1),
935 NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
936 RETRANS_TIMER,
937 );
938 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
939 }
940 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
941 assert_matches!(*state, Ipv6DadState::Assigned);
942 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
943 assert!(*assigned);
944 assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
945 assert_eq!(
946 bindings_ctx.take_events(),
947 &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
948 );
949 }
950
951 #[test]
952 fn stop_dad() {
953 const DAD_TRANSMITS_REQUIRED: u16 = 2;
954 const RETRANS_TIMER: NonZeroDuration =
955 NonZeroDuration::new(Duration::from_secs(2)).unwrap();
956
957 let FakeCtx { mut core_ctx, mut bindings_ctx } =
958 FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
959 FakeCoreCtxImpl::with_state(FakeDadContext {
960 state: Ipv6DadState::Tentative {
961 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
962 timer: bindings_ctx.new_timer(dad_timer_id()),
963 nonces: Default::default(),
964 added_extra_transmits_after_detecting_looped_back_ns: false,
965 },
966 retrans_timer: RETRANS_TIMER,
967 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
968 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
969 addr: DAD_ADDRESS,
970 assigned: false,
971 groups: HashMap::default(),
972 }),
973 })
974 });
975 let address_id = get_address_id(DAD_ADDRESS.get());
976 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
977 &mut core_ctx,
978 &mut bindings_ctx,
979 &FakeDeviceId,
980 &address_id,
981 );
982 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
983 DadHandler::<Ipv6, _>::start_duplicate_address_detection(
984 &mut core_ctx,
985 &mut bindings_ctx,
986 token,
987 );
988
989 check_dad(
990 &core_ctx,
991 &bindings_ctx,
992 1,
993 NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
994 RETRANS_TIMER,
995 );
996
997 DadHandler::<Ipv6, _>::stop_duplicate_address_detection(
998 &mut core_ctx,
999 &mut bindings_ctx,
1000 &FakeDeviceId,
1001 &get_address_id(DAD_ADDRESS.get()),
1002 );
1003 bindings_ctx.timers.assert_no_timers_installed();
1004 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1005 assert_matches!(*state, Ipv6DadState::Uninitialized);
1006 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1007 assert!(!*assigned);
1008 assert_eq!(groups, &HashMap::new());
1009 }
1010
1011 #[test_case(true, None ; "assigned with no incoming nonce")]
1012 #[test_case(true, Some([1u8; MIN_NONCE_LENGTH]) ; "assigned with incoming nonce")]
1013 #[test_case(false, None ; "uninitialized with no incoming nonce")]
1014 #[test_case(false, Some([1u8; MIN_NONCE_LENGTH]) ; "uninitialized with incoming nonce")]
1015 fn handle_incoming_dad_neighbor_solicitation_while_not_tentative(
1016 assigned: bool,
1017 nonce: Option<[u8; MIN_NONCE_LENGTH]>,
1018 ) {
1019 const MAX_DAD_TRANSMITS: u16 = 1;
1020 const RETRANS_TIMER: NonZeroDuration =
1021 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1022
1023 let mut ctx = FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
1024 state: if assigned { Ipv6DadState::Assigned } else { Ipv6DadState::Uninitialized },
1025 retrans_timer: RETRANS_TIMER,
1026 max_dad_transmits: NonZeroU16::new(MAX_DAD_TRANSMITS),
1027 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
1028 addr: DAD_ADDRESS,
1029 assigned: false,
1030 groups: HashMap::default(),
1031 }),
1032 }));
1033 let addr = get_address_id(DAD_ADDRESS.get());
1034
1035 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1036
1037 let want_lookup_result = if assigned {
1038 DadAddressStateLookupResult::Assigned
1039 } else {
1040 DadAddressStateLookupResult::Uninitialized
1041 };
1042
1043 assert_eq!(
1044 DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
1045 core_ctx,
1046 bindings_ctx,
1047 &FakeDeviceId,
1048 &addr,
1049 nonce.as_ref().map(NdpNonce::from),
1050 ),
1051 want_lookup_result
1052 );
1053 }
1054
1055 #[test_case(true ; "discards looped back NS")]
1056 #[test_case(false ; "acts on non-looped-back NS")]
1057 fn handle_incoming_dad_neighbor_solicitation_during_tentative(looped_back: bool) {
1058 const DAD_TRANSMITS_REQUIRED: u16 = 1;
1059 const RETRANS_TIMER: NonZeroDuration =
1060 NonZeroDuration::new(Duration::from_secs(1)).unwrap();
1061
1062 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1063 FakeCoreCtxImpl::with_state(FakeDadContext {
1064 state: Ipv6DadState::Tentative {
1065 dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1066 timer: bindings_ctx.new_timer(dad_timer_id()),
1067 nonces: Default::default(),
1068 added_extra_transmits_after_detecting_looped_back_ns: false,
1069 },
1070 retrans_timer: RETRANS_TIMER,
1071 max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
1072 address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
1073 addr: DAD_ADDRESS,
1074 assigned: false,
1075 groups: HashMap::default(),
1076 }),
1077 })
1078 });
1079 let addr = get_address_id(DAD_ADDRESS.get());
1080
1081 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
1082 let address_id = get_address_id(DAD_ADDRESS.get());
1083 let start_dad = DadHandler::<Ipv6, _>::initialize_duplicate_address_detection(
1084 core_ctx,
1085 bindings_ctx,
1086 &FakeDeviceId,
1087 &address_id,
1088 );
1089 let token = assert_matches!(start_dad, NeedsDad::Yes(token) => token);
1090 DadHandler::<Ipv6, _>::start_duplicate_address_detection(core_ctx, bindings_ctx, token);
1091
1092 check_dad(core_ctx, bindings_ctx, 1, None, RETRANS_TIMER);
1093
1094 let sent_nonce: [u8; MIN_NONCE_LENGTH] = {
1095 let (DadMessageMeta { dst_ip: _, message: _, nonce }, _frame) =
1096 core_ctx.frames().last().expect("should have transmitted a frame");
1097 nonce.clone().try_into().expect("should be nonce of MIN_NONCE_LENGTH")
1098 };
1099
1100 let alternative_nonce = {
1101 let mut nonce = sent_nonce.clone();
1102 nonce[0] = nonce[0].wrapping_add(1);
1103 nonce
1104 };
1105
1106 let incoming_nonce =
1107 NdpNonce::from(if looped_back { &sent_nonce } else { &alternative_nonce });
1108
1109 let matched_nonce = assert_matches!(
1110 DadHandler::<Ipv6, _>::handle_incoming_dad_neighbor_solicitation(
1111 core_ctx,
1112 bindings_ctx,
1113 &FakeDeviceId,
1114 &addr,
1115 Some(incoming_nonce),
1116 ),
1117 DadAddressStateLookupResult::Tentative { matched_nonce } => matched_nonce
1118 );
1119
1120 assert_eq!(matched_nonce, looped_back);
1121
1122 let frames_len_before_extra_transmits = core_ctx.frames().len();
1123 assert_eq!(frames_len_before_extra_transmits, 1);
1124
1125 let extra_dad_transmits_required =
1126 NonZero::new(if looped_back { DEFAULT_MAX_MULTICAST_SOLICIT.get() } else { 0 });
1127
1128 let (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns) = assert_matches!(
1129 &core_ctx.state.state,
1130 Ipv6DadState::Tentative {
1131 dad_transmits_remaining,
1132 timer: _,
1133 nonces: _,
1134 added_extra_transmits_after_detecting_looped_back_ns
1135 } => (dad_transmits_remaining, added_extra_transmits_after_detecting_looped_back_ns),
1136 "DAD state should be Tentative"
1137 );
1138
1139 assert_eq!(dad_transmits_remaining, &extra_dad_transmits_required);
1140 assert_eq!(added_extra_transmits_after_detecting_looped_back_ns, &looped_back);
1141
1142 let extra_dad_transmits_required =
1143 extra_dad_transmits_required.map(|n| n.get()).unwrap_or(0);
1144
1145 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1147
1148 for count in 0..extra_dad_transmits_required {
1151 check_dad(
1152 core_ctx,
1153 bindings_ctx,
1154 usize::from(count) + frames_len_before_extra_transmits + 1,
1155 NonZeroU16::new(extra_dad_transmits_required - count - 1),
1156 RETRANS_TIMER,
1157 );
1158 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
1159 }
1160 let FakeDadContext { state, address_ctx, .. } = &core_ctx.state;
1161 assert_matches!(*state, Ipv6DadState::Assigned);
1162 let FakeDadAddressContext { assigned, groups, .. } = &address_ctx.state;
1163 assert!(*assigned);
1164 assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
1165 assert_eq!(
1166 bindings_ctx.take_events(),
1167 &[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
1168 );
1169 }
1170}