1use alloc::vec::Vec;
12use core::fmt::Debug;
13use core::marker::PhantomData;
14use core::num::NonZeroU16;
15use core::ops::ControlFlow;
16use core::time::Duration;
17
18use assert_matches::assert_matches;
19use log::{debug, error, trace, warn};
20use net_types::Witness as _;
21use net_types::ip::{AddrSubnet, Ip as _, IpAddress, Ipv6, Ipv6Addr, Subnet};
22use netstack3_base::{
23 AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext, DeviceIdentifier,
24 EventContext, ExistsError, HandleableTimer, Instant, InstantBindingsTypes, InstantContext,
25 LocalTimerHeap, NotFoundError, RngContext, TimerBindingsTypes, TimerContext,
26 WeakDeviceIdentifier,
27};
28use packet_formats::icmp::ndp::NonZeroNdpLifetime;
29use packet_formats::utils::NonZeroDuration;
30use rand::distr::Uniform;
31use rand::{Rng, RngExt as _};
32
33use crate::device::Ipv6AddrSlaacConfig;
34use crate::internal::device::opaque_iid::{IidSecret, OpaqueIid, OpaqueIidNonce};
35use crate::internal::device::state::{
36 Lifetime, PreferredLifetime, SlaacConfig, TemporarySlaacConfig,
37};
38use crate::internal::device::{
39 AddressRemovedReason, IpDeviceEvent, Ipv6DeviceAddr, Ipv6LinkLayerAddr,
40};
41
42const MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE: NonZeroDuration =
46 NonZeroDuration::new(Duration::from_secs(7200)).unwrap();
47
48const REQUIRED_PREFIX_BITS: u8 = 64;
53
54const MAX_LOCAL_REGEN_ATTEMPTS: u8 = 10;
59
60const MAX_AUTOCONFIGURED_ADDRESSES: usize = 16;
64
65#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
67#[allow(missing_docs)]
68pub enum InnerSlaacTimerId {
69 DeprecateSlaacAddress { addr: Ipv6DeviceAddr },
71 InvalidateSlaacAddress { addr: Ipv6DeviceAddr },
73 RegenerateTemporaryAddress { addr_subnet: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> },
76}
77
78pub struct SlaacState<BT: SlaacBindingsTypes> {
80 timers: LocalTimerHeap<InnerSlaacTimerId, (), BT>,
81}
82
83impl<BC: SlaacBindingsTypes + TimerContext> SlaacState<BC> {
84 pub fn new<D: WeakDeviceIdentifier, CC: CoreTimerContext<SlaacTimerId<D>, BC>>(
86 bindings_ctx: &mut BC,
87 device_id: D,
88 ) -> Self {
89 Self {
90 timers: LocalTimerHeap::new_with_context::<_, CC>(
91 bindings_ctx,
92 SlaacTimerId { device_id },
93 ),
94 }
95 }
96
97 #[cfg(any(test, feature = "testutils"))]
99 pub fn timers(&self) -> &LocalTimerHeap<InnerSlaacTimerId, (), BC> {
100 &self.timers
101 }
102}
103
104#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
106pub struct SlaacTimerId<D: WeakDeviceIdentifier> {
107 device_id: D,
108}
109
110impl<D: WeakDeviceIdentifier> SlaacTimerId<D> {
111 pub(super) fn device_id(&self) -> &D {
112 let Self { device_id } = self;
113 device_id
114 }
115
116 #[cfg(any(test, feature = "testutils"))]
118 pub fn new(device_id: D) -> Self {
119 Self { device_id }
120 }
121}
122
123#[derive(Copy, Clone, Debug, Eq, PartialEq)]
125pub struct SlaacAddressEntry<Instant> {
126 pub addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
128 pub config: Ipv6AddrSlaacConfig<Instant>,
130}
131
132pub struct SlaacAddressEntryMut<'a, Instant> {
134 pub addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
136 pub config: &'a mut Ipv6AddrSlaacConfig<Instant>,
138}
139
140pub trait SlaacAddresses<BT: SlaacBindingsTypes> {
142 fn for_each_addr_mut<F: FnMut(SlaacAddressEntryMut<'_, BT::Instant>)>(&mut self, cb: F);
145
146 type AddrsIter<'x>: Iterator<Item = SlaacAddressEntry<BT::Instant>>;
148
149 fn with_addrs<O, F: FnOnce(Self::AddrsIter<'_>) -> O>(&mut self, cb: F) -> O;
151
152 fn add_addr_sub_and_then<O, F: FnOnce(SlaacAddressEntryMut<'_, BT::Instant>, &mut BT) -> O>(
155 &mut self,
156 bindings_ctx: &mut BT,
157 addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
158 config: Ipv6AddrSlaacConfig<BT::Instant>,
159 and_then: F,
160 ) -> Result<O, ExistsError>;
161
162 fn remove_addr(
168 &mut self,
169 bindings_ctx: &mut BT,
170 addr: &Ipv6DeviceAddr,
171 ) -> Result<
172 (AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, Ipv6AddrSlaacConfig<BT::Instant>),
173 NotFoundError,
174 >;
175}
176
177pub struct SlaacConfigAndState<A: Ipv6LinkLayerAddr, BT: SlaacBindingsTypes> {
181 pub config: SlaacConfiguration,
183 pub dad_transmits: Option<NonZeroU16>,
185 pub retrans_timer: Duration,
187 pub link_layer_addr: Option<A>,
191 pub temp_secret_key: IidSecret,
193 pub stable_secret_key: IidSecret,
195 #[allow(missing_docs)]
196 pub _marker: PhantomData<BT>,
197}
198
199pub trait SlaacContext<BC: SlaacBindingsContext<Self::DeviceId>>:
201 DeviceIdContext<AnyDevice>
202{
203 type LinkLayerAddr: Ipv6LinkLayerAddr;
205
206 type SlaacAddrs<'a>: SlaacAddresses<BC> + CounterContext<SlaacCounters> + 'a;
208
209 fn with_slaac_addrs_mut_and_configs<
212 O,
213 F: FnOnce(
214 &mut Self::SlaacAddrs<'_>,
215 SlaacConfigAndState<Self::LinkLayerAddr, BC>,
216 &mut SlaacState<BC>,
217 ) -> O,
218 >(
219 &mut self,
220 device_id: &Self::DeviceId,
221 cb: F,
222 ) -> O;
223
224 fn with_slaac_addrs_mut<O, F: FnOnce(&mut Self::SlaacAddrs<'_>, &mut SlaacState<BC>) -> O>(
226 &mut self,
227 device_id: &Self::DeviceId,
228 cb: F,
229 ) -> O {
230 self.with_slaac_addrs_mut_and_configs(device_id, |addrs, _config, state| cb(addrs, state))
231 }
232}
233
234#[derive(Default)]
236pub struct SlaacCounters {
237 pub generated_slaac_addr_exists: Counter,
239}
240
241fn update_slaac_addr_valid_until<I: Instant>(
252 slaac_config: &mut SlaacConfig<I>,
253 valid_until: Lifetime<I>,
254) {
255 match slaac_config {
256 SlaacConfig::Stable { valid_until: v, .. } => *v = valid_until,
257 SlaacConfig::Temporary(TemporarySlaacConfig { valid_until: v, .. }) => {
258 *v = match valid_until {
259 Lifetime::Finite(v) => v,
260 Lifetime::Infinite => panic!("temporary addresses may not be valid forever"),
261 }
262 }
263 };
264}
265
266pub trait SlaacBindingsTypes: InstantBindingsTypes + TimerBindingsTypes {}
268impl<BT> SlaacBindingsTypes for BT where BT: InstantBindingsTypes + TimerBindingsTypes {}
269
270pub trait SlaacBindingsContext<D>:
272 RngContext + TimerContext + EventContext<IpDeviceEvent<D, Ipv6, Self::Instant>> + SlaacBindingsTypes
273{
274}
275impl<D, BC> SlaacBindingsContext<D> for BC where
276 BC: RngContext
277 + TimerContext
278 + EventContext<IpDeviceEvent<D, Ipv6, Self::Instant>>
279 + SlaacBindingsTypes
280{
281}
282
283pub trait SlaacHandler<BC: InstantContext>: DeviceIdContext<AnyDevice> {
285 fn apply_slaac_update(
295 &mut self,
296 bindings_ctx: &mut BC,
297 device_id: &Self::DeviceId,
298 prefix: Subnet<Ipv6Addr>,
299 preferred_lifetime: Option<NonZeroNdpLifetime>,
300 valid_lifetime: Option<NonZeroNdpLifetime>,
301 );
302
303 fn generate_link_local_address(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
305
306 fn on_address_removed(
310 &mut self,
311 bindings_ctx: &mut BC,
312 device_id: &Self::DeviceId,
313 addr: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
314 addr_config: Ipv6AddrSlaacConfig<BC::Instant>,
315 reason: AddressRemovedReason,
316 );
317
318 fn remove_all_slaac_addresses(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
320}
321
322impl<BC: SlaacBindingsContext<CC::DeviceId>, CC: SlaacContext<BC>> SlaacHandler<BC> for CC {
323 fn apply_slaac_update(
324 &mut self,
325 bindings_ctx: &mut BC,
326 device_id: &Self::DeviceId,
327 subnet: Subnet<Ipv6Addr>,
328 preferred_lifetime: Option<NonZeroNdpLifetime>,
329 valid_lifetime: Option<NonZeroNdpLifetime>,
330 ) {
331 if preferred_lifetime > valid_lifetime {
332 trace!(
336 "receive_ndp_packet: autonomous prefix's preferred lifetime is greater than valid lifetime, ignoring"
337 );
338 return;
339 }
340
341 let mut seen_stable = false;
342 let mut seen_temporary = false;
343
344 let now = bindings_ctx.now();
345 self.with_slaac_addrs_mut_and_configs(device_id, |slaac_addrs, config, slaac_state| {
346 let SlaacConfigAndState { config: device_config, dad_transmits, retrans_timer, .. } =
347 config;
348 let mut num_addresses = 0;
349 slaac_addrs.for_each_addr_mut(|address_entry| {
352 num_addresses += 1;
353 let slaac_type = match apply_slaac_update_to_addr(
354 address_entry,
355 slaac_state,
356 subnet,
357 device_id,
358 valid_lifetime,
359 preferred_lifetime,
360 &device_config,
361 now,
362 retrans_timer,
363 dad_transmits,
364 bindings_ctx,
365 ) {
366 ControlFlow::Break(()) => return,
367 ControlFlow::Continue(slaac_type) => slaac_type,
368 };
369 match slaac_type {
376 SlaacType::Stable => seen_stable = true,
377 SlaacType::Temporary => seen_temporary = true,
378 }
379 });
380
381 let valid_lifetime = match valid_lifetime {
393 Some(valid_lifetime) => valid_lifetime,
394 None => {
395 trace!(
396 "receive_ndp_packet: autonomous prefix has valid \
397 lifetime = 0, ignoring"
398 );
399 return;
400 }
401 };
402
403 let address_types_to_add = (!seen_stable)
404 .then_some({
405 SlaacType::Stable
415 })
416 .into_iter()
417 .chain((!seen_temporary).then_some({
418 SlaacType::Temporary
425 }));
426
427 for slaac_type in address_types_to_add {
428 if num_addresses >= MAX_AUTOCONFIGURED_ADDRESSES {
429 debug!(
430 "Not Adding {slaac_type:?} address: {subnet:?}. \
431 Maximum number of autoconfigured addresses reached on {device_id:?}"
432 );
433 break;
434 }
435 num_addresses += 1;
436 add_slaac_addr_sub::<_, CC>(
437 bindings_ctx,
438 device_id,
439 slaac_addrs,
440 &config,
441 slaac_state,
442 now,
443 SlaacInitConfig::new(slaac_type),
444 valid_lifetime,
445 preferred_lifetime,
446 &subnet,
447 );
448 }
449 });
450 }
451
452 fn generate_link_local_address(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
453 let now = bindings_ctx.now();
454 self.with_slaac_addrs_mut_and_configs(device_id, |addrs, config, slaac_state| {
455 let link_local_subnet =
462 Subnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), REQUIRED_PREFIX_BITS)
463 .expect("link local subnet should be valid");
464 add_slaac_addr_sub::<_, CC>(
465 bindings_ctx,
466 device_id,
467 addrs,
468 &config,
469 slaac_state,
470 now,
471 SlaacInitConfig::new(SlaacType::Stable),
472 NonZeroNdpLifetime::Infinite, Some(NonZeroNdpLifetime::Infinite), &link_local_subnet,
475 );
476 });
477 }
478
479 fn on_address_removed(
480 &mut self,
481 bindings_ctx: &mut BC,
482 device_id: &Self::DeviceId,
483 addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
484 addr_config: Ipv6AddrSlaacConfig<BC::Instant>,
485 reason: AddressRemovedReason,
486 ) {
487 self.with_slaac_addrs_mut_and_configs(device_id, |addrs, config, slaac_state| {
488 on_address_removed_inner::<_, CC>(
489 bindings_ctx,
490 addr_sub,
491 device_id,
492 addrs,
493 config,
494 slaac_state,
495 addr_config,
496 reason,
497 )
498 });
499 }
500
501 fn remove_all_slaac_addresses(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
502 self.with_slaac_addrs_mut(device_id, |slaac_addrs, _| {
503 slaac_addrs
504 .with_addrs(|addrs| addrs.map(|a| a.addr_sub.addr()).collect::<Vec<_>>())
505 .into_iter()
506 .filter_map(|addr| {
507 slaac_addrs.remove_addr(bindings_ctx, &addr).map(Some).unwrap_or_else(
508 |NotFoundError| {
509 None
514 },
515 )
516 })
517 .collect::<Vec<_>>()
518 })
519 .into_iter()
520 .for_each(|(addr, config)| {
521 self.on_address_removed(
522 bindings_ctx,
523 device_id,
524 addr,
525 config,
526 AddressRemovedReason::Manual,
527 )
528 })
529 }
530}
531
532fn on_address_removed_inner<BC: SlaacBindingsContext<CC::DeviceId>, CC: SlaacContext<BC>>(
533 bindings_ctx: &mut BC,
534 addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
535 device_id: &CC::DeviceId,
536 slaac_addrs: &mut CC::SlaacAddrs<'_>,
537 config: SlaacConfigAndState<CC::LinkLayerAddr, BC>,
538 slaac_state: &mut SlaacState<BC>,
539 addr_config: Ipv6AddrSlaacConfig<BC::Instant>,
540 reason: AddressRemovedReason,
541) {
542 let SlaacState { timers } = slaac_state;
543 let preferred_until = timers
544 .cancel(bindings_ctx, &InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() })
545 .map(|(t, ())| t);
546 let _valid_until: Option<(BC::Instant, ())> = timers
547 .cancel(bindings_ctx, &InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() });
548
549 let now = bindings_ctx.now();
550
551 let Ipv6AddrSlaacConfig { inner, preferred_lifetime } = addr_config;
552
553 match inner {
554 SlaacConfig::Temporary(TemporarySlaacConfig {
555 valid_until,
556 creation_time,
557 desync_factor,
558 dad_counter,
559 }) => {
560 let _regen_at: Option<(BC::Instant, ())> = timers.cancel(
561 bindings_ctx,
562 &InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub },
563 );
564
565 match reason {
566 AddressRemovedReason::Manual => return,
567 AddressRemovedReason::DadFailed => {
568 }
570 AddressRemovedReason::Forfeited => {
571 unreachable!("IPv6 addresses should not be forfeited");
574 }
575 }
576
577 let temp_valid_lifetime = match config.config.temporary_address_configuration {
578 TemporarySlaacAddressConfiguration::Enabled {
579 temp_idgen_retries,
580 temp_valid_lifetime,
581 temp_preferred_lifetime: _,
582 } => {
583 if dad_counter >= temp_idgen_retries {
584 return;
585 }
586 temp_valid_lifetime
587 }
588 TemporarySlaacAddressConfiguration::Disabled => return,
589 };
590
591 let preferred_for = match preferred_until.map(|preferred_until| {
599 preferred_until.saturating_duration_since(creation_time) + desync_factor
600 }) {
601 Some(preferred_for) => preferred_for,
602 None => return,
605 };
606
607 let valid_for =
612 NonZeroDuration::new(valid_until.saturating_duration_since(creation_time))
613 .unwrap_or(temp_valid_lifetime);
614
615 add_slaac_addr_sub::<_, CC>(
616 bindings_ctx,
617 device_id,
618 slaac_addrs,
619 &config,
620 slaac_state,
621 now,
622 SlaacInitConfig::Temporary { dad_count: dad_counter + 1 },
623 NonZeroNdpLifetime::Finite(valid_for),
624 NonZeroDuration::new(preferred_for).map(NonZeroNdpLifetime::Finite),
625 &addr_sub.subnet(),
626 );
627 }
628 SlaacConfig::Stable { valid_until, creation_time, regen_counter, dad_counter } => {
629 match reason {
630 AddressRemovedReason::Manual => return,
631 AddressRemovedReason::DadFailed => {
632 }
634 AddressRemovedReason::Forfeited => {
635 unreachable!("IPv6 addresses should not be forfeited");
638 }
639 }
640
641 match config.config.stable_address_configuration {
642 StableSlaacAddressConfiguration::Disabled => return,
645 StableSlaacAddressConfiguration::Enabled { iid_generation } => match iid_generation
646 {
647 IidGenerationConfiguration::Eui64 => return,
650 IidGenerationConfiguration::Opaque { idgen_retries } => {
651 if dad_counter >= idgen_retries {
652 return;
653 }
654 }
655 },
656 }
657
658 let valid_for = match valid_until {
662 Lifetime::Infinite => NonZeroNdpLifetime::Infinite,
663 Lifetime::Finite(valid_until) => {
664 let Some(valid_for) =
665 NonZeroDuration::new(valid_until.saturating_duration_since(creation_time))
666 else {
667 return;
669 };
670 NonZeroNdpLifetime::Finite(valid_for)
671 }
672 };
673
674 let preferred_for = match preferred_lifetime {
680 PreferredLifetime::Deprecated => None,
681 PreferredLifetime::Preferred(lifetime) => match lifetime {
682 Lifetime::Infinite => Some(NonZeroNdpLifetime::Infinite),
683 Lifetime::Finite(preferred_until) => NonZeroDuration::new(
684 preferred_until.saturating_duration_since(creation_time),
685 )
686 .map(NonZeroNdpLifetime::Finite),
687 },
688 };
689
690 add_slaac_addr_sub::<_, CC>(
691 bindings_ctx,
692 device_id,
693 slaac_addrs,
694 &config,
695 slaac_state,
696 now,
697 SlaacInitConfig::Stable { regen_count: regen_counter, dad_count: dad_counter + 1 },
698 valid_for,
699 preferred_for,
700 &addr_sub.subnet(),
701 );
702 }
703 }
704}
705
706fn apply_slaac_update_to_addr<D: DeviceIdentifier, BC: SlaacBindingsContext<D>>(
707 address_entry: SlaacAddressEntryMut<'_, BC::Instant>,
708 state: &mut SlaacState<BC>,
709 subnet: Subnet<Ipv6Addr>,
710 device_id: &D,
711 valid_lifetime: Option<NonZeroNdpLifetime>,
712 addr_preferred_lifetime: Option<NonZeroNdpLifetime>,
713 config: &SlaacConfiguration,
714 now: <BC as InstantBindingsTypes>::Instant,
715 retrans_timer: Duration,
716 dad_transmits: Option<NonZeroU16>,
717 bindings_ctx: &mut BC,
718) -> ControlFlow<(), SlaacType> {
719 let SlaacAddressEntryMut {
720 addr_sub,
721 config: Ipv6AddrSlaacConfig { inner: slaac_config, preferred_lifetime },
722 } = address_entry;
723 let SlaacState { timers } = state;
724 if addr_sub.subnet() != subnet {
725 return ControlFlow::Break(());
726 }
727 let addr = addr_sub.addr();
728 let slaac_type = SlaacType::from(&*slaac_config);
729 trace!(
730 "receive_ndp_packet: already have a {:?} SLAAC address {:?} configured on device {:?}",
731 slaac_type, addr_sub, device_id
732 );
733
734 #[derive(Copy, Clone)]
736 enum ValidLifetimeBound {
737 FromPrefix(Option<NonZeroNdpLifetime>),
738 FromMaxBound(Duration),
739 }
740 impl ValidLifetimeBound {
741 fn get(self) -> Option<NonZeroNdpLifetime> {
743 match self {
744 Self::FromPrefix(d) => d,
745 Self::FromMaxBound(d) => NonZeroDuration::new(d).map(NonZeroNdpLifetime::Finite),
746 }
747 }
748 }
749 let (valid_for, entry_valid_until, preferred_for_and_regen_at) = match slaac_config {
750 SlaacConfig::Stable {
751 valid_until: entry_valid_until,
752 creation_time: _,
753 regen_counter: _,
754 dad_counter: _,
755 } => (
756 ValidLifetimeBound::FromPrefix(valid_lifetime),
757 *entry_valid_until,
758 addr_preferred_lifetime.map(|p| (p, None)),
759 ),
760 SlaacConfig::Temporary(TemporarySlaacConfig {
763 valid_until: entry_valid_until,
764 creation_time,
765 desync_factor,
766 dad_counter: _,
767 }) => {
768 let SlaacConfiguration {
769 stable_address_configuration: _,
770 temporary_address_configuration,
771 } = config;
772 let (valid_for, preferred_for, entry_valid_until) =
773 match temporary_address_configuration {
774 TemporarySlaacAddressConfiguration::Disabled => {
780 (ValidLifetimeBound::FromMaxBound(Duration::ZERO), None, *entry_valid_until)
781 }
782 TemporarySlaacAddressConfiguration::Enabled {
783 temp_preferred_lifetime,
784 temp_valid_lifetime,
785 temp_idgen_retries: _,
786 } => {
787 let preferred_for =
795 addr_preferred_lifetime.and_then(|preferred_lifetime| {
796 temp_preferred_lifetime
797 .get()
798 .checked_sub(now.saturating_duration_since(*creation_time))
799 .and_then(|p| p.checked_sub(*desync_factor))
800 .and_then(NonZeroDuration::new)
801 .map(|d| preferred_lifetime.min_finite_duration(d))
802 });
803 let since_creation = now.saturating_duration_since(*creation_time);
811 let configured_max_lifetime = temp_valid_lifetime.get();
812 let max_valid_lifetime = if since_creation > configured_max_lifetime {
813 Duration::ZERO
814 } else {
815 configured_max_lifetime - since_creation
816 };
817
818 let valid_for = valid_lifetime.map_or(
819 ValidLifetimeBound::FromPrefix(None),
820 |d| match d {
821 NonZeroNdpLifetime::Infinite => {
822 ValidLifetimeBound::FromMaxBound(max_valid_lifetime)
823 }
824 NonZeroNdpLifetime::Finite(d) => {
825 if max_valid_lifetime <= d.get() {
826 ValidLifetimeBound::FromMaxBound(max_valid_lifetime)
827 } else {
828 ValidLifetimeBound::FromPrefix(valid_lifetime)
829 }
830 }
831 },
832 );
833
834 (valid_for, preferred_for, *entry_valid_until)
835 }
836 };
837
838 let preferred_for_and_regen_at = preferred_for.map(|preferred_for| {
839 let SlaacConfiguration {
840 stable_address_configuration: _,
841 temporary_address_configuration,
842 } = config;
843
844 let regen_at = match temporary_address_configuration {
845 TemporarySlaacAddressConfiguration::Disabled => None,
846 TemporarySlaacAddressConfiguration::Enabled {
847 temp_idgen_retries,
848 temp_preferred_lifetime: _,
849 temp_valid_lifetime: _,
850 } => {
851 let regen_advance = regen_advance(
852 *temp_idgen_retries,
853 retrans_timer,
854 dad_transmits.map_or(0, NonZeroU16::get),
855 )
856 .get();
857 preferred_for
870 .get()
871 .checked_sub(regen_advance)
872 .map_or(Some(now), |d| now.checked_add(d))
873 }
874 };
875
876 (NonZeroNdpLifetime::Finite(preferred_for), regen_at)
877 });
878
879 (valid_for, Lifetime::Finite(entry_valid_until), preferred_for_and_regen_at)
880 }
881 };
882
883 let remaining_lifetime = match entry_valid_until {
885 Lifetime::Infinite => Some(Lifetime::Infinite),
886 Lifetime::Finite(entry_valid_until) => entry_valid_until
887 .checked_duration_since(now)
888 .and_then(NonZeroDuration::new)
889 .map(|d| Lifetime::Finite(d)),
890 };
891
892 let preferred_lifetime_updated = match preferred_for_and_regen_at {
899 None => {
900 if preferred_lifetime.is_deprecated() {
901 false
902 } else {
903 *preferred_lifetime = PreferredLifetime::Deprecated;
904 let _: Option<(BC::Instant, ())> =
905 timers.cancel(bindings_ctx, &InnerSlaacTimerId::DeprecateSlaacAddress { addr });
906 let _: Option<(BC::Instant, ())> = timers.cancel(
907 bindings_ctx,
908 &InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub },
909 );
910 true
911 }
912 }
913 Some((preferred_for, regen_at)) => {
914 let timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr };
915 let preferred_instant = Lifetime::from_ndp(now, preferred_for);
916 match preferred_instant {
917 Lifetime::Finite(instant) => {
918 let _previously_scheduled_instant: Option<(BC::Instant, ())> =
919 timers.schedule_instant(bindings_ctx, timer_id, (), instant);
920 }
921 Lifetime::Infinite => {
922 let _previously_scheduled_instant: Option<(BC::Instant, ())> =
923 timers.cancel(bindings_ctx, &timer_id);
924 }
925 };
926 let new_lifetime = PreferredLifetime::Preferred(preferred_instant);
927 let updated = core::mem::replace(preferred_lifetime, new_lifetime) != new_lifetime;
928 let timer_id = InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub };
929 let _prev_regen_at: Option<(BC::Instant, ())> = match regen_at {
930 Some(regen_at) => timers.schedule_instant(bindings_ctx, timer_id, (), regen_at),
931 None => timers.cancel(bindings_ctx, &timer_id),
932 };
933 updated
934 }
935 };
936
937 let valid_for_to_update = match valid_for {
942 ValidLifetimeBound::FromMaxBound(valid_for) => {
943 NonZeroDuration::new(valid_for).map(NonZeroNdpLifetime::Finite)
946 }
947 ValidLifetimeBound::FromPrefix(valid_for) => {
948 match valid_for {
953 Some(NonZeroNdpLifetime::Infinite) => Some(NonZeroNdpLifetime::Infinite),
954 Some(NonZeroNdpLifetime::Finite(v))
955 if v > MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE
956 || remaining_lifetime.map_or(true, |r| r < Lifetime::Finite(v)) =>
957 {
958 Some(NonZeroNdpLifetime::Finite(v))
959 }
960 None | Some(NonZeroNdpLifetime::Finite(_)) => {
961 if remaining_lifetime.map_or(true, |r| {
962 r <= Lifetime::Finite(MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE)
963 }) {
964 None
979 } else {
980 Some(NonZeroNdpLifetime::Finite(MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE))
983 }
984 }
985 }
986 }
987 };
988
989 match valid_for_to_update {
990 Some(valid_for) => match Lifetime::from_ndp(now, valid_for) {
991 Lifetime::Finite(valid_until) => {
992 trace!(
993 "receive_ndp_packet: updating valid lifetime to {:?} for SLAAC address {:?} on device {:?}",
994 valid_until, addr, device_id
995 );
996
997 update_slaac_addr_valid_until(slaac_config, Lifetime::Finite(valid_until));
999
1000 let _: Option<(BC::Instant, ())> = timers.schedule_instant(
1001 bindings_ctx,
1002 InnerSlaacTimerId::InvalidateSlaacAddress { addr },
1003 (),
1004 valid_until,
1005 );
1006 }
1007 Lifetime::Infinite => {
1008 update_slaac_addr_valid_until(slaac_config, Lifetime::Infinite);
1010
1011 let _: Option<(BC::Instant, ())> = timers
1012 .cancel(bindings_ctx, &InnerSlaacTimerId::InvalidateSlaacAddress { addr });
1013 }
1014 },
1015 None => {
1016 trace!(
1017 "receive_ndp_packet: not updating valid lifetime for SLAAC address {:?} on device {:?} as remaining lifetime is less than 2 hours and new valid lifetime ({:?}) is less than remaining lifetime",
1018 addr,
1019 device_id,
1020 valid_for.get()
1021 );
1022 }
1023 }
1024
1025 if preferred_lifetime_updated || valid_for_to_update.is_some() {
1026 bindings_ctx.on_event(IpDeviceEvent::AddressPropertiesChanged {
1027 device: device_id.clone(),
1028 addr: addr_sub.addr().into(),
1029 valid_until: slaac_config.valid_until(),
1030 preferred_lifetime: *preferred_lifetime,
1031 });
1032 }
1033 ControlFlow::Continue(slaac_type)
1034}
1035
1036impl<BC: SlaacBindingsContext<CC::DeviceId>, CC: SlaacContext<BC>> HandleableTimer<CC, BC>
1037 for SlaacTimerId<CC::WeakDeviceId>
1038{
1039 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
1040 let Self { device_id } = self;
1041 let Some(device_id) = device_id.upgrade() else {
1042 return;
1043 };
1044 core_ctx.with_slaac_addrs_mut_and_configs(&device_id, |addrs, config, slaac_state| {
1045 let Some((timer_id, ())) = slaac_state.timers.pop(bindings_ctx) else {
1046 return;
1047 };
1048 match timer_id {
1049 InnerSlaacTimerId::DeprecateSlaacAddress { addr } => {
1050 addrs.for_each_addr_mut(|SlaacAddressEntryMut { addr_sub, config }| {
1051 if addr_sub.addr() == addr {
1052 config.preferred_lifetime = PreferredLifetime::Deprecated;
1053
1054 bindings_ctx.on_event(IpDeviceEvent::AddressPropertiesChanged {
1055 device: device_id.clone(),
1056 addr: addr_sub.addr().into(),
1057 valid_until: config.inner.valid_until(),
1058 preferred_lifetime: config.preferred_lifetime,
1059 });
1060 }
1061 })
1062 }
1063 InnerSlaacTimerId::InvalidateSlaacAddress { addr } => {
1064 let (addr, slaac_config) = match addrs.remove_addr(bindings_ctx, &addr) {
1065 Ok(addr_config) => addr_config,
1066 Err(NotFoundError) => {
1067 #[cfg(test)]
1074 panic!("Failed to remove address {addr} on invalidation");
1075 #[cfg(not(test))]
1076 {
1077 log::warn!(
1078 "failed to remove SLAAC address {addr}, assuming raced \
1079 with user removal"
1080 );
1081 return;
1082 }
1083 }
1084 };
1085
1086 on_address_removed_inner::<_, CC>(
1087 bindings_ctx,
1088 addr,
1089 &device_id,
1090 addrs,
1091 config,
1092 slaac_state,
1093 slaac_config,
1094 AddressRemovedReason::Manual,
1095 );
1096 }
1097 InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet } => {
1098 regenerate_temporary_slaac_addr::<_, CC>(
1099 bindings_ctx,
1100 addrs,
1101 config,
1102 slaac_state,
1103 &device_id,
1104 &addr_subnet,
1105 );
1106 }
1107 }
1108 });
1109 }
1110}
1111
1112#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1115pub enum IidGenerationConfiguration {
1116 Eui64,
1121 Opaque {
1125 idgen_retries: u8,
1129 },
1130}
1131
1132#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
1134pub enum StableSlaacAddressConfiguration {
1135 Enabled {
1137 iid_generation: IidGenerationConfiguration,
1140 },
1141 #[default]
1143 Disabled,
1144}
1145
1146impl StableSlaacAddressConfiguration {
1147 pub const DEFAULT_IDGEN_RETRIES: u8 = 3;
1151
1152 #[cfg(any(test, feature = "testutils"))]
1154 pub const ENABLED_WITH_EUI64: Self =
1155 Self::Enabled { iid_generation: IidGenerationConfiguration::Eui64 };
1156
1157 #[cfg(any(test, feature = "testutils"))]
1159 pub const ENABLED_WITH_OPAQUE_IIDS: Self = Self::Enabled {
1160 iid_generation: IidGenerationConfiguration::Opaque {
1161 idgen_retries: Self::DEFAULT_IDGEN_RETRIES,
1162 },
1163 };
1164}
1165
1166#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
1181pub enum TemporarySlaacAddressConfiguration {
1182 Enabled {
1184 temp_valid_lifetime: NonZeroDuration,
1187
1188 temp_preferred_lifetime: NonZeroDuration,
1191
1192 temp_idgen_retries: u8,
1196 },
1197 #[default]
1199 Disabled,
1200}
1201
1202impl TemporarySlaacAddressConfiguration {
1203 pub const DEFAULT_TEMP_VALID_LIFETIME: NonZeroDuration = NonZeroDuration::from_secs(2 * 24 * 60 * 60u64).unwrap();
1208
1209 pub const DEFAULT_TEMP_PREFERRED_LIFETIME: NonZeroDuration = NonZeroDuration::from_secs(1 * 24 * 60 * 60u64).unwrap();
1214
1215 pub const DEFAULT_TEMP_IDGEN_RETRIES: u8 = 3;
1219
1220 pub fn enabled_with_rfc_defaults() -> Self {
1222 Self::Enabled {
1223 temp_valid_lifetime: Self::DEFAULT_TEMP_VALID_LIFETIME,
1224 temp_preferred_lifetime: Self::DEFAULT_TEMP_PREFERRED_LIFETIME,
1225 temp_idgen_retries: Self::DEFAULT_TEMP_IDGEN_RETRIES,
1226 }
1227 }
1228
1229 pub fn is_enabled(&self) -> bool {
1231 match self {
1232 Self::Enabled { .. } => true,
1233 Self::Disabled => false,
1234 }
1235 }
1236}
1237
1238#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
1240pub struct SlaacConfiguration {
1241 pub stable_address_configuration: StableSlaacAddressConfiguration,
1243
1244 pub temporary_address_configuration: TemporarySlaacAddressConfiguration,
1246}
1247
1248impl SlaacConfiguration {
1249 pub fn update(
1251 &mut self,
1252 SlaacConfigurationUpdate {
1253 stable_address_configuration,
1254 temporary_address_configuration,
1255 }: SlaacConfigurationUpdate,
1256 ) -> SlaacConfigurationUpdate {
1257 fn get_prev_and_update<T>(old: &mut T, update: Option<T>) -> Option<T> {
1258 update.map(|new| core::mem::replace(old, new))
1259 }
1260 SlaacConfigurationUpdate {
1261 stable_address_configuration: get_prev_and_update(
1262 &mut self.stable_address_configuration,
1263 stable_address_configuration,
1264 ),
1265 temporary_address_configuration: get_prev_and_update(
1266 &mut self.temporary_address_configuration,
1267 temporary_address_configuration,
1268 ),
1269 }
1270 }
1271}
1272
1273#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
1277pub struct SlaacConfigurationUpdate {
1278 pub stable_address_configuration: Option<StableSlaacAddressConfiguration>,
1280
1281 pub temporary_address_configuration: Option<TemporarySlaacAddressConfiguration>,
1283}
1284
1285#[derive(PartialEq, Eq)]
1286enum SlaacType {
1287 Stable,
1288 Temporary,
1289}
1290
1291impl Debug for SlaacType {
1292 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1293 match self {
1294 SlaacType::Stable => f.write_str("stable"),
1295 SlaacType::Temporary => f.write_str("temporary"),
1296 }
1297 }
1298}
1299
1300impl<'a, Instant> From<&'a SlaacConfig<Instant>> for SlaacType {
1301 fn from(slaac_config: &'a SlaacConfig<Instant>) -> Self {
1302 match slaac_config {
1303 SlaacConfig::Stable { .. } => SlaacType::Stable,
1304 SlaacConfig::Temporary { .. } => SlaacType::Temporary,
1305 }
1306 }
1307}
1308
1309pub const SLAAC_MIN_REGEN_ADVANCE: NonZeroDuration = NonZeroDuration::from_secs(2).unwrap();
1320
1321fn regen_advance(
1325 temp_idgen_retries: u8,
1326 retrans_timer: Duration,
1327 dad_transmits: u16,
1328) -> NonZeroDuration {
1329 SLAAC_MIN_REGEN_ADVANCE
1335 + retrans_timer
1336 .checked_mul(u32::from(temp_idgen_retries) * u32::from(dad_transmits))
1337 .unwrap_or(Duration::ZERO)
1338}
1339
1340fn desync_factor<R: Rng>(
1356 rng: &mut R,
1357 temp_preferred_lifetime: NonZeroDuration,
1358 regen_advance: NonZeroDuration,
1359) -> Option<Duration> {
1360 let temp_preferred_lifetime = temp_preferred_lifetime.get();
1361
1362 temp_preferred_lifetime.checked_sub(regen_advance.get()).map(|max_desync_factor| {
1377 let max_desync_factor =
1378 core::cmp::min(max_desync_factor, (temp_preferred_lifetime * 2) / 5);
1379 rng.sample(Uniform::new(Duration::ZERO, max_desync_factor).unwrap())
1380 })
1381}
1382
1383fn regenerate_temporary_slaac_addr<BC: SlaacBindingsContext<CC::DeviceId>, CC: SlaacContext<BC>>(
1384 bindings_ctx: &mut BC,
1385 slaac_addrs: &mut CC::SlaacAddrs<'_>,
1386 config_and_state: SlaacConfigAndState<CC::LinkLayerAddr, BC>,
1387 slaac_state: &mut SlaacState<BC>,
1388 device_id: &CC::DeviceId,
1389 addr_subnet: &AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
1390) {
1391 let SlaacConfigAndState { config, .. } = config_and_state;
1392 let SlaacState { timers } = slaac_state;
1393 let now = bindings_ctx.now();
1394
1395 enum Action {
1396 SkipRegen,
1397 Regen { valid_for: NonZeroDuration, preferred_for: Duration },
1398 }
1399
1400 let action = slaac_addrs.with_addrs(|addrs| {
1401 let entry = {
1402 let mut found_entry = None;
1403
1404 for entry in addrs {
1405 if entry.addr_sub.subnet() != addr_subnet.subnet() {
1406 continue;
1407 }
1408
1409 if !entry.config.preferred_lifetime.is_deprecated() {
1422 if let Some((entry, regen_at)) = timers
1423 .get(&InnerSlaacTimerId::RegenerateTemporaryAddress {
1424 addr_subnet: entry.addr_sub,
1425 })
1426 .map(|(instant, ())| (entry, instant))
1427 {
1428 debug!(
1429 "ignoring regen event at {:?} for {:?} since {:?} \
1430 will regenerate after at {:?}",
1431 bindings_ctx.now(),
1432 addr_subnet,
1433 entry.addr_sub.addr(),
1434 regen_at
1435 );
1436 return Action::SkipRegen;
1437 }
1438 }
1439
1440 if &entry.addr_sub == addr_subnet {
1441 assert_matches!(found_entry, None);
1442 found_entry = Some(entry);
1443 }
1444 }
1445
1446 if let Some(entry) = found_entry {
1447 entry
1448 } else {
1449 log::warn!(
1452 "Could not find temporary SLAAC address {addr_subnet}. \
1453 Assuming timer raced with removal"
1454 );
1455 return Action::SkipRegen;
1456 }
1457 };
1458
1459 assert!(
1460 !entry.config.preferred_lifetime.is_deprecated(),
1461 "can't regenerate deprecated address {:?}",
1462 addr_subnet
1463 );
1464
1465 let TemporarySlaacConfig { creation_time, desync_factor, valid_until, dad_counter: _ } =
1466 match entry.config.inner {
1467 SlaacConfig::Temporary(temporary_config) => temporary_config,
1468 SlaacConfig::Stable { .. } => unreachable!(
1469 "can't regenerate a temporary address for {:?}, which is stable",
1470 addr_subnet
1471 ),
1472 };
1473
1474 let temp_valid_lifetime = match config.temporary_address_configuration {
1475 TemporarySlaacAddressConfiguration::Enabled {
1476 temp_valid_lifetime,
1477 temp_preferred_lifetime: _,
1478 temp_idgen_retries: _,
1479 } => temp_valid_lifetime,
1480 TemporarySlaacAddressConfiguration::Disabled => return Action::SkipRegen,
1481 };
1482
1483 let (deprecate_at, ()) = timers
1484 .get(&InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_subnet.addr() })
1485 .unwrap_or_else(|| {
1486 unreachable!(
1487 "temporary SLAAC address {:?} had a regen timer fire but \
1488 does not have a deprecation timer",
1489 addr_subnet.addr()
1490 )
1491 });
1492 let preferred_for = deprecate_at.saturating_duration_since(creation_time) + desync_factor;
1493
1494 let valid_for = valid_until
1499 .checked_duration_since(creation_time)
1500 .and_then(NonZeroDuration::new)
1501 .unwrap_or(temp_valid_lifetime);
1502
1503 Action::Regen { valid_for, preferred_for }
1504 });
1505
1506 match action {
1507 Action::SkipRegen => {}
1508 Action::Regen { valid_for, preferred_for } => add_slaac_addr_sub::<_, CC>(
1509 bindings_ctx,
1510 device_id,
1511 slaac_addrs,
1512 &config_and_state,
1513 slaac_state,
1514 now,
1515 SlaacInitConfig::Temporary { dad_count: 0 },
1516 NonZeroNdpLifetime::Finite(valid_for),
1517 NonZeroDuration::new(preferred_for).map(NonZeroNdpLifetime::Finite),
1518 &addr_subnet.subnet(),
1519 ),
1520 }
1521}
1522
1523#[derive(Copy, Clone, Debug)]
1524enum SlaacInitConfig {
1525 Stable {
1526 regen_count: u8,
1529 dad_count: u8,
1531 },
1532 Temporary {
1533 dad_count: u8,
1534 },
1535}
1536
1537impl SlaacInitConfig {
1538 fn new(slaac_type: SlaacType) -> Self {
1539 match slaac_type {
1540 SlaacType::Stable => Self::Stable { regen_count: 0, dad_count: 0 },
1541 SlaacType::Temporary => Self::Temporary { dad_count: 0 },
1542 }
1543 }
1544}
1545
1546fn has_iana_allowed_iid(address: Ipv6Addr) -> bool {
1552 let mut iid = [0u8; 8];
1553 const U64_SUFFIX_LEN: usize = Ipv6Addr::BYTES as usize - u64::BITS as usize / 8;
1554 iid.copy_from_slice(&address.bytes()[U64_SUFFIX_LEN..]);
1555 let iid = u64::from_be_bytes(iid);
1556 match iid {
1557 0x0000_0000_0000_0000 => false,
1559 0x0200_5EFF_FE00_0000..=0x0200_5EFF_FEFF_FFFF => false,
1564 0xFDFF_FFFF_FFFF_FF80..=0xFDFF_FFFF_FFFF_FFFF => false,
1566
1567 _iid => true,
1569 }
1570}
1571
1572fn generate_stable_address(
1588 prefix: &Subnet<Ipv6Addr>,
1589 iid: &[u8],
1590) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
1591 if prefix.prefix() % 8 != 0 {
1592 unimplemented!(
1593 "generate_stable_address: not implemented for when prefix length is not a multiple of \
1594 8 bits"
1595 );
1596 }
1597
1598 let mut address = prefix.network().ipv6_bytes();
1599 let prefix_len = usize::from(prefix.prefix() / 8);
1600 assert_eq!(address.len() - prefix_len, iid.len());
1601 address[prefix_len..].copy_from_slice(&iid);
1602
1603 let address = AddrSubnet::new(Ipv6Addr::from(address), prefix.prefix()).unwrap();
1604 assert_eq!(address.subnet(), *prefix);
1605
1606 address
1607}
1608
1609fn generate_stable_address_with_opaque_iid(
1612 prefix: &Subnet<Ipv6Addr>,
1613 network_interface: &[u8],
1614 dad_counter: u8,
1615 secret_key: &IidSecret,
1616) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
1617 let iid = OpaqueIid::new(
1618 *prefix,
1619 network_interface,
1620 None::<&[_]>,
1621 OpaqueIidNonce::DadCounter(dad_counter),
1622 secret_key,
1623 );
1624 let prefix_len = prefix.prefix() / 8;
1625 let iid = &iid.to_be_bytes()[..usize::from(Ipv6Addr::BYTES - prefix_len)];
1626
1627 generate_stable_address(prefix, iid)
1628}
1629
1630fn generate_global_temporary_address(
1644 prefix: &Subnet<Ipv6Addr>,
1645 network_interface: &[u8],
1646 seed: u64,
1647 secret_key: &IidSecret,
1648) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
1649 let prefix_len = usize::from(prefix.prefix() / 8);
1650 let mut address = prefix.network().ipv6_bytes();
1651
1652 let interface_identifier = OpaqueIid::new(
1655 *prefix,
1656 network_interface,
1657 None::<[_; 0]>,
1658 OpaqueIidNonce::Random(seed),
1659 secret_key,
1660 );
1661 let suffix_bytes = &interface_identifier.to_be_bytes()[..(address.len() - prefix_len)];
1662 address[prefix_len..].copy_from_slice(suffix_bytes);
1663
1664 let address = AddrSubnet::new(Ipv6Addr::from(address), prefix.prefix()).unwrap();
1665 assert_eq!(address.subnet(), *prefix);
1666
1667 address
1668}
1669
1670fn add_slaac_addr_sub<BC: SlaacBindingsContext<CC::DeviceId>, CC: SlaacContext<BC>>(
1671 bindings_ctx: &mut BC,
1672 device_id: &CC::DeviceId,
1673 slaac_addrs: &mut CC::SlaacAddrs<'_>,
1674 config: &SlaacConfigAndState<CC::LinkLayerAddr, BC>,
1675 slaac_state: &mut SlaacState<BC>,
1676 now: BC::Instant,
1677 slaac_config: SlaacInitConfig,
1678 prefix_valid_for: NonZeroNdpLifetime,
1679 prefix_preferred_for: Option<NonZeroNdpLifetime>,
1680 subnet: &Subnet<Ipv6Addr>,
1681) {
1682 if subnet.prefix() != REQUIRED_PREFIX_BITS {
1683 error!(
1687 "receive_ndp_packet: autonomous prefix length {:?} and interface identifier length {:?} cannot form valid IPv6 address, ignoring",
1688 subnet.prefix(),
1689 REQUIRED_PREFIX_BITS
1690 );
1691 return;
1692 }
1693
1694 struct PreferredForAndRegenAt<Instant>(NonZeroNdpLifetime, Option<Instant>);
1695
1696 let SlaacConfigAndState {
1697 config,
1698 dad_transmits,
1699 retrans_timer,
1700 link_layer_addr,
1701 temp_secret_key,
1702 stable_secret_key,
1703 _marker,
1704 } = config;
1705
1706 let Some(link_layer_addr) = link_layer_addr else {
1707 warn!(
1708 "add_slaac_addr_sub: cannot derive IIDs for device {device_id:?} that does not support \
1709 link-layer addressing"
1710 );
1711 return;
1712 };
1713
1714 let SlaacConfiguration { stable_address_configuration, temporary_address_configuration } =
1715 config;
1716 let SlaacState { timers } = slaac_state;
1717
1718 let (valid_until, preferred_and_regen, mut addresses) = match slaac_config {
1719 SlaacInitConfig::Stable { mut regen_count, dad_count } => {
1720 let iid_generation = match stable_address_configuration {
1721 StableSlaacAddressConfiguration::Disabled => {
1722 trace!("stable SLAAC addresses are disabled on device {:?}", device_id);
1723 return;
1724 }
1725 StableSlaacAddressConfiguration::Enabled { iid_generation } => iid_generation,
1726 };
1727
1728 let valid_until = Lifetime::from_ndp(now, prefix_valid_for);
1729
1730 let addresses = either::Either::Left(match iid_generation {
1738 IidGenerationConfiguration::Eui64 => {
1739 assert_eq!(regen_count, 0);
1743
1744 if dad_count != 0 {
1748 return;
1749 }
1750
1751 let address = generate_stable_address(&subnet, &link_layer_addr.eui64_iid());
1752 let config = SlaacConfig::Stable {
1753 valid_until,
1754 creation_time: now,
1755 regen_counter: regen_count,
1756 dad_counter: dad_count,
1757 };
1758 either::Either::Left(core::iter::once((address, config)))
1759 }
1760 IidGenerationConfiguration::Opaque { idgen_retries: _ } => {
1761 either::Either::Right(core::iter::from_fn(move || {
1762 let mut attempts = 0;
1772 loop {
1773 let address = generate_stable_address_with_opaque_iid(
1774 &subnet,
1775 link_layer_addr.as_bytes(),
1776 regen_count + dad_count,
1784 &stable_secret_key,
1785 );
1786 let config = SlaacConfig::Stable {
1787 valid_until,
1788 creation_time: now,
1789 regen_counter: regen_count,
1790 dad_counter: dad_count,
1791 };
1792
1793 regen_count = regen_count.wrapping_add(1);
1794
1795 if has_iana_allowed_iid(address.addr().get()) {
1796 break Some((address, config));
1797 }
1798
1799 attempts += 1;
1800 if attempts > MAX_LOCAL_REGEN_ATTEMPTS {
1801 return None;
1802 }
1803 }
1804 }))
1805 }
1806 });
1807
1808 (valid_until, prefix_preferred_for.map(|p| PreferredForAndRegenAt(p, None)), addresses)
1809 }
1810 SlaacInitConfig::Temporary { dad_count } => {
1811 match temporary_address_configuration {
1812 TemporarySlaacAddressConfiguration::Disabled => {
1813 trace!(
1814 "receive_ndp_packet: temporary addresses are disabled on device {:?}",
1815 device_id
1816 );
1817 return;
1818 }
1819 TemporarySlaacAddressConfiguration::Enabled {
1820 temp_valid_lifetime,
1821 temp_preferred_lifetime,
1822 temp_idgen_retries,
1823 } => {
1824 let per_attempt_random_seed: u64 = bindings_ctx.rng().random();
1825
1826 let valid_for = match prefix_valid_for {
1838 NonZeroNdpLifetime::Finite(prefix_valid_for) => {
1839 core::cmp::min(prefix_valid_for, *temp_valid_lifetime)
1840 }
1841 NonZeroNdpLifetime::Infinite => *temp_valid_lifetime,
1842 };
1843
1844 let regen_advance = regen_advance(
1845 *temp_idgen_retries,
1846 *retrans_timer,
1847 dad_transmits.map_or(0, NonZeroU16::get),
1848 );
1849
1850 let valid_until = now.saturating_add(valid_for.get());
1851
1852 let desync_factor = if let Some(d) = desync_factor(
1853 &mut bindings_ctx.rng(),
1854 *temp_preferred_lifetime,
1855 regen_advance,
1856 ) {
1857 d
1858 } else {
1859 trace!(
1867 "failed to calculate DESYNC_FACTOR; \
1868 temp_preferred_lifetime={:?}, regen_advance={:?}",
1869 temp_preferred_lifetime, regen_advance,
1870 );
1871 return;
1872 };
1873
1874 let preferred_for = prefix_preferred_for.and_then(|prefix_preferred_for| {
1875 temp_preferred_lifetime
1876 .get()
1877 .checked_sub(desync_factor)
1878 .and_then(NonZeroDuration::new)
1879 .map(|d| prefix_preferred_for.min_finite_duration(d))
1880 });
1881
1882 let preferred_for_and_regen_at = match preferred_for {
1888 None => return,
1889 Some(preferred_for) => {
1890 match preferred_for.get().checked_sub(regen_advance.get()) {
1891 Some(before_regen) => PreferredForAndRegenAt(
1892 NonZeroNdpLifetime::Finite(preferred_for),
1893 now.checked_add(before_regen),
1896 ),
1897 None => {
1898 trace!(
1899 "receive_ndp_packet: preferred lifetime of {:?} \
1900 for subnet {:?} is too short to allow regen",
1901 preferred_for, subnet
1902 );
1903 return;
1904 }
1905 }
1906 }
1907 };
1908
1909 let config = SlaacConfig::Temporary(TemporarySlaacConfig {
1910 desync_factor,
1911 valid_until,
1912 creation_time: now,
1913 dad_counter: dad_count,
1914 });
1915
1916 let mut seed = per_attempt_random_seed;
1917 let addresses = either::Either::Right(core::iter::from_fn(move || {
1918 let mut attempts = 0;
1927 loop {
1928 let address = generate_global_temporary_address(
1929 &subnet,
1930 link_layer_addr.as_bytes(),
1931 seed,
1932 &temp_secret_key,
1933 );
1934 seed = seed.wrapping_add(1);
1935
1936 if has_iana_allowed_iid(address.addr().get()) {
1937 break Some((address, config));
1938 }
1939
1940 attempts += 1;
1941 if attempts > MAX_LOCAL_REGEN_ATTEMPTS {
1942 return None;
1943 }
1944 }
1945 }));
1946
1947 (Lifetime::Finite(valid_until), Some(preferred_for_and_regen_at), addresses)
1948 }
1949 }
1950 }
1951 };
1952
1953 let mut local_regen_attempts = 0;
1955 loop {
1956 let Some((address, slaac_config)) = addresses.next() else {
1957 debug!("exhausted possible SLAAC addresses without assigning on device {device_id:?}");
1959 return;
1960 };
1961
1962 let (preferred_lifetime, regen_at) = match preferred_and_regen {
1967 Some(PreferredForAndRegenAt(preferred_for, regen_at)) => {
1968 (PreferredLifetime::preferred_for(now, preferred_for), regen_at)
1969 }
1970 None => (PreferredLifetime::Deprecated, None),
1971 };
1972 let config = Ipv6AddrSlaacConfig { inner: slaac_config, preferred_lifetime };
1973
1974 let res = slaac_addrs.add_addr_sub_and_then(
1977 bindings_ctx,
1978 address,
1979 config,
1980 |SlaacAddressEntryMut { addr_sub, config: _ }, ctx| {
1981 match valid_until {
1986 Lifetime::Finite(valid_until) => {
1987 assert_eq!(
1988 timers.schedule_instant(
1989 ctx,
1990 InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() },
1991 (),
1992 valid_until,
1993 ),
1994 None
1995 );
1996 }
1997 Lifetime::Infinite => {}
1998 }
1999
2000 let deprecate_timer_id =
2001 InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
2002
2003 match preferred_lifetime {
2004 PreferredLifetime::Preferred(Lifetime::Finite(instant)) => {
2005 assert_eq!(
2006 timers.schedule_instant(ctx, deprecate_timer_id, (), instant,),
2007 None
2008 );
2009 }
2010 PreferredLifetime::Preferred(Lifetime::Infinite) => {}
2011 PreferredLifetime::Deprecated => {
2012 assert_eq!(timers.cancel(ctx, &deprecate_timer_id), None);
2013 }
2014 }
2015
2016 match regen_at {
2017 Some(regen_at) => assert_eq!(
2018 timers.schedule_instant(
2019 ctx,
2020 InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub },
2021 (),
2022 regen_at,
2023 ),
2024 None
2025 ),
2026 None => (),
2027 }
2028 addr_sub
2029 },
2030 );
2031
2032 match res {
2033 Err(ExistsError) => {
2034 trace!("IPv6 SLAAC address {:?} already exists on device {:?}", address, device_id);
2035
2036 slaac_addrs.counters().generated_slaac_addr_exists.increment();
2039 local_regen_attempts += 1;
2040 if local_regen_attempts > MAX_LOCAL_REGEN_ATTEMPTS {
2041 debug!(
2042 "exceeded max local SLAAC addr generation attempts on device {device_id:?}"
2043 );
2044 return;
2045 }
2046 }
2047 Ok(addr_sub) => {
2048 trace!(
2049 "receive_ndp_packet: Successfully configured new IPv6 address {:?} on device {:?} via SLAAC",
2050 addr_sub, device_id
2051 );
2052 break;
2053 }
2054 }
2055 }
2056}
2057
2058#[cfg(any(test, feature = "testutils"))]
2059pub(crate) mod testutil {
2060 use super::*;
2061
2062 use netstack3_hashmap::HashMap;
2063
2064 use net_types::ip::Ipv6;
2065
2066 use crate::internal::device::{IpDeviceBindingsContext, Ipv6DeviceConfigurationContext};
2067
2068 pub fn collect_slaac_timers_integration<CC, BC>(
2071 core_ctx: &mut CC,
2072 device_id: &CC::DeviceId,
2073 ) -> HashMap<InnerSlaacTimerId, BC::Instant>
2074 where
2075 CC: Ipv6DeviceConfigurationContext<BC>,
2076 for<'a> CC::Ipv6DeviceStateCtx<'a>: SlaacContext<BC>,
2077 BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId> + SlaacBindingsContext<CC::DeviceId>,
2078 {
2079 core_ctx.with_ipv6_device_configuration(device_id, |_, mut core_ctx| {
2080 core_ctx.with_slaac_addrs_mut(device_id, |_, state| {
2081 state.timers().iter().map(|(k, (), t)| (*k, *t)).collect::<HashMap<_, _>>()
2082 })
2083 })
2084 }
2085
2086 pub fn calculate_slaac_addr_sub(
2093 subnet: Subnet<Ipv6Addr>,
2094 iid: [u8; 8],
2095 ) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
2096 assert_eq!(subnet.prefix(), 64);
2097 let mut bytes = subnet.network().ipv6_bytes();
2098 bytes[8..].copy_from_slice(&iid);
2099 AddrSubnet::new(Ipv6Addr::from_bytes(bytes), subnet.prefix()).unwrap()
2100 }
2101}
2102
2103#[cfg(test)]
2104mod tests {
2105 use alloc::vec;
2106 use core::convert::TryFrom as _;
2107
2108 use net_declare::net::ip_v6;
2109 use netstack3_base::testutil::{
2110 FakeBindingsCtx, FakeCoreCtx, FakeCryptoRng, FakeDeviceId, FakeInstant,
2111 FakeTimerCtxExt as _, FakeWeakDeviceId, assert_empty,
2112 };
2113 use netstack3_base::{CtxPair, IntoCoreTimerCtx};
2114 use netstack3_hashmap::HashSet;
2115 use test_case::test_case;
2116
2117 use super::*;
2118
2119 fn calculate_stable_slaac_addr_sub_with_opaque_iid(
2126 subnet: Subnet<Ipv6Addr>,
2127 network_interface: impl AsRef<[u8]>,
2128 dad_counter: u8,
2129 ) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
2130 let iid = OpaqueIid::new(
2131 subnet,
2132 network_interface.as_ref(),
2133 None::<&[_]>,
2134 OpaqueIidNonce::DadCounter(dad_counter),
2135 &STABLE_SECRET_KEY,
2136 );
2137 let iid = &iid.to_be_bytes()[..8];
2138 testutil::calculate_slaac_addr_sub(subnet, iid.try_into().unwrap())
2139 }
2140
2141 struct FakeSlaacContext {
2142 config: SlaacConfiguration,
2143 dad_transmits: Option<NonZeroU16>,
2144 retrans_timer: Duration,
2145 slaac_addrs: FakeSlaacAddrs,
2146 slaac_state: SlaacState<FakeBindingsCtxImpl>,
2147 }
2148
2149 type FakeCoreCtxImpl = FakeCoreCtx<FakeSlaacContext, (), FakeDeviceId>;
2150 type FakeBindingsCtxImpl = FakeBindingsCtx<
2151 SlaacTimerId<FakeWeakDeviceId<FakeDeviceId>>,
2152 IpDeviceEvent<FakeDeviceId, Ipv6, FakeInstant>,
2153 (),
2154 (),
2155 >;
2156
2157 struct FakeLinkLayerAddr;
2158
2159 const IID: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
2160
2161 impl Ipv6LinkLayerAddr for FakeLinkLayerAddr {
2162 fn as_bytes(&self) -> &[u8] {
2163 &IID
2164 }
2165
2166 fn eui64_iid(&self) -> [u8; 8] {
2167 IID
2168 }
2169 }
2170
2171 #[derive(Default)]
2172 struct FakeSlaacAddrs {
2173 slaac_addrs: Vec<SlaacAddressEntry<FakeInstant>>,
2174 non_slaac_addrs: Vec<Ipv6DeviceAddr>,
2175 counters: SlaacCounters,
2176 }
2177
2178 impl<'a> CounterContext<SlaacCounters> for &'a mut FakeSlaacAddrs {
2179 fn counters(&self) -> &SlaacCounters {
2180 &self.counters
2181 }
2182 }
2183
2184 impl<'a> SlaacAddresses<FakeBindingsCtxImpl> for &'a mut FakeSlaacAddrs {
2185 fn for_each_addr_mut<F: FnMut(SlaacAddressEntryMut<'_, FakeInstant>)>(
2186 &mut self,
2187 mut cb: F,
2188 ) {
2189 let FakeSlaacAddrs { slaac_addrs, non_slaac_addrs: _, counters: _ } = self;
2190 slaac_addrs.iter_mut().for_each(|SlaacAddressEntry { addr_sub, config }| {
2191 cb(SlaacAddressEntryMut { addr_sub: *addr_sub, config })
2192 })
2193 }
2194
2195 type AddrsIter<'b> =
2196 core::iter::Cloned<core::slice::Iter<'b, SlaacAddressEntry<FakeInstant>>>;
2197 fn with_addrs<O, F: FnOnce(Self::AddrsIter<'_>) -> O>(&mut self, cb: F) -> O {
2198 let FakeSlaacAddrs { slaac_addrs, non_slaac_addrs: _, counters: _ } = self;
2199 cb(slaac_addrs.iter().cloned())
2200 }
2201
2202 fn add_addr_sub_and_then<
2203 O,
2204 F: FnOnce(SlaacAddressEntryMut<'_, FakeInstant>, &mut FakeBindingsCtxImpl) -> O,
2205 >(
2206 &mut self,
2207 bindings_ctx: &mut FakeBindingsCtxImpl,
2208 add_addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
2209 config: Ipv6AddrSlaacConfig<FakeInstant>,
2210 and_then: F,
2211 ) -> Result<O, ExistsError> {
2212 let FakeSlaacAddrs { slaac_addrs, non_slaac_addrs, counters: _ } = self;
2213
2214 if non_slaac_addrs.iter().any(|a| *a == add_addr_sub.addr()) {
2215 return Err(ExistsError);
2216 }
2217
2218 if slaac_addrs.iter_mut().any(|e| e.addr_sub.addr() == add_addr_sub.addr()) {
2219 return Err(ExistsError);
2220 }
2221
2222 slaac_addrs.push(SlaacAddressEntry { addr_sub: add_addr_sub, config });
2223
2224 let SlaacAddressEntry { addr_sub, config } = slaac_addrs.iter_mut().last().unwrap();
2225
2226 Ok(and_then(SlaacAddressEntryMut { addr_sub: *addr_sub, config }, bindings_ctx))
2227 }
2228
2229 fn remove_addr(
2230 &mut self,
2231 _bindings_ctx: &mut FakeBindingsCtxImpl,
2232 addr: &Ipv6DeviceAddr,
2233 ) -> Result<
2234 (AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, Ipv6AddrSlaacConfig<FakeInstant>),
2235 NotFoundError,
2236 > {
2237 let FakeSlaacAddrs { slaac_addrs, non_slaac_addrs: _, counters: _ } = self;
2238
2239 slaac_addrs
2240 .iter()
2241 .enumerate()
2242 .find_map(|(i, a)| (&a.addr_sub.addr() == addr).then(|| i))
2243 .ok_or(NotFoundError)
2244 .map(|i| {
2245 let SlaacAddressEntry { addr_sub, config } = slaac_addrs.remove(i);
2246 (addr_sub, config)
2247 })
2248 }
2249 }
2250
2251 impl SlaacContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
2252 type LinkLayerAddr = FakeLinkLayerAddr;
2253
2254 type SlaacAddrs<'a>
2255 = &'a mut FakeSlaacAddrs
2256 where
2257 FakeCoreCtxImpl: 'a;
2258
2259 fn with_slaac_addrs_mut_and_configs<
2260 O,
2261 F: FnOnce(
2262 &mut Self::SlaacAddrs<'_>,
2263 SlaacConfigAndState<FakeLinkLayerAddr, FakeBindingsCtxImpl>,
2264 &mut SlaacState<FakeBindingsCtxImpl>,
2265 ) -> O,
2266 >(
2267 &mut self,
2268 &FakeDeviceId: &FakeDeviceId,
2269 cb: F,
2270 ) -> O {
2271 let FakeSlaacContext {
2272 config,
2273 dad_transmits,
2274 retrans_timer,
2275 slaac_addrs,
2276 slaac_state,
2277 ..
2278 } = &mut self.state;
2279 let mut slaac_addrs = slaac_addrs;
2280 cb(
2281 &mut slaac_addrs,
2282 SlaacConfigAndState {
2283 config: *config,
2284 dad_transmits: *dad_transmits,
2285 retrans_timer: *retrans_timer,
2286 link_layer_addr: Some(FakeLinkLayerAddr),
2287 temp_secret_key: TEMP_SECRET_KEY,
2288 stable_secret_key: STABLE_SECRET_KEY,
2289 _marker: PhantomData,
2290 },
2291 slaac_state,
2292 )
2293 }
2294 }
2295
2296 impl FakeSlaacContext {
2297 fn iter_slaac_addrs(&self) -> impl Iterator<Item = SlaacAddressEntry<FakeInstant>> + '_ {
2298 self.slaac_addrs.slaac_addrs.iter().cloned()
2299 }
2300 }
2301
2302 fn new_timer_id() -> SlaacTimerId<FakeWeakDeviceId<FakeDeviceId>> {
2303 SlaacTimerId { device_id: FakeWeakDeviceId(FakeDeviceId) }
2304 }
2305
2306 fn new_context(
2307 config: SlaacConfiguration,
2308 slaac_addrs: FakeSlaacAddrs,
2309 dad_transmits: Option<NonZeroU16>,
2310 retrans_timer: Duration,
2311 ) -> CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
2312 CtxPair::with_default_bindings_ctx(|bindings_ctx| {
2313 FakeCoreCtxImpl::with_state(FakeSlaacContext {
2314 config,
2315 dad_transmits,
2316 retrans_timer,
2317 slaac_addrs,
2318 slaac_state: SlaacState::new::<_, IntoCoreTimerCtx>(
2319 bindings_ctx,
2320 FakeWeakDeviceId(FakeDeviceId),
2321 ),
2322 })
2323 })
2324 }
2325
2326 impl<Instant> SlaacAddressEntry<Instant> {
2327 fn to_deprecated(self) -> Self {
2328 let Self { addr_sub, config: Ipv6AddrSlaacConfig { inner, preferred_lifetime: _ } } =
2329 self;
2330 Self {
2331 addr_sub,
2332 config: Ipv6AddrSlaacConfig {
2333 inner,
2334 preferred_lifetime: PreferredLifetime::Deprecated,
2335 },
2336 }
2337 }
2338 }
2339
2340 #[test_case(ip_v6!("1:2:3:4::"), false; "subnet-router anycast")]
2341 #[test_case(ip_v6!("::1"), true; "allowed 1")]
2342 #[test_case(ip_v6!("1:2:3:4::1"), true; "allowed 2")]
2343 #[test_case(ip_v6!("4:4:4:4:0200:5eff:fe00:1"), false; "first ethernet block")]
2344 #[test_case(ip_v6!("1:1:1:1:0200:5eff:fe00:5213"), false; "proxy mobile")]
2345 #[test_case(ip_v6!("8:8:8:8:0200:5eff:fe00:8000"), false; "second ethernet block")]
2346 #[test_case(ip_v6!("a:a:a:a:fdff:ffff:ffff:ffaa"), false; "subnet anycast")]
2347 #[test_case(ip_v6!("c:c:c:c:fe00::"), true; "allowed 3")]
2348 fn test_has_iana_allowed_iid(addr: Ipv6Addr, expect_allowed: bool) {
2349 assert_eq!(has_iana_allowed_iid(addr), expect_allowed);
2350 }
2351
2352 const DEFAULT_RETRANS_TIMER: Duration = Duration::from_secs(1);
2353 const SUBNET: Subnet<Ipv6Addr> = net_declare::net_subnet_v6!("200a::/64");
2354
2355 #[test_case(0, 0, true; "zero lifetimes")]
2356 #[test_case(2, 1, true; "preferred larger than valid")]
2357 #[test_case(1, 2, false; "disabled")]
2358 fn dont_generate_address(
2359 preferred_lifetime_secs: u32,
2360 valid_lifetime_secs: u32,
2361 enable_stable_addresses: bool,
2362 ) {
2363 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2364 SlaacConfiguration {
2365 stable_address_configuration: if enable_stable_addresses {
2366 StableSlaacAddressConfiguration::ENABLED_WITH_EUI64
2367 } else {
2368 StableSlaacAddressConfiguration::Disabled
2369 },
2370 ..Default::default()
2371 },
2372 Default::default(),
2373 None,
2374 DEFAULT_RETRANS_TIMER,
2375 );
2376
2377 SlaacHandler::apply_slaac_update(
2378 &mut core_ctx,
2379 &mut bindings_ctx,
2380 &FakeDeviceId,
2381 SUBNET,
2382 NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
2383 NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
2384 );
2385 assert_empty(core_ctx.state.iter_slaac_addrs());
2386 bindings_ctx.timers.assert_no_timers_installed();
2387 }
2388
2389 #[test_case(0, false; "deprecated EUI64")]
2390 #[test_case(1, false; "preferred EUI64")]
2391 #[test_case(0, true; "deprecated opaque")]
2392 #[test_case(1, true; "preferred opaque")]
2393 fn generate_stable_address(preferred_lifetime_secs: u32, opaque_iids: bool) {
2394 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2395 SlaacConfiguration {
2396 stable_address_configuration: if opaque_iids {
2397 StableSlaacAddressConfiguration::ENABLED_WITH_OPAQUE_IIDS
2398 } else {
2399 StableSlaacAddressConfiguration::ENABLED_WITH_EUI64
2400 },
2401 ..Default::default()
2402 },
2403 Default::default(),
2404 None,
2405 DEFAULT_RETRANS_TIMER,
2406 );
2407
2408 let valid_lifetime_secs = preferred_lifetime_secs + 1;
2409 let addr_sub = if opaque_iids {
2410 calculate_stable_slaac_addr_sub_with_opaque_iid(SUBNET, IID, 0)
2411 } else {
2412 testutil::calculate_slaac_addr_sub(SUBNET, IID)
2413 };
2414
2415 SlaacHandler::apply_slaac_update(
2417 &mut core_ctx,
2418 &mut bindings_ctx,
2419 &FakeDeviceId,
2420 SUBNET,
2421 NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
2422 NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
2423 );
2424 let address_created_deprecated = preferred_lifetime_secs == 0;
2425 let now = bindings_ctx.now();
2426 let valid_until = now + Duration::from_secs(valid_lifetime_secs.into());
2427 let preferred_lifetime = match preferred_lifetime_secs {
2428 0 => PreferredLifetime::Deprecated,
2429 secs => PreferredLifetime::preferred_until(now + Duration::from_secs(secs.into())),
2430 };
2431 let inner = SlaacConfig::Stable {
2432 valid_until: Lifetime::Finite(valid_until),
2433 creation_time: bindings_ctx.now(),
2434 regen_counter: 0,
2435 dad_counter: 0,
2436 };
2437 let entry = SlaacAddressEntry {
2438 addr_sub,
2439 config: Ipv6AddrSlaacConfig { inner, preferred_lifetime },
2440 };
2441 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry],);
2442 let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
2443 let invalidate_timer_id =
2444 InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
2445 if !address_created_deprecated {
2446 core_ctx.state.slaac_state.timers.assert_timers([
2447 (deprecate_timer_id, (), now + Duration::from_secs(preferred_lifetime_secs.into())),
2448 (invalidate_timer_id, (), valid_until),
2449 ]);
2450
2451 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
2453 let entry = SlaacAddressEntry {
2454 addr_sub,
2455 config: Ipv6AddrSlaacConfig {
2456 inner,
2457 preferred_lifetime: PreferredLifetime::Deprecated,
2458 },
2459 };
2460 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2461 }
2462 core_ctx.state.slaac_state.timers.assert_timers([(invalidate_timer_id, (), valid_until)]);
2463
2464 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
2466 assert_empty(core_ctx.state.iter_slaac_addrs());
2467 bindings_ctx.timers.assert_no_timers_installed();
2468 }
2469
2470 enum StableAddress {
2471 Global,
2472 LinkLocal,
2473 }
2474
2475 #[test_case(StableAddress::Global, true; "opaque global")]
2476 #[test_case(StableAddress::Global, false; "EUI64-based global")]
2477 #[test_case(StableAddress::LinkLocal, true; "opaque link-local")]
2478 #[test_case(StableAddress::LinkLocal, false; "EUI64-based link-local")]
2479 fn stable_address_conflict(address_type: StableAddress, opaque_iids: bool) {
2480 let subnet = match address_type {
2481 StableAddress::Global => SUBNET,
2482 StableAddress::LinkLocal => {
2483 Subnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), REQUIRED_PREFIX_BITS)
2484 .unwrap()
2485 }
2486 };
2487 let addr_sub = if opaque_iids {
2488 let dad_counter = 0;
2489 calculate_stable_slaac_addr_sub_with_opaque_iid(subnet, IID, dad_counter)
2490 } else {
2491 testutil::calculate_slaac_addr_sub(subnet, IID)
2492 };
2493
2494 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2495 SlaacConfiguration {
2496 stable_address_configuration: if opaque_iids {
2497 StableSlaacAddressConfiguration::ENABLED_WITH_OPAQUE_IIDS
2498 } else {
2499 StableSlaacAddressConfiguration::ENABLED_WITH_EUI64
2500 },
2501 ..Default::default()
2502 },
2503 FakeSlaacAddrs {
2504 slaac_addrs: Default::default(),
2505 non_slaac_addrs: vec![addr_sub.addr()],
2508 counters: Default::default(),
2509 },
2510 None,
2511 DEFAULT_RETRANS_TIMER,
2512 );
2513
2514 const LIFETIME_SECS: u32 = 1;
2515
2516 match address_type {
2518 StableAddress::Global => {
2519 SlaacHandler::apply_slaac_update(
2520 &mut core_ctx,
2521 &mut bindings_ctx,
2522 &FakeDeviceId,
2523 SUBNET,
2524 NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
2525 NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
2526 );
2527 }
2528 StableAddress::LinkLocal => {
2529 SlaacHandler::generate_link_local_address(
2530 &mut core_ctx,
2531 &mut bindings_ctx,
2532 &FakeDeviceId,
2533 );
2534 }
2535 }
2536
2537 if !opaque_iids {
2541 assert_empty(core_ctx.state.iter_slaac_addrs());
2542 bindings_ctx.timers.assert_no_timers_installed();
2543 return;
2544 }
2545
2546 let dad_counter = 1;
2550 let addr_sub = calculate_stable_slaac_addr_sub_with_opaque_iid(subnet, &IID, dad_counter);
2551 match address_type {
2552 StableAddress::Global => {
2553 let now = bindings_ctx.now();
2554 let valid_until = now + Duration::from_secs(LIFETIME_SECS.into());
2555 let entry = SlaacAddressEntry {
2556 addr_sub,
2557 config: Ipv6AddrSlaacConfig {
2558 inner: SlaacConfig::Stable {
2559 valid_until: Lifetime::Finite(valid_until),
2560 creation_time: bindings_ctx.now(),
2561 regen_counter: 1,
2562 dad_counter: 0,
2563 },
2564 preferred_lifetime: PreferredLifetime::preferred_until(valid_until),
2565 },
2566 };
2567 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2568 let deprecate_timer_id =
2569 InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
2570 let invalidate_timer_id =
2571 InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
2572 core_ctx.state.slaac_state.timers.assert_timers([
2573 (deprecate_timer_id, (), valid_until),
2574 (invalidate_timer_id, (), valid_until),
2575 ]);
2576 }
2577 StableAddress::LinkLocal => {
2578 let entry = SlaacAddressEntry {
2579 addr_sub,
2580 config: Ipv6AddrSlaacConfig {
2581 inner: SlaacConfig::Stable {
2582 valid_until: Lifetime::Infinite,
2583 creation_time: bindings_ctx.now(),
2584 regen_counter: 1,
2585 dad_counter: 0,
2586 },
2587 preferred_lifetime: PreferredLifetime::preferred_forever(),
2588 },
2589 };
2590 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2591 bindings_ctx.timers.assert_no_timers_installed();
2592 }
2593 };
2594 }
2595
2596 #[test]
2597 fn temporary_address_conflict() {
2598 const TEMP_IDGEN_RETRIES: u8 = 0;
2599
2600 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2601 SlaacConfiguration {
2602 temporary_address_configuration: TemporarySlaacAddressConfiguration::Enabled {
2603 temp_valid_lifetime: ONE_HOUR,
2604 temp_preferred_lifetime: ONE_HOUR,
2605 temp_idgen_retries: TEMP_IDGEN_RETRIES,
2606 },
2607 ..Default::default()
2608 },
2609 FakeSlaacAddrs::default(),
2610 None,
2611 DEFAULT_RETRANS_TIMER,
2612 );
2613
2614 let mut dup_rng = bindings_ctx.rng().deep_clone();
2617 let seed = dup_rng.random();
2618 let first_attempt =
2619 generate_global_temporary_address(&SUBNET, &IID, seed, &TEMP_SECRET_KEY);
2620 core_ctx.state.slaac_addrs.non_slaac_addrs = vec![first_attempt.addr()];
2621
2622 SlaacHandler::apply_slaac_update(
2624 &mut core_ctx,
2625 &mut bindings_ctx,
2626 &FakeDeviceId,
2627 SUBNET,
2628 Some(NonZeroNdpLifetime::Finite(ONE_HOUR)),
2629 Some(NonZeroNdpLifetime::Finite(ONE_HOUR)),
2630 );
2631
2632 let seed = seed.wrapping_add(1);
2635 let addr_sub = generate_global_temporary_address(&SUBNET, &IID, seed, &TEMP_SECRET_KEY);
2636 assert_ne!(addr_sub, first_attempt);
2637 let regen_advance =
2638 regen_advance(TEMP_IDGEN_RETRIES, DEFAULT_RETRANS_TIMER, 0);
2639 let desync_factor = desync_factor(&mut dup_rng, ONE_HOUR, regen_advance).unwrap();
2640 let preferred_until = {
2641 let d = bindings_ctx.now() + ONE_HOUR.into();
2642 d - desync_factor
2643 };
2644 let entry = SlaacAddressEntry {
2645 addr_sub,
2646 config: Ipv6AddrSlaacConfig {
2647 inner: SlaacConfig::Temporary(TemporarySlaacConfig {
2648 valid_until: bindings_ctx.now() + ONE_HOUR.into(),
2649 desync_factor,
2650 creation_time: bindings_ctx.now(),
2651 dad_counter: 0,
2652 }),
2653 preferred_lifetime: PreferredLifetime::preferred_until(preferred_until),
2654 },
2655 };
2656 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2657 }
2658
2659 #[test]
2660 fn local_regen_limit() {
2661 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2662 SlaacConfiguration {
2663 stable_address_configuration:
2664 StableSlaacAddressConfiguration::ENABLED_WITH_OPAQUE_IIDS,
2665 temporary_address_configuration: TemporarySlaacAddressConfiguration::Enabled {
2666 temp_valid_lifetime: ONE_HOUR,
2667 temp_preferred_lifetime: ONE_HOUR,
2668 temp_idgen_retries: 0,
2669 },
2670 ..Default::default()
2671 },
2672 FakeSlaacAddrs::default(),
2673 None,
2674 DEFAULT_RETRANS_TIMER,
2675 );
2676
2677 let mut dup_rng = bindings_ctx.rng().deep_clone();
2678 let mut seed = dup_rng.random();
2679
2680 let link_local_subnet =
2681 Subnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), REQUIRED_PREFIX_BITS).unwrap();
2682
2683 for attempt in 0..=MAX_LOCAL_REGEN_ATTEMPTS {
2686 let link_local =
2687 calculate_stable_slaac_addr_sub_with_opaque_iid(link_local_subnet, IID, attempt);
2688
2689 let stable = calculate_stable_slaac_addr_sub_with_opaque_iid(SUBNET, IID, attempt);
2690
2691 let temporary =
2692 generate_global_temporary_address(&SUBNET, &IID, seed, &TEMP_SECRET_KEY);
2693 seed = seed.wrapping_add(1);
2694
2695 core_ctx.state.slaac_addrs.non_slaac_addrs.extend(&[
2696 link_local.addr(),
2697 stable.addr(),
2698 temporary.addr(),
2699 ]);
2700 }
2701
2702 SlaacHandler::apply_slaac_update(
2705 &mut core_ctx,
2706 &mut bindings_ctx,
2707 &FakeDeviceId,
2708 SUBNET,
2709 Some(NonZeroNdpLifetime::Finite(ONE_HOUR)),
2710 Some(NonZeroNdpLifetime::Finite(ONE_HOUR)),
2711 );
2712 SlaacHandler::generate_link_local_address(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
2713
2714 assert_empty(core_ctx.state.iter_slaac_addrs());
2717 bindings_ctx.timers.assert_no_timers_installed();
2718 }
2719
2720 const LIFETIME: NonZeroNdpLifetime =
2721 NonZeroNdpLifetime::Finite(NonZeroDuration::new(Duration::from_secs(1)).unwrap());
2722
2723 #[test_case(AddressRemovedReason::Manual, LIFETIME; "manual")]
2724 #[test_case(AddressRemovedReason::DadFailed, LIFETIME; "dad failed")]
2725 #[test_case(
2726 AddressRemovedReason::DadFailed,
2727 NonZeroNdpLifetime::Infinite;
2728 "dad failed infinite lifetime"
2729 )]
2730 fn remove_stable_address(reason: AddressRemovedReason, lifetime: NonZeroNdpLifetime) {
2731 let addr_sub =
2732 calculate_stable_slaac_addr_sub_with_opaque_iid(SUBNET, IID, 0);
2733
2734 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2735 SlaacConfiguration {
2736 stable_address_configuration:
2737 StableSlaacAddressConfiguration::ENABLED_WITH_OPAQUE_IIDS,
2738 ..Default::default()
2739 },
2740 Default::default(),
2741 None,
2742 DEFAULT_RETRANS_TIMER,
2743 );
2744
2745 SlaacHandler::apply_slaac_update(
2747 &mut core_ctx,
2748 &mut bindings_ctx,
2749 &FakeDeviceId,
2750 SUBNET,
2751 Some(lifetime),
2752 Some(lifetime),
2753 );
2754 let now = bindings_ctx.now();
2755 let valid_until = Lifetime::from_ndp(now, lifetime);
2756 let preferred_lifetime = PreferredLifetime::preferred_for(now, lifetime);
2757 let entry = SlaacAddressEntry {
2758 addr_sub,
2759 config: Ipv6AddrSlaacConfig {
2760 inner: SlaacConfig::Stable {
2761 valid_until,
2762 creation_time: bindings_ctx.now(),
2763 regen_counter: 0,
2764 dad_counter: 0,
2765 },
2766 preferred_lifetime,
2767 },
2768 };
2769 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2770
2771 let assert_expected_timers = |slaac_state: &SlaacState<_>, addr| {
2772 let expected_timers = match lifetime {
2773 NonZeroNdpLifetime::Infinite => vec![],
2774 NonZeroNdpLifetime::Finite(duration) => {
2775 let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr };
2776 let invalidate_timer_id = InnerSlaacTimerId::InvalidateSlaacAddress { addr };
2777 let instant = now + duration.get();
2778 vec![(deprecate_timer_id, (), instant), (invalidate_timer_id, (), instant)]
2779 }
2780 };
2781 slaac_state.timers.assert_timers(expected_timers);
2782 };
2783 assert_expected_timers(&core_ctx.state.slaac_state, addr_sub.addr());
2784
2785 let config = {
2787 let SlaacAddressEntry { addr_sub: got_addr_sub, config } =
2788 core_ctx.state.slaac_addrs.slaac_addrs.remove(0);
2789 assert_eq!(addr_sub, got_addr_sub);
2790 assert_eq!(config.preferred_lifetime, preferred_lifetime);
2791 config
2792 };
2793 SlaacHandler::on_address_removed(
2794 &mut core_ctx,
2795 &mut bindings_ctx,
2796 &FakeDeviceId,
2797 addr_sub,
2798 config,
2799 reason,
2800 );
2801 match reason {
2802 AddressRemovedReason::Manual => {
2803 bindings_ctx.timers.assert_no_timers_installed();
2805 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), []);
2806 return;
2807 }
2808 AddressRemovedReason::DadFailed => {}
2809 AddressRemovedReason::Forfeited => {
2810 unreachable!("forfeited IPv6 addresses are not tested");
2811 }
2812 }
2813
2814 let addr_sub =
2817 calculate_stable_slaac_addr_sub_with_opaque_iid(SUBNET, IID, 1);
2818 let entry = SlaacAddressEntry {
2819 addr_sub,
2820 config: Ipv6AddrSlaacConfig {
2821 inner: SlaacConfig::Stable {
2822 valid_until,
2823 creation_time: now,
2824 regen_counter: 0,
2825 dad_counter: 1,
2826 },
2827 preferred_lifetime,
2828 },
2829 };
2830 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
2831 assert_expected_timers(&core_ctx.state.slaac_state, addr_sub.addr());
2832 }
2833
2834 #[test]
2835 fn stable_addr_regen_counters() {
2836 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2840 SlaacConfiguration {
2841 stable_address_configuration:
2842 StableSlaacAddressConfiguration::ENABLED_WITH_OPAQUE_IIDS,
2843 ..Default::default()
2844 },
2845 Default::default(),
2846 None,
2847 DEFAULT_RETRANS_TIMER,
2848 );
2849
2850 const LOCAL_REGEN_ATTEMPTS: u8 = 3;
2851 const DAD_FAILURE_REGEN_ATTEMPTS: u8 = 3;
2852
2853 let now = bindings_ctx.now();
2854 core_ctx.with_slaac_addrs_mut_and_configs(&FakeDeviceId, |addrs, config, slaac_state| {
2855 for regen_count in 0..LOCAL_REGEN_ATTEMPTS {
2856 for dad_count in 0..DAD_FAILURE_REGEN_ATTEMPTS {
2857 add_slaac_addr_sub::<_, FakeCoreCtx<_, _, _>>(
2858 &mut bindings_ctx,
2859 &FakeDeviceId,
2860 addrs,
2861 &config,
2862 slaac_state,
2863 now,
2864 SlaacInitConfig::Stable { regen_count, dad_count },
2865 NonZeroNdpLifetime::Infinite,
2866 Some(NonZeroNdpLifetime::Infinite),
2867 &SUBNET,
2868 );
2869 }
2870 }
2871 });
2872 let unique_addrs = core_ctx
2873 .state
2874 .iter_slaac_addrs()
2875 .map(|entry| entry.addr_sub.addr())
2876 .collect::<HashSet<_>>();
2877 assert_eq!(
2878 unique_addrs.len(),
2879 usize::from(LOCAL_REGEN_ATTEMPTS * DAD_FAILURE_REGEN_ATTEMPTS)
2880 );
2881 }
2882
2883 struct RefreshStableAddressTimersTest {
2884 orig_pl_secs: u32,
2885 orig_vl_secs: u32,
2886 new_pl_secs: u32,
2887 new_vl_secs: u32,
2888 effective_new_vl_secs: u32,
2889 }
2890
2891 const ONE_HOUR_AS_SECS: u32 = 60 * 60;
2892 const TWO_HOURS_AS_SECS: u32 = ONE_HOUR_AS_SECS * 2;
2893 const THREE_HOURS_AS_SECS: u32 = ONE_HOUR_AS_SECS * 3;
2894 const FOUR_HOURS_AS_SECS: u32 = ONE_HOUR_AS_SECS * 4;
2895 const INFINITE_LIFETIME: u32 = u32::MAX;
2896 const MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS: u32 =
2897 MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE.get().as_secs() as u32;
2898 #[test_case(RefreshStableAddressTimersTest {
2899 orig_pl_secs: 1,
2900 orig_vl_secs: 1,
2901 new_pl_secs: 1,
2902 new_vl_secs: 1,
2903 effective_new_vl_secs: 1,
2904 }; "do nothing")]
2905 #[test_case(RefreshStableAddressTimersTest {
2906 orig_pl_secs: 1,
2907 orig_vl_secs: 1,
2908 new_pl_secs: 2,
2909 new_vl_secs: 2,
2910 effective_new_vl_secs: 2,
2911 }; "increase lifetimes")]
2912 #[test_case(RefreshStableAddressTimersTest {
2913 orig_pl_secs: 1,
2914 orig_vl_secs: 1,
2915 new_pl_secs: 0,
2916 new_vl_secs: 1,
2917 effective_new_vl_secs: 1,
2918 }; "deprecate address only")]
2919 #[test_case(RefreshStableAddressTimersTest {
2920 orig_pl_secs: 0,
2921 orig_vl_secs: 1,
2922 new_pl_secs: 1,
2923 new_vl_secs: 1,
2924 effective_new_vl_secs: 1,
2925 }; "undeprecate address")]
2926 #[test_case(RefreshStableAddressTimersTest {
2927 orig_pl_secs: 1,
2928 orig_vl_secs: 1,
2929 new_pl_secs: 0,
2930 new_vl_secs: 0,
2931 effective_new_vl_secs: 1,
2932 }; "deprecate address only with new valid lifetime of zero")]
2933 #[test_case(RefreshStableAddressTimersTest {
2934 orig_pl_secs: ONE_HOUR_AS_SECS,
2935 orig_vl_secs: ONE_HOUR_AS_SECS,
2936 new_pl_secs: ONE_HOUR_AS_SECS - 1,
2937 new_vl_secs: ONE_HOUR_AS_SECS - 1,
2938 effective_new_vl_secs: ONE_HOUR_AS_SECS,
2939 }; "decrease preferred lifetime and ignore new valid lifetime if less than 2 hours and remaining lifetime")]
2940 #[test_case(RefreshStableAddressTimersTest {
2941 orig_pl_secs: THREE_HOURS_AS_SECS,
2942 orig_vl_secs: THREE_HOURS_AS_SECS,
2943 new_pl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
2944 new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
2945 effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
2946 }; "deprecate address only and bring valid lifetime down to 2 hours at max")]
2947 #[test_case(RefreshStableAddressTimersTest {
2948 orig_pl_secs: ONE_HOUR_AS_SECS - 1,
2949 orig_vl_secs: ONE_HOUR_AS_SECS - 1,
2950 new_pl_secs: ONE_HOUR_AS_SECS - 1,
2951 new_vl_secs: ONE_HOUR_AS_SECS,
2952 effective_new_vl_secs: ONE_HOUR_AS_SECS,
2953 }; "increase valid lifetime if more than remaining valid lifetime")]
2954 #[test_case(RefreshStableAddressTimersTest {
2955 orig_pl_secs: INFINITE_LIFETIME,
2956 orig_vl_secs: INFINITE_LIFETIME,
2957 new_pl_secs: INFINITE_LIFETIME,
2958 new_vl_secs: INFINITE_LIFETIME,
2959 effective_new_vl_secs: INFINITE_LIFETIME,
2960 }; "infinite lifetimes")]
2961 #[test_case(RefreshStableAddressTimersTest {
2962 orig_pl_secs: ONE_HOUR_AS_SECS,
2963 orig_vl_secs: TWO_HOURS_AS_SECS,
2964 new_pl_secs: TWO_HOURS_AS_SECS,
2965 new_vl_secs: INFINITE_LIFETIME,
2966 effective_new_vl_secs: INFINITE_LIFETIME,
2967 }; "update valid lifetime from finite to infinite")]
2968 #[test_case(RefreshStableAddressTimersTest {
2969 orig_pl_secs: ONE_HOUR_AS_SECS,
2970 orig_vl_secs: TWO_HOURS_AS_SECS,
2971 new_pl_secs: INFINITE_LIFETIME,
2972 new_vl_secs: INFINITE_LIFETIME,
2973 effective_new_vl_secs: INFINITE_LIFETIME,
2974 }; "update both lifetimes from finite to infinite")]
2975 #[test_case(RefreshStableAddressTimersTest {
2976 orig_pl_secs: TWO_HOURS_AS_SECS,
2977 orig_vl_secs: INFINITE_LIFETIME,
2978 new_pl_secs: ONE_HOUR_AS_SECS,
2979 new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
2980 effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
2981 }; "update valid lifetime from infinite to finite")]
2982 #[test_case(RefreshStableAddressTimersTest {
2983 orig_pl_secs: INFINITE_LIFETIME,
2984 orig_vl_secs: INFINITE_LIFETIME,
2985 new_pl_secs: ONE_HOUR_AS_SECS,
2986 new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
2987 effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
2988 }; "update both lifetimes from infinite to finite")]
2989 fn stable_address_timers(
2990 RefreshStableAddressTimersTest {
2991 orig_pl_secs,
2992 orig_vl_secs,
2993 new_pl_secs,
2994 new_vl_secs,
2995 effective_new_vl_secs,
2996 }: RefreshStableAddressTimersTest,
2997 ) {
2998 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
2999 SlaacConfiguration {
3000 stable_address_configuration: StableSlaacAddressConfiguration::ENABLED_WITH_EUI64,
3001 ..Default::default()
3002 },
3003 Default::default(),
3004 None,
3005 DEFAULT_RETRANS_TIMER,
3006 );
3007
3008 let addr_sub = testutil::calculate_slaac_addr_sub(SUBNET, IID);
3009
3010 let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
3011 let invalidate_timer_id =
3012 InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
3013
3014 let ndp_pl = NonZeroNdpLifetime::from_u32_with_infinite(orig_pl_secs);
3016 let ndp_vl = NonZeroNdpLifetime::from_u32_with_infinite(orig_vl_secs);
3017 SlaacHandler::apply_slaac_update(
3018 &mut core_ctx,
3019 &mut bindings_ctx,
3020 &FakeDeviceId,
3021 SUBNET,
3022 ndp_pl,
3023 ndp_vl,
3024 );
3025 let now = bindings_ctx.now();
3026 let mut expected_timers = Vec::new();
3027 let valid_until = match ndp_vl.expect("this test expects to create an address") {
3028 NonZeroNdpLifetime::Finite(d) => {
3029 let valid_until = now + d.get();
3030 expected_timers.push((invalidate_timer_id, (), valid_until));
3031 Lifetime::Finite(valid_until)
3032 }
3033 NonZeroNdpLifetime::Infinite => Lifetime::Infinite,
3034 };
3035 match ndp_pl {
3036 None | Some(NonZeroNdpLifetime::Infinite) => {}
3037 Some(NonZeroNdpLifetime::Finite(d)) => {
3038 expected_timers.push((deprecate_timer_id, (), now + d.get()))
3039 }
3040 }
3041 let entry = SlaacAddressEntry {
3042 addr_sub,
3043 config: Ipv6AddrSlaacConfig {
3044 inner: SlaacConfig::Stable {
3045 valid_until,
3046 creation_time: bindings_ctx.now(),
3047 regen_counter: 0,
3048 dad_counter: 0,
3049 },
3050 preferred_lifetime: PreferredLifetime::maybe_preferred_for(now, ndp_pl),
3051 },
3052 };
3053 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
3054 core_ctx.state.slaac_state.timers.assert_timers(expected_timers);
3055
3056 let ndp_pl = NonZeroNdpLifetime::from_u32_with_infinite(new_pl_secs);
3058 SlaacHandler::apply_slaac_update(
3059 &mut core_ctx,
3060 &mut bindings_ctx,
3061 &FakeDeviceId,
3062 SUBNET,
3063 ndp_pl,
3064 NonZeroNdpLifetime::from_u32_with_infinite(new_vl_secs),
3065 );
3066 let mut expected_timers = Vec::new();
3067 let valid_until = match NonZeroNdpLifetime::from_u32_with_infinite(effective_new_vl_secs)
3068 .expect("this test expects to keep the address")
3069 {
3070 NonZeroNdpLifetime::Finite(d) => {
3071 let valid_until = now + d.get();
3072 expected_timers.push((invalidate_timer_id, (), valid_until));
3073 Lifetime::Finite(valid_until)
3074 }
3075 NonZeroNdpLifetime::Infinite => Lifetime::Infinite,
3076 };
3077 match ndp_pl {
3078 None | Some(NonZeroNdpLifetime::Infinite) => {}
3079 Some(NonZeroNdpLifetime::Finite(d)) => {
3080 expected_timers.push((deprecate_timer_id, (), now + d.get()))
3081 }
3082 }
3083 let entry = SlaacAddressEntry {
3084 config: Ipv6AddrSlaacConfig {
3085 inner: SlaacConfig::Stable {
3086 valid_until,
3087 creation_time: bindings_ctx.now(),
3088 regen_counter: 0,
3089 dad_counter: 0,
3090 },
3091 preferred_lifetime: PreferredLifetime::maybe_preferred_for(now, ndp_pl),
3092 },
3093 ..entry
3094 };
3095 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
3096 core_ctx.state.slaac_state.timers.assert_timers(expected_timers);
3097 }
3098
3099 const TEMP_SECRET_KEY: IidSecret = IidSecret::ALL_ONES;
3100 const STABLE_SECRET_KEY: IidSecret = IidSecret::ALL_TWOS;
3101
3102 const ONE_HOUR: NonZeroDuration = NonZeroDuration::from_secs(ONE_HOUR_AS_SECS as u64).unwrap();
3103
3104 struct DontGenerateTemporaryAddressTest {
3105 preferred_lifetime_config: NonZeroDuration,
3106 preferred_lifetime_secs: u32,
3107 valid_lifetime_secs: u32,
3108 temp_idgen_retries: u8,
3109 dad_transmits: u16,
3110 retrans_timer: Duration,
3111 enable: bool,
3112 }
3113
3114 impl DontGenerateTemporaryAddressTest {
3115 fn with_pl_less_than_regen_advance(
3116 dad_transmits: u16,
3117 retrans_timer: Duration,
3118 temp_idgen_retries: u8,
3119 ) -> Self {
3120 DontGenerateTemporaryAddressTest {
3121 preferred_lifetime_config: ONE_HOUR,
3122 preferred_lifetime_secs: u32::try_from(
3123 (SLAAC_MIN_REGEN_ADVANCE.get()
3124 + (u32::from(temp_idgen_retries)
3125 * u32::from(dad_transmits)
3126 * retrans_timer))
3127 .as_secs(),
3128 )
3129 .unwrap()
3130 - 1,
3131 valid_lifetime_secs: TWO_HOURS_AS_SECS,
3132 temp_idgen_retries,
3133 dad_transmits,
3134 retrans_timer,
3135 enable: true,
3136 }
3137 }
3138 }
3139
3140 #[test_case(DontGenerateTemporaryAddressTest {
3141 preferred_lifetime_config: ONE_HOUR,
3142 preferred_lifetime_secs: ONE_HOUR_AS_SECS,
3143 valid_lifetime_secs: TWO_HOURS_AS_SECS,
3144 temp_idgen_retries: 0,
3145 dad_transmits: 0,
3146 retrans_timer: DEFAULT_RETRANS_TIMER,
3147 enable: false,
3148 }; "disabled")]
3149 #[test_case(DontGenerateTemporaryAddressTest{
3150 preferred_lifetime_config: ONE_HOUR,
3151 preferred_lifetime_secs: 0,
3152 valid_lifetime_secs: 0,
3153 temp_idgen_retries: 0,
3154 dad_transmits: 0,
3155 retrans_timer: DEFAULT_RETRANS_TIMER,
3156 enable: true,
3157 }; "zero lifetimes")]
3158 #[test_case(DontGenerateTemporaryAddressTest {
3159 preferred_lifetime_config: ONE_HOUR,
3160 preferred_lifetime_secs: TWO_HOURS_AS_SECS,
3161 valid_lifetime_secs: ONE_HOUR_AS_SECS,
3162 temp_idgen_retries: 0,
3163 dad_transmits: 0,
3164 retrans_timer: DEFAULT_RETRANS_TIMER,
3165 enable: true,
3166 }; "preferred larger than valid")]
3167 #[test_case(DontGenerateTemporaryAddressTest {
3168 preferred_lifetime_config: ONE_HOUR,
3169 preferred_lifetime_secs: 0,
3170 valid_lifetime_secs: TWO_HOURS_AS_SECS,
3171 temp_idgen_retries: 0,
3172 dad_transmits: 0,
3173 retrans_timer: DEFAULT_RETRANS_TIMER,
3174 enable: true,
3175 }; "not preferred")]
3176 #[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
3177 0 ,
3178 DEFAULT_RETRANS_TIMER ,
3179 0 ,
3180 ); "preferred lifetime less than regen advance with no DAD transmits")]
3181 #[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
3182 1 ,
3183 DEFAULT_RETRANS_TIMER ,
3184 0 ,
3185 ); "preferred lifetime less than regen advance with DAD transmits")]
3186 #[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
3187 1 ,
3188 DEFAULT_RETRANS_TIMER ,
3189 1 ,
3190 ); "preferred lifetime less than regen advance with DAD transmits and retries")]
3191 #[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
3192 2 ,
3193 DEFAULT_RETRANS_TIMER + Duration::from_secs(1) ,
3194 3 ,
3195 ); "preferred lifetime less than regen advance with multiple DAD transmits and multiple retries")]
3196 #[test_case(DontGenerateTemporaryAddressTest {
3197 preferred_lifetime_config: SLAAC_MIN_REGEN_ADVANCE,
3198 preferred_lifetime_secs: ONE_HOUR_AS_SECS,
3199 valid_lifetime_secs: TWO_HOURS_AS_SECS,
3200 temp_idgen_retries: 1,
3201 dad_transmits: 1,
3202 retrans_timer: DEFAULT_RETRANS_TIMER,
3203 enable: true,
3204 }; "configured preferred lifetime less than regen advance")]
3205 fn dont_generate_temporary_address(
3206 DontGenerateTemporaryAddressTest {
3207 preferred_lifetime_config,
3208 preferred_lifetime_secs,
3209 valid_lifetime_secs,
3210 temp_idgen_retries,
3211 dad_transmits,
3212 retrans_timer,
3213 enable,
3214 }: DontGenerateTemporaryAddressTest,
3215 ) {
3216 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
3217 SlaacConfiguration {
3218 temporary_address_configuration: if enable {
3219 TemporarySlaacAddressConfiguration::Enabled {
3220 temp_valid_lifetime: ONE_HOUR,
3221 temp_preferred_lifetime: preferred_lifetime_config,
3222 temp_idgen_retries,
3223 }
3224 } else {
3225 TemporarySlaacAddressConfiguration::Disabled
3226 },
3227 ..Default::default()
3228 },
3229 Default::default(),
3230 NonZeroU16::new(dad_transmits),
3231 retrans_timer,
3232 );
3233
3234 SlaacHandler::apply_slaac_update(
3235 &mut core_ctx,
3236 &mut bindings_ctx,
3237 &FakeDeviceId,
3238 SUBNET,
3239 NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
3240 NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
3241 );
3242 assert_empty(core_ctx.state.iter_slaac_addrs());
3243 bindings_ctx.timers.assert_no_timers_installed();
3244 }
3245
3246 struct GenerateTemporaryAddressTest {
3247 pl_config: u32,
3248 vl_config: u32,
3249 dad_transmits: u16,
3250 retrans_timer: Duration,
3251 temp_idgen_retries: u8,
3252 pl_ra: u32,
3253 vl_ra: u32,
3254 expected_pl_addr: u32,
3255 expected_vl_addr: u32,
3256 }
3257 #[test_case(GenerateTemporaryAddressTest{
3258 pl_config: ONE_HOUR_AS_SECS,
3259 vl_config: ONE_HOUR_AS_SECS,
3260 dad_transmits: 0,
3261 retrans_timer: DEFAULT_RETRANS_TIMER,
3262 temp_idgen_retries: 0,
3263 pl_ra: ONE_HOUR_AS_SECS,
3264 vl_ra: ONE_HOUR_AS_SECS,
3265 expected_pl_addr: ONE_HOUR_AS_SECS,
3266 expected_vl_addr: ONE_HOUR_AS_SECS,
3267 }; "config and prefix same lifetimes")]
3268 #[test_case(GenerateTemporaryAddressTest{
3269 pl_config: ONE_HOUR_AS_SECS,
3270 vl_config: TWO_HOURS_AS_SECS,
3271 dad_transmits: 0,
3272 retrans_timer: DEFAULT_RETRANS_TIMER,
3273 temp_idgen_retries: 0,
3274 pl_ra: THREE_HOURS_AS_SECS,
3275 vl_ra: THREE_HOURS_AS_SECS,
3276 expected_pl_addr: ONE_HOUR_AS_SECS,
3277 expected_vl_addr: TWO_HOURS_AS_SECS,
3278 }; "config smaller than prefix lifetimes")]
3279 #[test_case(GenerateTemporaryAddressTest{
3280 pl_config: TWO_HOURS_AS_SECS,
3281 vl_config: THREE_HOURS_AS_SECS,
3282 dad_transmits: 0,
3283 retrans_timer: DEFAULT_RETRANS_TIMER,
3284 temp_idgen_retries: 0,
3285 pl_ra: ONE_HOUR_AS_SECS,
3286 vl_ra: TWO_HOURS_AS_SECS,
3287 expected_pl_addr: ONE_HOUR_AS_SECS,
3288 expected_vl_addr: TWO_HOURS_AS_SECS,
3289 }; "config larger than prefix lifetimes")]
3290 #[test_case(GenerateTemporaryAddressTest{
3291 pl_config: TWO_HOURS_AS_SECS,
3292 vl_config: THREE_HOURS_AS_SECS,
3293 dad_transmits: 0,
3294 retrans_timer: DEFAULT_RETRANS_TIMER,
3295 temp_idgen_retries: 0,
3296 pl_ra: INFINITE_LIFETIME,
3297 vl_ra: INFINITE_LIFETIME,
3298 expected_pl_addr: TWO_HOURS_AS_SECS,
3299 expected_vl_addr: THREE_HOURS_AS_SECS,
3300 }; "prefix with infinite lifetimes")]
3301 #[test_case(GenerateTemporaryAddressTest{
3302 pl_config: TWO_HOURS_AS_SECS,
3303 vl_config: THREE_HOURS_AS_SECS,
3304 dad_transmits: 1,
3305 retrans_timer: DEFAULT_RETRANS_TIMER,
3306 temp_idgen_retries: 0,
3307 pl_ra: INFINITE_LIFETIME,
3308 vl_ra: INFINITE_LIFETIME,
3309 expected_pl_addr: TWO_HOURS_AS_SECS,
3310 expected_vl_addr: THREE_HOURS_AS_SECS,
3311 }; "generate_with_dad_enabled")]
3312 #[test_case(GenerateTemporaryAddressTest{
3313 pl_config: TWO_HOURS_AS_SECS,
3314 vl_config: THREE_HOURS_AS_SECS,
3315 dad_transmits: 2,
3316 retrans_timer: Duration::from_secs(5),
3317 temp_idgen_retries: 3,
3318 pl_ra: INFINITE_LIFETIME,
3319 vl_ra: INFINITE_LIFETIME,
3320 expected_pl_addr: TWO_HOURS_AS_SECS,
3321 expected_vl_addr: THREE_HOURS_AS_SECS,
3322 }; "generate_with_dad_enabled_and_retries")]
3323 #[test_case(GenerateTemporaryAddressTest{
3324 pl_config: TWO_HOURS_AS_SECS,
3325 vl_config: THREE_HOURS_AS_SECS,
3326 dad_transmits: 1,
3327 retrans_timer: Duration::from_secs(10),
3328 temp_idgen_retries: 0,
3329 pl_ra: INFINITE_LIFETIME,
3330 vl_ra: INFINITE_LIFETIME,
3331 expected_pl_addr: TWO_HOURS_AS_SECS,
3332 expected_vl_addr: THREE_HOURS_AS_SECS,
3333 }; "generate_with_dad_enabled_but_no_retries")]
3334 fn generate_temporary_address(
3335 GenerateTemporaryAddressTest {
3336 pl_config,
3337 vl_config,
3338 dad_transmits,
3339 retrans_timer,
3340 temp_idgen_retries,
3341 pl_ra,
3342 vl_ra,
3343 expected_pl_addr,
3344 expected_vl_addr,
3345 }: GenerateTemporaryAddressTest,
3346 ) {
3347 let pl_config = Duration::from_secs(pl_config.into());
3348 let regen_advance = regen_advance(temp_idgen_retries, retrans_timer, dad_transmits);
3349
3350 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
3351 SlaacConfiguration {
3352 temporary_address_configuration: TemporarySlaacAddressConfiguration::Enabled {
3353 temp_valid_lifetime: NonZeroDuration::new(Duration::from_secs(
3354 vl_config.into(),
3355 ))
3356 .unwrap(),
3357 temp_preferred_lifetime: NonZeroDuration::new(pl_config).unwrap(),
3358 temp_idgen_retries,
3359 },
3360 ..Default::default()
3361 },
3362 Default::default(),
3363 NonZeroU16::new(dad_transmits),
3364 retrans_timer,
3365 );
3366
3367 let mut dup_rng = bindings_ctx.rng().deep_clone();
3368
3369 struct AddrProps {
3370 desync_factor: Duration,
3371 valid_until: FakeInstant,
3372 preferred_until: FakeInstant,
3373 entry: SlaacAddressEntry<FakeInstant>,
3374 deprecate_timer_id: InnerSlaacTimerId,
3375 invalidate_timer_id: InnerSlaacTimerId,
3376 regenerate_timer_id: InnerSlaacTimerId,
3377 }
3378
3379 let addr_props = |rng: &mut FakeCryptoRng<_>,
3380 creation_time,
3381 config_greater_than_ra_desync_factor_offset| {
3382 let valid_until = creation_time + Duration::from_secs(expected_vl_addr.into());
3383 let addr_sub =
3384 generate_global_temporary_address(&SUBNET, &IID, rng.random(), &TEMP_SECRET_KEY);
3385 let desync_factor =
3386 desync_factor(rng, NonZeroDuration::new(pl_config).unwrap(), regen_advance)
3387 .unwrap();
3388 let preferred_until = {
3389 let d = creation_time + Duration::from_secs(expected_pl_addr.into());
3390 if pl_config.as_secs() > pl_ra.into() {
3391 d + config_greater_than_ra_desync_factor_offset
3392 } else {
3393 d - desync_factor
3394 }
3395 };
3396
3397 AddrProps {
3398 desync_factor,
3399 valid_until,
3400 preferred_until,
3401 entry: SlaacAddressEntry {
3402 addr_sub,
3403 config: Ipv6AddrSlaacConfig {
3404 inner: SlaacConfig::Temporary(TemporarySlaacConfig {
3405 valid_until,
3406 desync_factor,
3407 creation_time,
3408 dad_counter: 0,
3409 }),
3410 preferred_lifetime: PreferredLifetime::preferred_until(preferred_until),
3411 },
3412 },
3413 deprecate_timer_id: InnerSlaacTimerId::DeprecateSlaacAddress {
3414 addr: addr_sub.addr(),
3415 },
3416 invalidate_timer_id: InnerSlaacTimerId::InvalidateSlaacAddress {
3417 addr: addr_sub.addr(),
3418 },
3419 regenerate_timer_id: InnerSlaacTimerId::RegenerateTemporaryAddress {
3420 addr_subnet: addr_sub,
3421 },
3422 }
3423 };
3424
3425 SlaacHandler::apply_slaac_update(
3427 &mut core_ctx,
3428 &mut bindings_ctx,
3429 &FakeDeviceId,
3430 SUBNET,
3431 NonZeroNdpLifetime::from_u32_with_infinite(pl_ra),
3432 NonZeroNdpLifetime::from_u32_with_infinite(vl_ra),
3433 );
3434 let AddrProps {
3435 desync_factor: first_desync_factor,
3436 valid_until: first_valid_until,
3437 preferred_until: first_preferred_until,
3438 entry: first_entry,
3439 deprecate_timer_id: first_deprecate_timer_id,
3440 invalidate_timer_id: first_invalidate_timer_id,
3441 regenerate_timer_id: first_regenerate_timer_id,
3442 } = addr_props(&mut dup_rng, bindings_ctx.now(), Duration::ZERO);
3443 assert_eq!(core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(), [first_entry]);
3444 core_ctx.state.slaac_state.timers.assert_timers([
3445 (first_deprecate_timer_id, (), first_preferred_until),
3446 (first_invalidate_timer_id, (), first_valid_until),
3447 (first_regenerate_timer_id, (), first_preferred_until - regen_advance.get()),
3448 ]);
3449
3450 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()),);
3453 let AddrProps {
3454 desync_factor: second_desync_factor,
3455 valid_until: second_valid_until,
3456 preferred_until: second_preferred_until,
3457 entry: second_entry,
3458 deprecate_timer_id: second_deprecate_timer_id,
3459 invalidate_timer_id: second_invalidate_timer_id,
3460 regenerate_timer_id: second_regenerate_timer_id,
3461 } = addr_props(&mut dup_rng, bindings_ctx.now(), first_desync_factor);
3462 assert_eq!(
3463 core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(),
3464 [first_entry, second_entry]
3465 );
3466 let second_regen_at = second_preferred_until - regen_advance.get();
3467 core_ctx.state.slaac_state.timers.assert_timers([
3468 (first_deprecate_timer_id, (), first_preferred_until),
3469 (first_invalidate_timer_id, (), first_valid_until),
3470 (second_deprecate_timer_id, (), second_preferred_until),
3471 (second_invalidate_timer_id, (), second_valid_until),
3472 (second_regenerate_timer_id, (), second_regen_at),
3473 ]);
3474
3475 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()),);
3477 let first_entry = first_entry.to_deprecated();
3478 assert_eq!(
3479 core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(),
3480 [first_entry, second_entry]
3481 );
3482 core_ctx.state.slaac_state.timers.assert_timers([
3483 (first_invalidate_timer_id, (), first_valid_until),
3484 (second_deprecate_timer_id, (), second_preferred_until),
3485 (second_invalidate_timer_id, (), second_valid_until),
3486 (second_regenerate_timer_id, (), second_regen_at),
3487 ]);
3488
3489 let third_created_at = {
3490 let expected_timer_order = if first_valid_until > second_regen_at {
3491 [second_regenerate_timer_id, second_deprecate_timer_id, first_invalidate_timer_id]
3492 } else {
3493 [first_invalidate_timer_id, second_regenerate_timer_id, second_deprecate_timer_id]
3494 };
3495
3496 let mut third_created_at = None;
3497 for timer_id in expected_timer_order.iter() {
3498 let timer_id = *timer_id;
3499
3500 core_ctx.state.slaac_state.timers.assert_top(&timer_id, &());
3501 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
3502
3503 if timer_id == second_regenerate_timer_id {
3504 assert_eq!(third_created_at, None);
3505 third_created_at = Some(bindings_ctx.now());
3506 }
3507 }
3508
3509 third_created_at.unwrap()
3510 };
3511
3512 let AddrProps {
3515 desync_factor: _,
3516 valid_until: third_valid_until,
3517 preferred_until: third_preferred_until,
3518 entry: third_entry,
3519 deprecate_timer_id: third_deprecate_timer_id,
3520 invalidate_timer_id: third_invalidate_timer_id,
3521 regenerate_timer_id: third_regenerate_timer_id,
3522 } = addr_props(&mut dup_rng, third_created_at, first_desync_factor + second_desync_factor);
3523 let second_entry = second_entry.to_deprecated();
3524 assert_eq!(
3525 core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>(),
3526 [second_entry, third_entry]
3527 );
3528 core_ctx.state.slaac_state.timers.assert_timers([
3529 (second_invalidate_timer_id, (), second_valid_until),
3530 (third_deprecate_timer_id, (), third_preferred_until),
3531 (third_invalidate_timer_id, (), third_valid_until),
3532 (third_regenerate_timer_id, (), third_preferred_until - regen_advance.get()),
3533 ]);
3534 }
3535
3536 #[test]
3537 fn temporary_address_not_updated_while_disabled() {
3538 let want_valid_until =
3539 FakeInstant::default() + Duration::from_secs(THREE_HOURS_AS_SECS.into());
3540 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
3541 SlaacConfiguration {
3542 stable_address_configuration: StableSlaacAddressConfiguration::Disabled,
3543 temporary_address_configuration: TemporarySlaacAddressConfiguration::Disabled,
3544 },
3545 FakeSlaacAddrs {
3546 slaac_addrs: vec![SlaacAddressEntry {
3547 addr_sub: testutil::calculate_slaac_addr_sub(SUBNET, IID),
3548 config: Ipv6AddrSlaacConfig {
3549 inner: SlaacConfig::Temporary(TemporarySlaacConfig {
3550 valid_until: want_valid_until,
3551 desync_factor: Duration::default(),
3552 creation_time: FakeInstant::default(),
3553 dad_counter: 0,
3554 }),
3555 preferred_lifetime: PreferredLifetime::preferred_forever(),
3556 },
3557 }],
3558 ..Default::default()
3559 },
3560 None, DEFAULT_RETRANS_TIMER,
3562 );
3563
3564 SlaacHandler::apply_slaac_update(
3565 &mut core_ctx,
3566 &mut bindings_ctx,
3567 &FakeDeviceId,
3568 SUBNET,
3569 NonZeroNdpLifetime::from_u32_with_infinite(FOUR_HOURS_AS_SECS),
3570 NonZeroNdpLifetime::from_u32_with_infinite(FOUR_HOURS_AS_SECS),
3571 );
3572 let addrs = core_ctx.state.iter_slaac_addrs().collect::<Vec<_>>();
3573 assert_eq!(addrs.len(), 1);
3574 let SlaacAddressEntry { config: Ipv6AddrSlaacConfig { inner, preferred_lifetime }, .. } =
3575 addrs[0];
3576 assert_matches!(inner,SlaacConfig::Temporary(TemporarySlaacConfig {
3577 valid_until,
3578 ..
3579 }) => {
3580 assert_eq!(valid_until, want_valid_until);
3581 });
3582 assert_eq!(preferred_lifetime, PreferredLifetime::Deprecated);
3587 }
3588
3589 #[test]
3590 fn max_autoconfigured_addresses() {
3591 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context(
3592 SlaacConfiguration {
3593 stable_address_configuration: StableSlaacAddressConfiguration::ENABLED_WITH_EUI64,
3594 temporary_address_configuration:
3595 TemporarySlaacAddressConfiguration::enabled_with_rfc_defaults(),
3596 },
3597 Default::default(),
3598 None,
3599 DEFAULT_RETRANS_TIMER,
3600 );
3601
3602 fn make_subnet(i: usize) -> Subnet<Ipv6Addr> {
3603 let i = u8::try_from(i).expect("should fit in a u8");
3604 let bytes = [0x20, 0x01, 0x0d, 0xb8, i, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
3605 Subnet::new(Ipv6Addr::from_bytes(bytes), REQUIRED_PREFIX_BITS)
3606 .expect("should be a valid_subnet")
3607 }
3608
3609 for i in 0..MAX_AUTOCONFIGURED_ADDRESSES / 2 {
3612 SlaacHandler::apply_slaac_update(
3613 &mut core_ctx,
3614 &mut bindings_ctx,
3615 &FakeDeviceId,
3616 make_subnet(i),
3617 NonZeroNdpLifetime::from_u32_with_infinite(ONE_HOUR_AS_SECS),
3618 NonZeroNdpLifetime::from_u32_with_infinite(TWO_HOURS_AS_SECS),
3619 );
3620 }
3621 assert_eq!(core_ctx.state.iter_slaac_addrs().count(), MAX_AUTOCONFIGURED_ADDRESSES);
3622
3623 SlaacHandler::apply_slaac_update(
3626 &mut core_ctx,
3627 &mut bindings_ctx,
3628 &FakeDeviceId,
3629 make_subnet(MAX_AUTOCONFIGURED_ADDRESSES),
3630 NonZeroNdpLifetime::from_u32_with_infinite(ONE_HOUR_AS_SECS),
3631 NonZeroNdpLifetime::from_u32_with_infinite(TWO_HOURS_AS_SECS),
3632 );
3633 assert_eq!(core_ctx.state.iter_slaac_addrs().count(), MAX_AUTOCONFIGURED_ADDRESSES);
3634
3635 let updated_subnet = make_subnet(0);
3637 SlaacHandler::apply_slaac_update(
3638 &mut core_ctx,
3639 &mut bindings_ctx,
3640 &FakeDeviceId,
3641 updated_subnet,
3642 NonZeroNdpLifetime::from_u32_with_infinite(THREE_HOURS_AS_SECS),
3643 NonZeroNdpLifetime::from_u32_with_infinite(FOUR_HOURS_AS_SECS),
3644 );
3645 assert_eq!(core_ctx.state.iter_slaac_addrs().count(), MAX_AUTOCONFIGURED_ADDRESSES);
3646 assert!(core_ctx.state.iter_slaac_addrs().any(|addr| {
3647 addr.addr_sub.subnet() == updated_subnet
3648 && matches!(
3649 addr.config.inner, SlaacConfig::Stable { valid_until: Lifetime::Finite(i), ..}
3650 if i == bindings_ctx.now() + Duration::from_secs(FOUR_HOURS_AS_SECS.into())
3651 )
3652 }));
3653 assert!(core_ctx.state.iter_slaac_addrs().any(|addr| {
3654 addr.addr_sub.subnet() == updated_subnet
3655 && matches!(
3656 addr.config.inner,
3657 SlaacConfig::Temporary(TemporarySlaacConfig{valid_until: i, ..})
3658 if i == bindings_ctx.now() + Duration::from_secs(FOUR_HOURS_AS_SECS.into())
3659 )
3660 }));
3661 }
3662}