blob: 61ee0ea241ece5f8c90ceaa2d05bcbcf3de280ce [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! IPv6 Stateless Address Autoconfiguration (SLAAC) as defined by [RFC 4862]
//! and temporary address extensions for SLAAC as defined by [RFC 8981].
//!
//! [RFC 4862]: https://datatracker.ietf.org/doc/html/rfc4862
//! [RFC 8981]: https://datatracker.ietf.org/doc/html/rfc8981
use alloc::vec::Vec;
use core::{marker::PhantomData, num::NonZeroU16, ops::ControlFlow, time::Duration};
use assert_matches::assert_matches;
use const_unwrap::const_unwrap_option;
use lock_order::{lock::UnlockedAccess, wrap::prelude::*};
use net_types::{
ip::{AddrSubnet, IpAddress, Ipv6Addr, Subnet},
Witness as _,
};
use packet_formats::{icmp::ndp::NonZeroNdpLifetime, utils::NonZeroDuration};
use rand::{distributions::Uniform, Rng as _, RngCore};
use tracing::{debug, error, trace};
pub use crate::algorithm::STABLE_IID_SECRET_KEY_BYTES;
use crate::{
algorithm::{generate_opaque_interface_identifier, OpaqueIidNonce},
context::{
CoreTimerContext, CounterContext, HandleableTimer, InstantBindingsTypes, InstantContext,
RngContext, TimerBindingsTypes, TimerContext,
},
counters::Counter,
device::{self, AnyDevice, DeviceIdContext, Id, WeakId as _},
error::{ExistsError, NotFoundError},
ip::device::{
state::{Lifetime, SlaacConfig, TemporarySlaacConfig},
AddressRemovedReason, Ipv6DeviceAddr,
},
time::LocalTimerHeap,
BindingsContext, CoreCtx, Instant, StackState,
};
/// Minimum Valid Lifetime value to actually update an address's valid lifetime.
///
/// 2 hours.
const MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE: Duration = Duration::from_secs(7200);
/// Required prefix length for SLAAC.
///
/// We need 64 bits in the prefix because the interface identifier is 64 bits,
/// and IPv6 addresses are 128 bits.
const REQUIRED_PREFIX_BITS: u8 = 64;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum InnerSlaacTimerId {
/// Timer to deprecate an address configured via SLAAC.
DeprecateSlaacAddress { addr: Ipv6DeviceAddr },
/// Timer to invalidate an address configured via SLAAC.
InvalidateSlaacAddress { addr: Ipv6DeviceAddr },
/// Timer to generate a new temporary SLAAC address before an existing one
/// expires.
RegenerateTemporaryAddress { addr_subnet: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> },
}
/// Global Slaac state on a device.
pub struct SlaacState<BT: SlaacBindingsTypes> {
timers: LocalTimerHeap<InnerSlaacTimerId, (), BT>,
}
impl<BC: SlaacBindingsTypes + TimerContext> SlaacState<BC> {
pub fn new<D: device::WeakId, CC: CoreTimerContext<SlaacTimerId<D>, BC>>(
bindings_ctx: &mut BC,
device_id: D,
) -> Self {
Self {
timers: LocalTimerHeap::new_with_context::<_, CC>(
bindings_ctx,
SlaacTimerId { device_id },
),
}
}
#[cfg(test)]
pub(crate) fn timers(&self) -> &LocalTimerHeap<InnerSlaacTimerId, (), BC> {
&self.timers
}
}
/// A timer ID for SLAAC.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct SlaacTimerId<D: device::WeakId> {
device_id: D,
}
impl<D: device::WeakId> SlaacTimerId<D> {
pub(super) fn device_id(&self) -> &D {
let Self { device_id } = self;
device_id
}
#[cfg(test)]
pub(crate) fn new(device_id: D) -> Self {
Self { device_id }
}
}
/// The state associated with a SLAAC address.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SlaacAddressEntry<Instant> {
pub(super) addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
pub(super) config: SlaacConfig<Instant>,
pub(super) deprecated: bool,
}
/// A mutable view into state associated with a SLAAC address's mutable state.
pub struct SlaacAddressEntryMut<'a, Instant> {
pub(super) addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
pub(super) config: &'a mut SlaacConfig<Instant>,
pub(super) deprecated: &'a mut bool,
}
pub trait SlaacAddresses<BT: SlaacBindingsTypes> {
/// Returns an iterator providing a mutable view of mutable SLAAC address
/// state.
fn for_each_addr_mut<F: FnMut(SlaacAddressEntryMut<'_, BT::Instant>)>(&mut self, cb: F);
/// The iterator provided to `with_addrs`.
type AddrsIter<'x>: Iterator<Item = SlaacAddressEntry<BT::Instant>>;
/// Calls the callback with an iterator over the addresses.
fn with_addrs<O, F: FnOnce(Self::AddrsIter<'_>) -> O>(&mut self, cb: F) -> O;
fn add_addr_sub_and_then<O, F: FnOnce(SlaacAddressEntryMut<'_, BT::Instant>, &mut BT) -> O>(
&mut self,
bindings_ctx: &mut BT,
addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
config: SlaacConfig<BT::Instant>,
and_then: F,
) -> Result<O, ExistsError>;
/// Removes a SLAAC address.
///
/// # Panics
///
/// May panic if `addr` is not an address recognized.
fn remove_addr(
&mut self,
bindings_ctx: &mut BT,
addr: &Ipv6DeviceAddr,
) -> Result<(AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, SlaacConfig<BT::Instant>), NotFoundError>;
}
pub struct SlaacAddrsMutAndConfig<'a, BT: SlaacBindingsTypes, A: SlaacAddresses<BT>> {
pub(super) addrs: &'a mut A,
pub(super) config: SlaacConfiguration,
pub(super) dad_transmits: Option<NonZeroU16>,
pub(super) retrans_timer: Duration,
pub(super) interface_identifier: [u8; 8],
pub(super) _marker: PhantomData<BT>,
}
/// The execution context for SLAAC.
pub trait SlaacContext<BC: SlaacBindingsContext>: DeviceIdContext<AnyDevice> {
type SlaacAddrs<'a>: SlaacAddresses<BC> + CounterContext<SlaacCounters> + 'a;
fn with_slaac_addrs_mut_and_configs<
O,
F: FnOnce(SlaacAddrsMutAndConfig<'_, BC, Self::SlaacAddrs<'_>>, &mut SlaacState<BC>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
fn with_slaac_addrs_mut<O, F: FnOnce(&mut Self::SlaacAddrs<'_>, &mut SlaacState<BC>) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O {
self.with_slaac_addrs_mut_and_configs(
device_id,
|SlaacAddrsMutAndConfig {
addrs,
config: _,
dad_transmits: _,
retrans_timer: _,
interface_identifier: _,
_marker,
},
state| cb(addrs, state),
)
}
}
/// Counters for SLAAC.
#[derive(Default)]
pub struct SlaacCounters {
/// Count of already exists errors when adding a generated SLAAC address.
pub(crate) generated_slaac_addr_exists: Counter,
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::SlaacCounters> for StackState<BC> {
type Data = SlaacCounters;
type Guard<'l> = &'l SlaacCounters where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
&self.slaac_counters()
}
}
impl<BC: BindingsContext, L> CounterContext<SlaacCounters> for CoreCtx<'_, BC, L> {
fn with_counters<O, F: FnOnce(&SlaacCounters) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::SlaacCounters>())
}
}
/// Update the instant at which an address configured via SLAAC is no longer
/// valid.
///
/// A `None` value for `valid_until` indicates that the address is valid
/// forever; `Some` indicates valid for some finite lifetime.
///
/// # Panics
///
/// May panic if `addr` is not an address configured via SLAAC on
/// `device_id`.
fn update_slaac_addr_valid_until<I: Instant>(
slaac_config: &mut SlaacConfig<I>,
valid_until: Lifetime<I>,
) {
match slaac_config {
SlaacConfig::Static { valid_until: v } => *v = valid_until,
SlaacConfig::Temporary(TemporarySlaacConfig {
valid_until: v,
desync_factor: _,
creation_time: _,
dad_counter: _,
}) => {
*v = match valid_until {
Lifetime::Finite(v) => v,
Lifetime::Infinite => panic!("temporary addresses may not be valid forever"),
}
}
};
}
/// The bindings types for SLAAC.
pub trait SlaacBindingsTypes: InstantBindingsTypes + TimerBindingsTypes {}
impl<BT> SlaacBindingsTypes for BT where BT: InstantBindingsTypes + TimerBindingsTypes {}
/// The bindings execution context for SLAAC.
pub trait SlaacBindingsContext: RngContext + TimerContext + SlaacBindingsTypes {}
impl<BC> SlaacBindingsContext for BC where BC: RngContext + TimerContext + SlaacBindingsTypes {}
/// An implementation of SLAAC.
pub trait SlaacHandler<BC: InstantContext>: DeviceIdContext<AnyDevice> {
/// Executes the algorithm in [RFC 4862 Section 5.5.3], with the extensions
/// from [RFC 8981 Section 3.4] for temporary addresses, for a given prefix
/// advertised by a router.
///
/// This function updates all static and temporary SLAAC addresses for the
/// given prefix and adds new ones if necessary.
///
/// [RFC 4862 Section 5.5.3]: http://tools.ietf.org/html/rfc4862#section-5.5.3
/// [RFC 8981 Section 3.4]: https://tools.ietf.org/html/rfc8981#section-3.4
fn apply_slaac_update(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
prefix: Subnet<Ipv6Addr>,
preferred_lifetime: Option<NonZeroNdpLifetime>,
valid_lifetime: Option<NonZeroNdpLifetime>,
);
/// Handles SLAAC specific aspects of address removal.
///
/// Must only be called after the address is removed from the interface.
fn on_address_removed(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
state: SlaacConfig<BC::Instant>,
reason: AddressRemovedReason,
);
/// Removes all SLAAC addresses assigned to the device.
fn remove_all_slaac_addresses(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
}
impl<BC: SlaacBindingsContext, CC: SlaacContext<BC>> SlaacHandler<BC> for CC {
fn apply_slaac_update(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
subnet: Subnet<Ipv6Addr>,
preferred_lifetime: Option<NonZeroNdpLifetime>,
valid_lifetime: Option<NonZeroNdpLifetime>,
) {
if preferred_lifetime > valid_lifetime {
// If the preferred lifetime is greater than the valid lifetime,
// silently ignore the Prefix Information option, as per RFC 4862
// section 5.5.3.
trace!("receive_ndp_packet: autonomous prefix's preferred lifetime is greater than valid lifetime, ignoring");
return;
}
let mut seen_static = false;
let mut seen_temporary = false;
let now = bindings_ctx.now();
self.with_slaac_addrs_mut_and_configs(device_id, |addrs_config, slaac_state| {
let SlaacAddrsMutAndConfig {
addrs: slaac_addrs,
config,
dad_transmits,
retrans_timer,
interface_identifier: iid,
_marker,
} = addrs_config;
// Apply the update to each existing address, static or temporary, for the
// prefix.
slaac_addrs.for_each_addr_mut(|address_entry| {
let slaac_type = match apply_slaac_update_to_addr(
address_entry,
slaac_state,
subnet,
device_id,
valid_lifetime,
preferred_lifetime,
&config,
now,
retrans_timer,
dad_transmits,
bindings_ctx,
) {
ControlFlow::Break(()) => return,
ControlFlow::Continue(slaac_type) => slaac_type,
};
// Mark the SLAAC address type as existing so we know not to
// generate an address for the type later.
//
// Note that SLAAC addresses are never invalidated/removed
// in response to a prefix update and addresses types never
// change after the address is added.
match slaac_type {
SlaacType::Static => seen_static = true,
SlaacType::Temporary => seen_temporary = true,
}
});
// As per RFC 4862 section 5.5.3.e, if the prefix advertised is not equal to
// the prefix of an address configured by stateless autoconfiguration
// already in the list of addresses associated with the interface, and if
// the Valid Lifetime is not 0, form an address (and add it to the list) by
// combining the advertised prefix with an interface identifier of the link
// as follows:
//
// | 128 - N bits | N bits |
// +--------------------+------------------------+
// | link prefix | interface identifier |
// +---------------------------------------------+
let valid_lifetime = match valid_lifetime {
Some(valid_lifetime) => valid_lifetime,
None => {
trace!(
"receive_ndp_packet: autonomous prefix has valid \
lifetime = 0, ignoring"
);
return;
}
};
let address_types_to_add = (!seen_static)
.then(|| {
// As per RFC 4862 Section 5.5.3.d,
//
//
// If the prefix advertised is not equal to the prefix of an
// address configured by stateless autoconfiguration already
// in the list of addresses associated with the interface
// (where 'equal' means the two prefix lengths are the same
// and the first prefix- length bits of the prefixes are
// identical), and if the Valid Lifetime is not 0, form an
// address [...].
SlaacType::Static
})
.into_iter()
.chain((!seen_temporary).then(|| {
// As per RFC 8981 Section 3.4.3,
//
// If the host has not configured any temporary
// address for the corresponding prefix, the host
// SHOULD create a new temporary address for such
// prefix.
SlaacType::Temporary
}));
for slaac_type in address_types_to_add {
add_slaac_addr_sub::<_, CC>(
slaac_addrs,
slaac_state,
bindings_ctx,
device_id,
now,
SlaacInitConfig::new(slaac_type),
valid_lifetime,
preferred_lifetime,
&subnet,
config,
dad_transmits,
retrans_timer,
iid,
);
}
});
}
fn on_address_removed(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
state: SlaacConfig<BC::Instant>,
reason: AddressRemovedReason,
) {
self.with_slaac_addrs_mut_and_configs(device_id, |addrs_config, slaac_state| {
on_address_removed_inner::<_, CC>(
bindings_ctx,
addr_sub,
device_id,
addrs_config,
slaac_state,
state,
reason,
)
});
}
fn remove_all_slaac_addresses(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
self.with_slaac_addrs_mut(device_id, |slaac_addrs, _| {
slaac_addrs
.with_addrs(|addrs| addrs.map(|a| a.addr_sub.addr()).collect::<Vec<_>>())
.into_iter()
.filter_map(|addr| {
slaac_addrs.remove_addr(bindings_ctx, &addr).map(Some).unwrap_or_else(
|NotFoundError| {
// We're not holding locks on the assigned addresses
// here, so we can't assume a race is impossible with
// something else removing the address. Just assume that
// it is gone.
None
},
)
})
.collect::<Vec<_>>()
})
.into_iter()
.for_each(|(addr, config)| {
self.on_address_removed(
bindings_ctx,
device_id,
addr,
config,
AddressRemovedReason::Manual,
)
})
}
}
fn on_address_removed_inner<BC: SlaacBindingsContext, CC: SlaacContext<BC>>(
bindings_ctx: &mut BC,
addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
device_id: &CC::DeviceId,
addrs_config: SlaacAddrsMutAndConfig<'_, BC, CC::SlaacAddrs<'_>>,
slaac_state: &mut SlaacState<BC>,
state: SlaacConfig<BC::Instant>,
reason: AddressRemovedReason,
) {
let SlaacState { timers } = slaac_state;
let preferred_until = timers
.cancel(bindings_ctx, &InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() })
.map(|(t, ())| t);
let _valid_until: Option<(BC::Instant, ())> = timers
.cancel(bindings_ctx, &InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() });
let TemporarySlaacConfig { valid_until, creation_time, desync_factor, dad_counter } =
match state {
SlaacConfig::Temporary(temporary_config) => {
let _regen_at: Option<(BC::Instant, ())> = timers.cancel(
bindings_ctx,
&InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub },
);
temporary_config
}
SlaacConfig::Static { .. } => return,
};
match reason {
AddressRemovedReason::Manual => return,
AddressRemovedReason::DadFailed => {
// Attempt to regenerate the address.
}
}
let SlaacAddrsMutAndConfig {
addrs: slaac_addrs,
config,
dad_transmits,
retrans_timer,
interface_identifier: iid,
_marker,
} = addrs_config;
let SlaacConfiguration { enable_stable_addresses: _, temporary_address_configuration } = config;
let temporary_address_configuration = match temporary_address_configuration {
Some(configuration) => configuration,
None => return,
};
if dad_counter >= temporary_address_configuration.temp_idgen_retries {
return;
}
let temp_valid_lifetime = temporary_address_configuration.temp_valid_lifetime;
// Compute the original preferred lifetime for the removed address so that
// it can be used for the new address being generated. If, when the address
// was created, the prefix's preferred lifetime was less than
// `temporary_address_configuration.temp_preferred_lifetime`, then that's
// what will be calculated here. That's fine because it's a lower bound on
// the prefix's value, which means the prefix's value is still being
// respected.
let preferred_for = match preferred_until
.map(|preferred_until| preferred_until.duration_since(creation_time) + desync_factor)
{
Some(preferred_for) => preferred_for,
// If the address is already deprecated, a new address should already
// have been generated, so ignore this one.
None => return,
};
let now = bindings_ctx.now();
// It's possible this `valid_for` value is larger than `temp_valid_lifetime`
// (e.g. if the NDP configuration was changed since this address was
// generated). That's okay, because `add_slaac_addr_sub` will apply the
// current maximum valid lifetime when called below.
let valid_for = NonZeroDuration::new(valid_until.duration_since(creation_time))
.unwrap_or(temp_valid_lifetime);
add_slaac_addr_sub::<_, CC>(
slaac_addrs,
slaac_state,
bindings_ctx,
device_id,
now,
SlaacInitConfig::Temporary { dad_count: dad_counter + 1 },
NonZeroNdpLifetime::Finite(valid_for),
NonZeroDuration::new(preferred_for).map(NonZeroNdpLifetime::Finite),
&addr_sub.subnet(),
config,
dad_transmits,
retrans_timer,
iid,
)
}
fn apply_slaac_update_to_addr<D: Id, BC: SlaacBindingsContext>(
address_entry: SlaacAddressEntryMut<'_, BC::Instant>,
state: &mut SlaacState<BC>,
subnet: Subnet<Ipv6Addr>,
device_id: &D,
valid_lifetime: Option<NonZeroNdpLifetime>,
preferred_lifetime: Option<NonZeroNdpLifetime>,
config: &SlaacConfiguration,
now: <BC as InstantBindingsTypes>::Instant,
retrans_timer: Duration,
dad_transmits: Option<NonZeroU16>,
bindings_ctx: &mut BC,
) -> ControlFlow<(), SlaacType> {
let SlaacAddressEntryMut { addr_sub, config: slaac_config, deprecated } = address_entry;
let SlaacState { timers } = state;
if addr_sub.subnet() != subnet {
return ControlFlow::Break(());
}
let addr = addr_sub.addr();
let slaac_type = SlaacType::from(&*slaac_config);
trace!(
"receive_ndp_packet: already have a {:?} SLAAC address {:?} configured on device {:?}",
slaac_type,
addr_sub,
device_id
);
/// Encapsulates a lifetime bound and where it came from.
#[derive(Copy, Clone)]
enum ValidLifetimeBound {
FromPrefix(Option<NonZeroNdpLifetime>),
FromMaxBound(Duration),
}
impl ValidLifetimeBound {
/// Unwraps the object and returns the wrapped duration.
fn get(self) -> Option<NonZeroNdpLifetime> {
match self {
Self::FromPrefix(d) => d,
Self::FromMaxBound(d) => NonZeroDuration::new(d).map(NonZeroNdpLifetime::Finite),
}
}
}
let (valid_for, entry_valid_until, preferred_for_and_regen_at) = match slaac_config {
SlaacConfig::Static { valid_until: entry_valid_until } => (
ValidLifetimeBound::FromPrefix(valid_lifetime),
*entry_valid_until,
preferred_lifetime.map(|p| (p, None)),
),
// Select valid_for and preferred_for according to RFC 8981
// Section 3.4.
SlaacConfig::Temporary(TemporarySlaacConfig {
valid_until: entry_valid_until,
creation_time,
desync_factor,
dad_counter: _,
}) => {
let SlaacConfiguration { enable_stable_addresses: _, temporary_address_configuration } =
config;
let (valid_for, preferred_for, entry_valid_until) =
match temporary_address_configuration {
// Since it's possible to change NDP configuration for a
// device during runtime, we can end up here, with a
// temporary address on an interface even though temporary
// addressing is disabled. Setting its validity period to 0
// will force it to be removed ASAP.
None => {
(ValidLifetimeBound::FromMaxBound(Duration::ZERO), None, *entry_valid_until)
}
Some(temporary_address_config) => {
// RFC 8981 Section 3.4.2:
// When updating the preferred lifetime of an existing
// temporary address, it would be set to expire at
// whichever time is earlier: the time indicated by
// the received lifetime or (CREATION_TIME +
// TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR). A similar
// approach can be used with the valid lifetime.
let preferred_for = preferred_lifetime.and_then(|preferred_lifetime| {
temporary_address_config
.temp_preferred_lifetime
.get()
.checked_sub(now.duration_since(*creation_time))
.and_then(|p| p.checked_sub(*desync_factor))
.and_then(NonZeroDuration::new)
.map(|d| preferred_lifetime.min_finite_duration(d))
});
// Per RFC 8981 Section 3.4.1, `desync_factor` is only
// used for preferred lifetime:
// [...] with the overall constraint that no temporary
// addresses should ever remain "valid" or "preferred"
// for a time longer than (TEMP_VALID_LIFETIME) or
// (TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR),
// respectively.
let since_creation = now.duration_since(*creation_time);
let configured_max_lifetime =
temporary_address_config.temp_valid_lifetime.get();
let max_valid_lifetime = if since_creation > configured_max_lifetime {
Duration::ZERO
} else {
configured_max_lifetime - since_creation
};
let valid_for = valid_lifetime.map_or(
ValidLifetimeBound::FromPrefix(None),
|d| match d {
NonZeroNdpLifetime::Infinite => {
ValidLifetimeBound::FromMaxBound(max_valid_lifetime)
}
NonZeroNdpLifetime::Finite(d) => {
if max_valid_lifetime <= d.get() {
ValidLifetimeBound::FromMaxBound(max_valid_lifetime)
} else {
ValidLifetimeBound::FromPrefix(valid_lifetime)
}
}
},
);
(valid_for, preferred_for, *entry_valid_until)
}
};
let preferred_for_and_regen_at = preferred_for.map(|preferred_for| {
let SlaacConfiguration {
enable_stable_addresses: _,
temporary_address_configuration,
} = config;
let regen_at = temporary_address_configuration.and_then(
|TemporarySlaacAddressConfiguration {
temp_idgen_retries,
temp_preferred_lifetime: _,
temp_valid_lifetime: _,
secret_key: _,
}| {
let regen_advance = regen_advance(
temp_idgen_retries,
retrans_timer,
dad_transmits.map_or(0, NonZeroU16::get),
)
.get();
// Per RFC 8981 Section 3.6:
//
// Hosts following this specification SHOULD
// generate new temporary addresses over time.
// This can be achieved by generating a new
// temporary address REGEN_ADVANCE time units
// before a temporary address becomes deprecated.
//
// It's possible for regen_at to be before the
// current time. In that case, set it to `now` so
// that a new address is generated after the current
// prefix information is handled.
preferred_for
.get()
.checked_sub(regen_advance)
.map_or(Some(now), |d| now.checked_add(d))
},
);
(NonZeroNdpLifetime::Finite(preferred_for), regen_at)
});
(valid_for, Lifetime::Finite(entry_valid_until), preferred_for_and_regen_at)
}
};
// `Some` iff the remaining lifetime is a positive non-zero lifetime.
let remaining_lifetime = match entry_valid_until {
Lifetime::Infinite => Some(Lifetime::Infinite),
Lifetime::Finite(entry_valid_until) => (entry_valid_until > now)
.then(|| Lifetime::Finite(entry_valid_until.duration_since(now))),
};
// As per RFC 4862 section 5.5.3.e, if the advertised prefix is equal to the
// prefix of an address configured by stateless autoconfiguration in the
// list, the preferred lifetime of the address is reset to the Preferred
// Lifetime in the received advertisement.
// Update the preferred lifetime for this address.
match preferred_for_and_regen_at {
None => {
if !*deprecated {
*deprecated = true;
let _: Option<(BC::Instant, ())> =
timers.cancel(bindings_ctx, &InnerSlaacTimerId::DeprecateSlaacAddress { addr });
let _: Option<(BC::Instant, ())> = timers.cancel(
bindings_ctx,
&InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub },
);
}
}
Some((preferred_for, regen_at)) => {
if *deprecated {
*deprecated = false;
}
let timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr };
let _previously_scheduled_instant: Option<(BC::Instant, ())> = match preferred_for {
NonZeroNdpLifetime::Finite(preferred_for) => {
// Use `schedule_timer_instant` instead of `schedule_timer` to set
// the timeout relative to the previously recorded `now` value. This
// helps prevent skew in cases where this task gets preempted and
// isn't scheduled for some period of time between recording `now`
// and here.
timers.schedule_instant(
bindings_ctx,
timer_id,
(),
now.checked_add(preferred_for.get()).unwrap(),
)
}
NonZeroNdpLifetime::Infinite => timers.cancel(bindings_ctx, &timer_id),
};
let timer_id = InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: addr_sub };
let _prev_regen_at: Option<(BC::Instant, ())> = match regen_at {
Some(regen_at) => timers.schedule_instant(bindings_ctx, timer_id, (), regen_at),
None => timers.cancel(bindings_ctx, &timer_id),
};
}
}
// As per RFC 4862 section 5.5.3.e, the specific action to perform for the
// valid lifetime of the address depends on the Valid Lifetime in the
// received advertisement and the remaining time to the valid lifetime
// expiration of the previously autoconfigured address:
let valid_for_to_update = match valid_for {
ValidLifetimeBound::FromMaxBound(valid_for) => {
// If the maximum lifetime for the address is smaller than the
// lifetime specified for the prefix, then it must be applied.
NonZeroDuration::new(valid_for).map(NonZeroNdpLifetime::Finite)
}
ValidLifetimeBound::FromPrefix(valid_for) => {
// If the received Valid Lifetime is greater than 2 hours or
// greater than RemainingLifetime, set the valid lifetime of
// the corresponding address to the advertised Valid
// Lifetime.
match valid_for {
Some(NonZeroNdpLifetime::Infinite) => Some(NonZeroNdpLifetime::Infinite),
Some(NonZeroNdpLifetime::Finite(v))
if v.get() > MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE
|| remaining_lifetime.map_or(true, |r| r < Lifetime::Finite(v.get())) =>
{
Some(NonZeroNdpLifetime::Finite(v))
}
None | Some(NonZeroNdpLifetime::Finite(_)) => {
if remaining_lifetime.map_or(true, |r| {
r <= Lifetime::Finite(MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE)
}) {
// If RemainingLifetime is less than or equal to 2 hours,
// ignore the Prefix Information option with regards to the
// valid lifetime, unless the Router Advertisement from
// which this option was obtained has been authenticated
// (e.g., via Secure Neighbor Discovery [RFC3971]). If the
// Router Advertisement was authenticated, the valid
// lifetime of the corresponding address should be set to
// the Valid Lifetime in the received option.
//
// TODO(ghanan): If the NDP packet this prefix option is in
// was authenticated, update the valid
// lifetime of the address to the valid
// lifetime in the received option, as per RFC
// 4862 section 5.5.3.e.
None
} else {
// Otherwise, reset the valid lifetime of the corresponding
// address to 2 hours.
Some(NonZeroNdpLifetime::Finite(
NonZeroDuration::new(MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE).unwrap(),
))
}
}
}
}
};
match valid_for_to_update {
Some(valid_for) => match valid_for {
NonZeroNdpLifetime::Finite(valid_for) => {
let valid_until = now.checked_add(valid_for.get()).unwrap();
trace!(
"receive_ndp_packet: updating valid lifetime to {:?} for SLAAC address {:?} on device {:?}",
valid_until,
addr,
device_id
);
// Set the valid lifetime for this address.
update_slaac_addr_valid_until(slaac_config, Lifetime::Finite(valid_until));
let _: Option<(BC::Instant, ())> = timers.schedule_instant(
bindings_ctx,
InnerSlaacTimerId::InvalidateSlaacAddress { addr },
(),
valid_until,
);
}
NonZeroNdpLifetime::Infinite => {
// Set the valid lifetime for this address.
update_slaac_addr_valid_until(slaac_config, Lifetime::Infinite);
let _: Option<(BC::Instant, ())> = timers
.cancel(bindings_ctx, &InnerSlaacTimerId::InvalidateSlaacAddress { addr });
}
},
None => {
trace!(
"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",
addr,
device_id,
valid_for.get()
);
}
}
ControlFlow::Continue(slaac_type)
}
impl<BC: SlaacBindingsContext, CC: SlaacContext<BC>> HandleableTimer<CC, BC>
for SlaacTimerId<CC::WeakDeviceId>
{
fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) {
let Self { device_id } = self;
let Some(device_id) = device_id.upgrade() else {
return;
};
core_ctx.with_slaac_addrs_mut_and_configs(&device_id, |slaac_addrs_config, slaac_state| {
let Some((timer_id, ())) = slaac_state.timers.pop(bindings_ctx) else {
return;
};
match timer_id {
InnerSlaacTimerId::DeprecateSlaacAddress { addr } => {
slaac_addrs_config.addrs.for_each_addr_mut(
|SlaacAddressEntryMut { addr_sub, config: _, deprecated }| {
if addr_sub.addr() == addr {
*deprecated = true;
}
},
)
}
InnerSlaacTimerId::InvalidateSlaacAddress { addr } => {
let (addr, config) =
match slaac_addrs_config.addrs.remove_addr(bindings_ctx, &addr) {
Ok(addr_config) => addr_config,
Err(NotFoundError) => {
// Even though when a user removes an address we
// get notified, we could still race with our
// own timer here. This is a tight enough race
// that we can log at warn to call out in case
// something else is wrong. It should certainly
// not happen in tests, however.
#[cfg(test)]
panic!("Failed to remove address {addr} on invalidation");
#[cfg(not(test))]
{
tracing::warn!(
"failed to remove SLAAC address {addr}, assuming raced \
with user removal"
);
return;
}
}
};
on_address_removed_inner::<_, CC>(
bindings_ctx,
addr,
&device_id,
slaac_addrs_config,
slaac_state,
config,
AddressRemovedReason::Manual,
);
}
InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet } => {
regenerate_temporary_slaac_addr::<_, CC>(
bindings_ctx,
slaac_addrs_config,
slaac_state,
&device_id,
&addr_subnet,
);
}
}
});
}
}
/// Configuration values for SLAAC temporary addressing.
///
/// The algorithm specified in [RFC 8981 Section 3.4] references several
/// configuration parameters, which are defined in [Section 3.8] and
/// [Section 3.3.2] This struct contains the following values specified by the
/// RFC:
/// - TEMP_VALID_LIFETIME
/// - TEMP_PREFERRED_LIFETIME
/// - TEMP_IDGEN_RETRIES
/// - secret_key
///
/// [RFC 8981 Section 3.4]: http://tools.ietf.org/html/rfc8981#section-3.4
/// [Section 3.3.2]: http://tools.ietf.org/html/rfc8981#section-3.3.2
/// [Section 3.8]: http://tools.ietf.org/html/rfc8981#section-3.8
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TemporarySlaacAddressConfiguration {
/// The maximum amount of time that a temporary address can be considered
/// valid, from the time of its creation.
pub temp_valid_lifetime: NonZeroDuration,
/// The maximum amount of time that a temporary address can be preferred,
/// from the time of its creation.
pub temp_preferred_lifetime: NonZeroDuration,
/// The number of times to attempt to pick a new temporary address after DAD
/// detects a duplicate before stopping and giving up on temporary address
/// generation for that prefix.
pub temp_idgen_retries: u8,
/// The secret to use when generating new temporary addresses. This should
/// be initialized from a random number generator before generating any
/// temporary addresses.
pub secret_key: [u8; STABLE_IID_SECRET_KEY_BYTES],
}
impl TemporarySlaacAddressConfiguration {
/// Default TEMP_VALID_LIFETIME specified by [RFC 8981 Section 3.8].
///
/// [RFC 8981 Section 3.8]: https://www.rfc-editor.org/rfc/rfc8981#section-3.8
pub const DEFAULT_TEMP_VALID_LIFETIME: NonZeroDuration = // 2 days
const_unwrap_option(NonZeroDuration::from_secs(2 * 24 * 60 * 60u64));
/// Default TEMP_PREFERRED_LIFETIME specified by [RFC 8981 Section 3.8].
///
/// [RFC 8981 Section 3.8]: https://www.rfc-editor.org/rfc/rfc8981#section-3.8
pub const DEFAULT_TEMP_PREFERRED_LIFETIME: NonZeroDuration = // 1 day
const_unwrap_option(NonZeroDuration::from_secs(1 * 24 * 60 * 60u64));
/// Default TEMP_IDGEN_RETRIES specified by [RFC 8981 Section 3.8].
///
/// [RFC 8981 Section 3.8]: https://www.rfc-editor.org/rfc/rfc8981#section-3.8
pub const DEFAULT_TEMP_IDGEN_RETRIES: u8 = 3;
/// Constructs a new instance with default values and the given secret key.
pub fn default_with_secret_key(secret_key: [u8; STABLE_IID_SECRET_KEY_BYTES]) -> Self {
Self {
temp_valid_lifetime: Self::DEFAULT_TEMP_VALID_LIFETIME,
temp_preferred_lifetime: Self::DEFAULT_TEMP_PREFERRED_LIFETIME,
temp_idgen_retries: Self::DEFAULT_TEMP_IDGEN_RETRIES,
secret_key,
}
}
}
/// The configuration for SLAAC.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SlaacConfiguration {
/// Configuration to enable stable address assignment.
pub enable_stable_addresses: bool,
/// Configuration for temporary address assignment.
///
/// If `None`, temporary addresses will not be assigned to interfaces, and
/// any already-assigned temporary addresses will be removed.
///
/// If Some, specifies the configuration parameters for temporary addressing,
/// including those relating to how long temporary addresses should remain
/// preferred and valid.
pub temporary_address_configuration: Option<TemporarySlaacAddressConfiguration>,
}
#[derive(PartialEq, Eq)]
enum SlaacType {
Static,
Temporary,
}
impl core::fmt::Debug for SlaacType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SlaacType::Static => f.write_str("static"),
SlaacType::Temporary => f.write_str("temporary"),
}
}
}
impl<'a, Instant> From<&'a SlaacConfig<Instant>> for SlaacType {
fn from(slaac_config: &'a SlaacConfig<Instant>) -> Self {
match slaac_config {
SlaacConfig::Static { .. } => SlaacType::Static,
SlaacConfig::Temporary { .. } => SlaacType::Temporary,
}
}
}
/// The minimum REGEN_ADVANCE as specified in [RFC 8981 Section 3.8].
///
/// [RFC 8981 Section 3.8]: https://datatracker.ietf.org/doc/html/rfc8981#section-3.8
// As per [RFC 8981 Section 3.8],
//
// REGEN_ADVANCE
// 2 + (TEMP_IDGEN_RETRIES * DupAddrDetectTransmits * RetransTimer /
// 1000)
//
// ..., such that REGEN_ADVANCE is expressed in seconds.
const MIN_REGEN_ADVANCE: NonZeroDuration =
const_unwrap::const_unwrap_option(NonZeroDuration::from_secs(2));
/// Computes REGEN_ADVANCE as specified in [RFC 8981 Section 3.8].
///
/// [RFC 8981 Section 3.8]: http://tools.ietf.org/html/rfc8981#section-3.8
fn regen_advance(
temp_idgen_retries: u8,
retrans_timer: Duration,
dad_transmits: u16,
) -> NonZeroDuration {
// Per the RFC, REGEN_ADVANCE in seconds =
// 2 + (TEMP_IDGEN_RETRIES * DupAddrDetectTransmits * RetransTimer / 1000)
//
// where RetransTimer is in milliseconds. Since values here are kept as
// Durations, there is no need to apply scale factors.
MIN_REGEN_ADVANCE
+ retrans_timer
.checked_mul(u32::from(temp_idgen_retries) * u32::from(dad_transmits))
.unwrap_or(Duration::ZERO)
}
/// Computes the DESYNC_FACTOR as specified in [RFC 8981 section 3.8].
///
/// Per the RFC,
///
/// DESYNC_FACTOR
/// A random value within the range 0 - MAX_DESYNC_FACTOR. It
/// is computed each time a temporary address is generated, and
/// is associated with the corresponding address. It MUST be
/// smaller than (TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE).
///
/// Returns `None` if a DESYNC_FACTOR value cannot be calculated. This will
/// occur when REGEN_ADVANCE is larger than TEMP_PREFERRED_LIFETIME as no valid
/// DESYNC_FACTOR exists that is greater than or equal to 0.
///
/// [RFC 8981 Section 3.8]: http://tools.ietf.org/html/rfc8981#section-3.8
fn desync_factor<R: RngCore>(
rng: &mut R,
temp_preferred_lifetime: NonZeroDuration,
regen_advance: NonZeroDuration,
) -> Option<Duration> {
let temp_preferred_lifetime = temp_preferred_lifetime.get();
// Per RFC 8981 Section 3.8:
// MAX_DESYNC_FACTOR
// 0.4 * TEMP_PREFERRED_LIFETIME. Upper bound on DESYNC_FACTOR.
//
// | Rationale: Setting MAX_DESYNC_FACTOR to 0.4
// | TEMP_PREFERRED_LIFETIME results in addresses that have
// | statistically different lifetimes, and a maximum of three
// | concurrent temporary addresses when the default values
// | specified in this section are employed.
// DESYNC_FACTOR
// A random value within the range 0 - MAX_DESYNC_FACTOR. It
// is computed each time a temporary address is generated, and
// is associated with the corresponding address. It MUST be
// smaller than (TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE).
temp_preferred_lifetime.checked_sub(regen_advance.get()).map(|max_desync_factor| {
let max_desync_factor =
core::cmp::min(max_desync_factor, (temp_preferred_lifetime * 2) / 5);
rng.sample(Uniform::new(Duration::ZERO, max_desync_factor))
})
}
fn regenerate_temporary_slaac_addr<BC: SlaacBindingsContext, CC: SlaacContext<BC>>(
bindings_ctx: &mut BC,
addrs_config: SlaacAddrsMutAndConfig<'_, BC, CC::SlaacAddrs<'_>>,
slaac_state: &mut SlaacState<BC>,
device_id: &CC::DeviceId,
addr_subnet: &AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
) {
let SlaacAddrsMutAndConfig {
addrs: slaac_addrs,
config,
dad_transmits,
retrans_timer,
interface_identifier: iid,
_marker,
} = addrs_config;
let SlaacState { timers } = slaac_state;
let now = bindings_ctx.now();
enum Action {
SkipRegen,
Regen { valid_for: NonZeroDuration, preferred_for: Duration },
}
let action = slaac_addrs.with_addrs(|addrs| {
let entry = {
let mut found_entry = None;
for entry in addrs {
if entry.addr_sub.subnet() != addr_subnet.subnet() {
continue;
}
// It's possible that there are multiple non-deprecated temporary
// addresses in a subnet for this host (if prefix updates are received
// after regen but before deprecation). Per RFC 8981 Section 3.5:
//
// Note that, in normal operation, except for the transient period
// when a temporary address is being regenerated, at most one
// temporary address per prefix should be in a nondeprecated state at
// any given time on a given interface.
//
// In order to tend towards only one non-deprecated temporary address on
// a subnet, we ignore all but the last regen timer for the
// non-deprecated addresses in a subnet.
if !entry.deprecated {
if let Some((entry, regen_at)) = timers
.get(&InnerSlaacTimerId::RegenerateTemporaryAddress {
addr_subnet: entry.addr_sub,
})
.map(|(instant, ())| (entry, instant))
{
debug!(
"ignoring regen event at {:?} for {:?} since {:?} \
will regenerate after at {:?}",
bindings_ctx.now(),
addr_subnet,
entry.addr_sub.addr(),
regen_at
);
return Action::SkipRegen;
}
}
if &entry.addr_sub == addr_subnet {
assert_matches!(found_entry, None);
found_entry = Some(entry);
}
}
found_entry.unwrap_or_else(|| panic!("couldn't find {:?} to regenerate", addr_subnet))
};
assert!(!entry.deprecated, "can't regenerate deprecated address {:?}", addr_subnet);
let TemporarySlaacConfig { creation_time, desync_factor, valid_until, dad_counter: _ } =
match entry.config {
SlaacConfig::Temporary(temporary_config) => temporary_config,
SlaacConfig::Static { valid_until: _ } => unreachable!(
"can't regenerate a temporary address for {:?}, which is static",
addr_subnet
),
};
let SlaacConfiguration { enable_stable_addresses: _, temporary_address_configuration } =
config;
let TemporarySlaacAddressConfiguration {
temp_valid_lifetime,
temp_preferred_lifetime: _,
temp_idgen_retries: _,
secret_key: _,
} = match temporary_address_configuration {
Some(configuration) => configuration,
None => return Action::SkipRegen,
};
let (deprecate_at, ()) = timers
.get(&InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_subnet.addr() })
.unwrap_or_else(|| {
unreachable!(
"temporary SLAAC address {:?} had a regen timer fire but \
does not have a deprecation timer",
addr_subnet.addr()
)
});
let preferred_for = deprecate_at.duration_since(creation_time) + desync_factor;
// It's possible this `valid_for` value is larger than `temp_valid_lifetime`
// (e.g. if the NDP configuration was changed since this address was
// generated). That's okay, because `add_slaac_addr_sub` will apply the
// current maximum valid lifetime when called below.
let valid_for = NonZeroDuration::new(valid_until.duration_since(creation_time))
.unwrap_or(temp_valid_lifetime);
Action::Regen { valid_for, preferred_for }
});
match action {
Action::SkipRegen => {}
Action::Regen { valid_for, preferred_for } => add_slaac_addr_sub::<_, CC>(
slaac_addrs,
slaac_state,
bindings_ctx,
device_id,
now,
SlaacInitConfig::Temporary { dad_count: 0 },
NonZeroNdpLifetime::Finite(valid_for),
NonZeroDuration::new(preferred_for).map(NonZeroNdpLifetime::Finite),
&addr_subnet.subnet(),
config,
dad_transmits,
retrans_timer,
iid,
),
}
}
#[derive(Copy, Clone, Debug)]
enum SlaacInitConfig {
Static,
Temporary { dad_count: u8 },
}
impl SlaacInitConfig {
fn new(slaac_type: SlaacType) -> Self {
match slaac_type {
SlaacType::Static => Self::Static,
SlaacType::Temporary => Self::Temporary { dad_count: 0 },
}
}
}
/// Checks whether the address has an IID that doesn't conflict with existing
/// IANA reserved ranges.
///
/// Compares against the ranges defined by various RFCs and listed at
/// https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml
fn has_iana_allowed_iid(address: Ipv6Addr) -> bool {
let mut iid = [0u8; 8];
const U64_SUFFIX_LEN: usize = Ipv6Addr::BYTES as usize - u64::BITS as usize / 8;
iid.copy_from_slice(&address.bytes()[U64_SUFFIX_LEN..]);
let iid = u64::from_be_bytes(iid);
match iid {
// Subnet-Router Anycast
0x0000_0000_0000_0000 => false,
// Consolidated match for
// - Ethernet Block: 0x200:5EFF:FE00:0000-0200:4EFF:FE00:5212
// - Proxy Mobile: 0x200:5EFF:FE00:5213
// - Ethernet Block: 0x200:5EFF:FE00:5214-0200:4EFF:FEFF:FFFF
0x0200_5EFF_FE00_0000..=0x0200_5EFF_FEFF_FFFF => false,
// Subnet Anycast Addresses
0xFDFF_FFFF_FFFF_FF80..=0xFDFF_FFFF_FFFF_FFFF => false,
// All other IIDs not in the reserved ranges
_iid => true,
}
}
/// Generate an IPv6 Global Address as defined by RFC 4862 section 5.5.3.d.
///
/// The generated address will be of the format:
///
/// | 128 - N bits | N bits |
/// +---------------------------------------+------------------------+
/// | link prefix | interface identifier |
/// +----------------------------------------------------------------+
///
/// # Panics
///
/// Panics if a valid IPv6 unicast address cannot be formed with the provided
/// prefix and interface identifier, or if the prefix length is not a multiple
/// of 8 bits.
fn generate_global_static_address(
prefix: &Subnet<Ipv6Addr>,
iid: &[u8],
) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
if prefix.prefix() % 8 != 0 {
unimplemented!("generate_global_address: not implemented for when prefix length is not a multiple of 8 bits");
}
let prefix_len = prefix.prefix() / u8::try_from(u8::BITS).unwrap();
assert_eq!(usize::from(Ipv6Addr::BYTES - prefix_len), iid.len());
let mut address = prefix.network().ipv6_bytes();
address[prefix_len.into()..].copy_from_slice(&iid);
let address = AddrSubnet::new(Ipv6Addr::from(address), prefix.prefix()).unwrap();
assert_eq!(address.subnet(), *prefix);
address
}
/// Generate a temporary IPv6 Global Address as defined by RFC 8981 section 3.4.6
///
/// The generated address will be of the format:
///
/// | 128 - N bits | N bits |
/// +--------------------------------------+-------------------------+
/// | link prefix | randomized identifier |
/// +----------------------------------------------------------------+
///
/// # Panics
///
/// Panics if a valid IPv6 unicast address cannot be formed with the provided
/// prefix, or if the prefix length is not a multiple of 8 bits.
fn generate_global_temporary_address(
prefix: &Subnet<Ipv6Addr>,
iid: &[u8; 8],
seed: u64,
secret_key: &[u8; STABLE_IID_SECRET_KEY_BYTES],
) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
let prefix_len = usize::from(prefix.prefix() / 8);
assert_eq!(usize::from(Ipv6Addr::BYTES) - prefix_len, iid.len());
let mut address = prefix.network().ipv6_bytes();
let interface_identifier = generate_opaque_interface_identifier(
/* prefix */ *prefix,
/* net_iface */ iid,
/* net_id */ [],
/* nonce */ OpaqueIidNonce::Random(seed),
/* secret_key */ secret_key,
);
let suffix_bytes = &interface_identifier.to_be_bytes()[..(address.len() - prefix_len)];
address[prefix_len..].copy_from_slice(suffix_bytes);
let address = AddrSubnet::new(Ipv6Addr::from(address), prefix.prefix()).unwrap();
assert_eq!(address.subnet(), *prefix);
address
}
fn add_slaac_addr_sub<BC: SlaacBindingsContext, CC: SlaacContext<BC>>(
slaac_addrs: &mut CC::SlaacAddrs<'_>,
slaac_state: &mut SlaacState<BC>,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
now: BC::Instant,
slaac_config: SlaacInitConfig,
prefix_valid_for: NonZeroNdpLifetime,
prefix_preferred_for: Option<NonZeroNdpLifetime>,
subnet: &Subnet<Ipv6Addr>,
config: SlaacConfiguration,
dad_transmits: Option<NonZeroU16>,
retrans_timer: Duration,
iid: [u8; 8],
) {
if subnet.prefix() != REQUIRED_PREFIX_BITS {
// If the sum of the prefix length and interface identifier length does
// not equal 128 bits, the Prefix Information option MUST be ignored, as
// per RFC 4862 section 5.5.3.
error!(
"receive_ndp_packet: autonomous prefix length {:?} and interface identifier length {:?} cannot form valid IPv6 address, ignoring",
subnet.prefix(),
REQUIRED_PREFIX_BITS
);
return;
}
struct PreferredForAndRegenAt<Instant>(NonZeroNdpLifetime, Option<Instant>);
let SlaacConfiguration { enable_stable_addresses, temporary_address_configuration } = config;
let SlaacState { timers } = slaac_state;
let (valid_until, preferred_and_regen, slaac_config, mut addresses) = match slaac_config {
SlaacInitConfig::Static => {
if !enable_stable_addresses {
trace!("stable SLAAC addresses are disabled on device {:?}", device_id);
return;
}
let valid_until = match prefix_valid_for {
NonZeroNdpLifetime::Finite(d) => {
Lifetime::Finite(now.checked_add(d.get()).unwrap())
}
NonZeroNdpLifetime::Infinite => Lifetime::Infinite,
};
(
valid_until,
prefix_preferred_for.map(|p| PreferredForAndRegenAt(p, None)),
SlaacConfig::Static { valid_until },
// Generate the global address as defined by RFC 4862 section 5.5.3.d.
//
// TODO(https://fxbug.dev/42178008): Support regenerating address.
either::Either::Left(core::iter::once(generate_global_static_address(
&subnet,
&iid[..],
))),
)
}
SlaacInitConfig::Temporary { dad_count } => {
let temporary_address_config = match temporary_address_configuration {
Some(temporary_address_config) => temporary_address_config,
None => {
trace!(
"receive_ndp_packet: temporary addresses are disabled on device {:?}",
device_id
);
return;
}
};
let per_attempt_random_seed = bindings_ctx.rng().next_u64();
// Per RFC 8981 Section 3.4.4:
// When creating a temporary address, DESYNC_FACTOR MUST be computed
// and associated with the newly created address, and the address
// lifetime values MUST be derived from the corresponding prefix as
// follows:
//
// * Its valid lifetime is the lower of the Valid Lifetime of the
// prefix and TEMP_VALID_LIFETIME.
//
// * Its preferred lifetime is the lower of the Preferred Lifetime
// of the prefix and TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR.
let valid_for = match prefix_valid_for {
NonZeroNdpLifetime::Finite(prefix_valid_for) => {
core::cmp::min(prefix_valid_for, temporary_address_config.temp_valid_lifetime)
}
NonZeroNdpLifetime::Infinite => temporary_address_config.temp_valid_lifetime,
};
let regen_advance = regen_advance(
temporary_address_config.temp_idgen_retries,
retrans_timer,
dad_transmits.map_or(0, NonZeroU16::get),
);
let secret_key = temporary_address_config.secret_key;
let mut seed = per_attempt_random_seed;
let addresses = either::Either::Right(core::iter::repeat_with(move || {
// RFC 8981 Section 3.3.3 specifies that
//
// The resulting IID MUST be compared against the reserved
// IPv6 IIDs and against those IIDs already employed in an
// address of the same network interface and the same network
// prefix. In the event that an unacceptable identifier has
// been generated, the DAD_Counter should be incremented by 1,
// and the algorithm should be restarted from the first step.
loop {
let address =
generate_global_temporary_address(&subnet, &iid, seed, &secret_key);
seed = seed.wrapping_add(1);
if has_iana_allowed_iid(address.addr().get()) {
break address;
}
}
}));
let valid_until = now.checked_add(valid_for.get()).unwrap();
let desync_factor = if let Some(d) = desync_factor(
&mut bindings_ctx.rng(),
temporary_address_config.temp_preferred_lifetime,
regen_advance,
) {
d
} else {
// We only fail to calculate a desync factor when the configured
// maximum temporary address preferred lifetime is less than
// REGEN_ADVANCE and per RFC 8981 Section 3.4.5,
//
// A temporary address is created only if this calculated
// preferred lifetime is greater than REGEN_ADVANCE time
// units.
trace!(
"failed to calculate DESYNC_FACTOR; temp_preferred_lifetime={:?}, regen_advance={:?}",
temporary_address_config.temp_preferred_lifetime,
regen_advance,
);
return;
};
let preferred_for = prefix_preferred_for.and_then(|prefix_preferred_for| {
temporary_address_config
.temp_preferred_lifetime
.get()
.checked_sub(desync_factor)
.and_then(NonZeroDuration::new)
.map(|d| prefix_preferred_for.min_finite_duration(d))
});
// RFC 8981 Section 3.4.5:
//
// A temporary address is created only if this calculated
// preferred lifetime is greater than REGEN_ADVANCE time
// units.
let preferred_for_and_regen_at = match preferred_for {
None => return,
Some(preferred_for) => match preferred_for.get().checked_sub(regen_advance.get()) {
Some(before_regen) => PreferredForAndRegenAt(
NonZeroNdpLifetime::Finite(preferred_for),
Some(now.checked_add(before_regen).unwrap()),
),
None => {
trace!("receive_ndp_packet: preferred lifetime of {:?} for subnet {:?} is too short to allow regen", preferred_for, subnet);
return;
}
},
};
(
Lifetime::Finite(valid_until),
Some(preferred_for_and_regen_at),
SlaacConfig::Temporary(TemporarySlaacConfig {
desync_factor,
valid_until,
creation_time: now,
dad_counter: dad_count,
}),
addresses,
)
}
};
// Attempt to add the address to the device.
loop {
let address = match addresses.next() {
Some(address) => address,
// No more addresses to try - do nothing further.
None => {
trace!(
"exhausted possible SLAAC addresses without assigning on device {:?}",
device_id
);
return;
}
};
// TODO(https://fxbug.dev/42172850): Should bindings be the one to actually
// assign the address to maintain a "single source of truth"?
let res = slaac_addrs.add_addr_sub_and_then(
bindings_ctx,
address,
slaac_config,
|SlaacAddressEntryMut { addr_sub, config: _, deprecated }, ctx| {
// Set the valid lifetime for this address.
//
// Must not have reached this point if the address was already assigned
// to a device.
match valid_until {
Lifetime::Finite(valid_until) => {
assert_eq!(
timers.schedule_instant(
ctx,
InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() },
(),
valid_until,
),
None
);
}
Lifetime::Infinite => {}
}
let deprecate_timer_id =
InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
// Set the preferred lifetime for this address.
//
// Must not have reached this point if the address was already assigned
// to a device.
match preferred_and_regen {
// Use `schedule_timer_instant` instead of `schedule_timer` to set the timeout
// relative to the previously recorded `now` value. This helps prevent skew in
// cases where this task gets preempted and isn't scheduled for some period of
// time between recording `now` and here.
Some(PreferredForAndRegenAt(preferred_for, regen_at)) => {
match preferred_for {
NonZeroNdpLifetime::Finite(preferred_for) => {
assert_eq!(
timers.schedule_instant(
ctx,
deprecate_timer_id,
(),
now.checked_add(preferred_for.get()).unwrap(),
),
None
);
}
NonZeroNdpLifetime::Infinite => {}
}
match regen_at {
Some(regen_at) => assert_eq!(
timers.schedule_instant(
ctx,
InnerSlaacTimerId::RegenerateTemporaryAddress {
addr_subnet: addr_sub
},
(),
regen_at,
),
None
),
None => (),
}
}
None => {
*deprecated = true;
assert_eq!(timers.cancel(ctx, &deprecate_timer_id), None);
}
};
addr_sub
},
);
match res {
Err(ExistsError) => {
trace!("IPv6 SLAAC address {:?} already exists on device {:?}", address, device_id);
// Try the next address.
//
// TODO(https://fxbug.dev/42050670): Limit number of attempts.
slaac_addrs.increment(|counters| &counters.generated_slaac_addr_exists);
}
Ok(addr_sub) => {
trace!("receive_ndp_packet: Successfully configured new IPv6 address {:?} on device {:?} via SLAAC", addr_sub, device_id);
break;
}
}
}
}
#[cfg(test)]
pub(crate) mod testutil {
use super::*;
use alloc::collections::HashMap;
use net_types::ip::Ipv6;
use crate::ip::device::{IpDeviceBindingsContext, Ipv6DeviceConfigurationContext};
pub(crate) fn collect_slaac_timers_integration<CC, BC>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
) -> HashMap<InnerSlaacTimerId, BC::Instant>
where
CC: Ipv6DeviceConfigurationContext<BC>,
for<'a> CC::Ipv6DeviceStateCtx<'a>: SlaacContext<BC>,
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId> + SlaacBindingsContext,
{
core_ctx.with_ipv6_device_configuration(device_id, |_, mut core_ctx| {
core_ctx.with_slaac_addrs_mut(device_id, |_, state| {
state.timers().iter().map(|(k, (), t)| (*k, *t)).collect::<HashMap<_, _>>()
})
})
}
}
#[cfg(test)]
mod tests {
use core::convert::TryFrom as _;
use assert_matches::assert_matches;
use net_declare::net::ip_v6;
use net_types::{ethernet::Mac, ip::Ipv6, LinkLocalAddress as _, NonMappedAddr};
use netstack3_base::IntoCoreTimerCtx;
use packet::{Buf, InnerPacketBuilder as _, Serializer as _};
use packet_formats::{
icmp::{
ndp::{
options::{NdpOptionBuilder, PrefixInformation},
OptionSequenceBuilder, RouterAdvertisement,
},
IcmpPacketBuilder, IcmpUnusedCode,
},
ip::Ipv6Proto,
ipv6::Ipv6PacketBuilder,
};
use test_case::test_case;
use super::*;
use crate::{
context::testutil::{
FakeBindingsCtx, FakeCoreCtx, FakeCtx, FakeInstant, FakeTimerCtxExt as _,
},
device::{
ethernet::{EthernetCreationProperties, EthernetLinkDevice},
testutil::{FakeDeviceId, FakeWeakDeviceId},
FrameDestination,
},
ip::{
device::{
testutil::with_assigned_ipv6_addr_subnets, IpDeviceConfigurationUpdate,
Ipv6DeviceConfigurationUpdate,
},
icmp::REQUIRED_NDP_IP_PACKET_HOP_LIMIT,
testutil::FakeIpDeviceIdCtx,
},
testutil::{
assert_empty, FakeCryptoRng, FakeEventDispatcherConfig, TestIpExt as _,
DEFAULT_INTERFACE_METRIC, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
};
struct FakeSlaacContext {
config: SlaacConfiguration,
dad_transmits: Option<NonZeroU16>,
retrans_timer: Duration,
iid: [u8; 8],
slaac_addrs: FakeSlaacAddrs,
ip_device_id_ctx: FakeIpDeviceIdCtx<FakeDeviceId>,
slaac_state: SlaacState<FakeBindingsCtxImpl>,
}
impl AsRef<FakeIpDeviceIdCtx<FakeDeviceId>> for FakeSlaacContext {
fn as_ref(&self) -> &FakeIpDeviceIdCtx<FakeDeviceId> {
&self.ip_device_id_ctx
}
}
type FakeCoreCtxImpl = FakeCoreCtx<FakeSlaacContext, (), FakeDeviceId>;
type FakeBindingsCtxImpl =
FakeBindingsCtx<SlaacTimerId<FakeWeakDeviceId<FakeDeviceId>>, (), (), ()>;
#[derive(Default)]
struct FakeSlaacAddrs {
slaac_addrs: Vec<SlaacAddressEntry<FakeInstant>>,
non_slaac_addr: Option<Ipv6DeviceAddr>,
counters: SlaacCounters,
}
impl<'a> CounterContext<SlaacCounters> for &'a mut FakeSlaacAddrs {
fn with_counters<O, F: FnOnce(&SlaacCounters) -> O>(&self, cb: F) -> O {
cb(&self.counters)
}
}
impl<'a> SlaacAddresses<FakeBindingsCtxImpl> for &'a mut FakeSlaacAddrs {
fn for_each_addr_mut<F: FnMut(SlaacAddressEntryMut<'_, FakeInstant>)>(
&mut self,
mut cb: F,
) {
let FakeSlaacAddrs { slaac_addrs, non_slaac_addr: _, counters: _ } = self;
slaac_addrs.iter_mut().for_each(|SlaacAddressEntry { addr_sub, config, deprecated }| {
cb(SlaacAddressEntryMut { addr_sub: *addr_sub, config, deprecated })
})
}
type AddrsIter<'b> =
core::iter::Cloned<core::slice::Iter<'b, SlaacAddressEntry<FakeInstant>>>;
fn with_addrs<O, F: FnOnce(Self::AddrsIter<'_>) -> O>(&mut self, cb: F) -> O {
let FakeSlaacAddrs { slaac_addrs, non_slaac_addr: _, counters: _ } = self;
cb(slaac_addrs.iter().cloned())
}
fn add_addr_sub_and_then<
O,
F: FnOnce(SlaacAddressEntryMut<'_, FakeInstant>, &mut FakeBindingsCtxImpl) -> O,
>(
&mut self,
bindings_ctx: &mut FakeBindingsCtxImpl,
add_addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
config: SlaacConfig<FakeInstant>,
and_then: F,
) -> Result<O, ExistsError> {
let FakeSlaacAddrs { slaac_addrs, non_slaac_addr, counters: _ } = self;
if non_slaac_addr.is_some_and(|a| a == add_addr_sub.addr()) {
return Err(ExistsError);
}
if slaac_addrs.iter_mut().any(|e| e.addr_sub.addr() == add_addr_sub.addr()) {
return Err(ExistsError);
}
slaac_addrs.push(SlaacAddressEntry {
addr_sub: add_addr_sub,
config,
deprecated: false,
});
let SlaacAddressEntry { addr_sub, config, deprecated } =
slaac_addrs.iter_mut().last().unwrap();
Ok(and_then(
SlaacAddressEntryMut { addr_sub: *addr_sub, config, deprecated },
bindings_ctx,
))
}
fn remove_addr(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
addr: &Ipv6DeviceAddr,
) -> Result<(AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, SlaacConfig<FakeInstant>), NotFoundError>
{
let FakeSlaacAddrs { slaac_addrs, non_slaac_addr: _, counters: _ } = self;
slaac_addrs
.iter()
.enumerate()
.find_map(|(i, a)| (&a.addr_sub.addr() == addr).then(|| i))
.ok_or(NotFoundError)
.map(|i| {
let SlaacAddressEntry { addr_sub, config, deprecated: _ } =
slaac_addrs.remove(i);
(addr_sub, config)
})
}
}
impl SlaacContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
type SlaacAddrs<'a> = &'a mut FakeSlaacAddrs where FakeCoreCtxImpl: 'a;
fn with_slaac_addrs_mut_and_configs<
O,
F: FnOnce(
SlaacAddrsMutAndConfig<'_, FakeBindingsCtxImpl, &'_ mut FakeSlaacAddrs>,
&mut SlaacState<FakeBindingsCtxImpl>,
) -> O,
>(
&mut self,
&FakeDeviceId: &FakeDeviceId,
cb: F,
) -> O {
let FakeSlaacContext {
config,
dad_transmits,
retrans_timer,
iid,
slaac_addrs,
ip_device_id_ctx: _,
slaac_state,
} = self.get_mut();
let mut slaac_addrs = slaac_addrs;
cb(
SlaacAddrsMutAndConfig {
addrs: &mut slaac_addrs,
config: *config,
dad_transmits: *dad_transmits,
retrans_timer: *retrans_timer,
interface_identifier: *iid,
_marker: PhantomData,
},
slaac_state,
)
}
}
impl FakeSlaacContext {
fn iter_slaac_addrs(&self) -> impl Iterator<Item = SlaacAddressEntry<FakeInstant>> + '_ {
self.slaac_addrs.slaac_addrs.iter().cloned()
}
}
fn new_timer_id() -> SlaacTimerId<FakeWeakDeviceId<FakeDeviceId>> {
SlaacTimerId { device_id: FakeWeakDeviceId(FakeDeviceId) }
}
fn new_context(
config: SlaacConfiguration,
slaac_addrs: FakeSlaacAddrs,
dad_transmits: Option<NonZeroU16>,
retrans_timer: Duration,
) -> crate::testutil::ContextPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
FakeCoreCtxImpl::with_state(FakeSlaacContext {
config,
dad_transmits,
retrans_timer,
iid: IID,
slaac_addrs,
ip_device_id_ctx: Default::default(),
slaac_state: SlaacState::new::<_, IntoCoreTimerCtx>(
bindings_ctx,
FakeWeakDeviceId(FakeDeviceId),
),
})
})
}
#[test_case(ip_v6!("1:2:3:4::"), false; "subnet-router anycast")]
#[test_case(ip_v6!("::1"), true; "allowed 1")]
#[test_case(ip_v6!("1:2:3:4::1"), true; "allowed 2")]
#[test_case(ip_v6!("4:4:4:4:0200:5eff:fe00:1"), false; "first ethernet block")]
#[test_case(ip_v6!("1:1:1:1:0200:5eff:fe00:5213"), false; "proxy mobile")]
#[test_case(ip_v6!("8:8:8:8:0200:5eff:fe00:8000"), false; "second ethernet block")]
#[test_case(ip_v6!("a:a:a:a:fdff:ffff:ffff:ffaa"), false; "subnet anycast")]
#[test_case(ip_v6!("c:c:c:c:fe00::"), true; "allowed 3")]
fn test_has_iana_allowed_iid(addr: Ipv6Addr, expect_allowed: bool) {
assert_eq!(has_iana_allowed_iid(addr), expect_allowed);
}
const IID: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
const DEFAULT_RETRANS_TIMER: Duration = Duration::from_secs(1);
const SUBNET: Subnet<Ipv6Addr> =
unsafe { Subnet::new_unchecked(Ipv6Addr::new([0x200a, 0, 0, 0, 0, 0, 0, 0]), 64) };
#[test_case(0, 0, true; "zero lifetimes")]
#[test_case(2, 1, true; "preferred larger than valid")]
#[test_case(1, 2, false; "disabled")]
fn dont_generate_address(
preferred_lifetime_secs: u32,
valid_lifetime_secs: u32,
enable_stable_addresses: bool,
) {
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration { enable_stable_addresses, ..Default::default() },
Default::default(),
None,
DEFAULT_RETRANS_TIMER,
);
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
);
assert_empty(core_ctx.get_ref().iter_slaac_addrs());
bindings_ctx.timer_ctx().assert_no_timers_installed();
}
fn calculate_addr_sub(
subnet: Subnet<Ipv6Addr>,
iid: [u8; 8],
) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
let mut bytes = subnet.network().ipv6_bytes();
bytes[8..].copy_from_slice(&iid);
AddrSubnet::new(Ipv6Addr::from_bytes(bytes), subnet.prefix()).unwrap()
}
#[test_case(0; "deprecated")]
#[test_case(1; "preferred")]
fn generate_stable_address(preferred_lifetime_secs: u32) {
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration { enable_stable_addresses: true, ..Default::default() },
Default::default(),
None,
DEFAULT_RETRANS_TIMER,
);
let valid_lifetime_secs = preferred_lifetime_secs + 1;
let addr_sub = calculate_addr_sub(SUBNET, IID);
// Generate a new SLAAC address.
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
);
let address_created_deprecated = preferred_lifetime_secs == 0;
let now = bindings_ctx.now();
let valid_until = now + Duration::from_secs(valid_lifetime_secs.into());
let entry = SlaacAddressEntry {
addr_sub,
config: SlaacConfig::Static { valid_until: Lifetime::Finite(valid_until) },
deprecated: address_created_deprecated,
};
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [entry],);
let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
let invalidate_timer_id =
InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
if !address_created_deprecated {
core_ctx.state.slaac_state.timers.assert_timers([
(deprecate_timer_id, (), now + Duration::from_secs(preferred_lifetime_secs.into())),
(invalidate_timer_id, (), valid_until),
]);
// Trigger the deprecation timer.
assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
let entry = SlaacAddressEntry { deprecated: true, ..entry };
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
}
core_ctx.state.slaac_state.timers.assert_timers([(invalidate_timer_id, (), valid_until)]);
// Trigger the invalidation timer.
assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
assert_empty(core_ctx.get_ref().iter_slaac_addrs());
bindings_ctx.timer_ctx().assert_no_timers_installed();
}
#[test]
fn stable_address_conflict() {
let addr_sub = calculate_addr_sub(SUBNET, IID);
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration { enable_stable_addresses: true, ..Default::default() },
FakeSlaacAddrs {
slaac_addrs: Default::default(),
// Consider the address we will generate as already assigned without
// SLAAC.
non_slaac_addr: Some(addr_sub.addr()),
counters: Default::default(),
},
None,
DEFAULT_RETRANS_TIMER,
);
const LIFETIME_SECS: u32 = 1;
// Generate a new SLAAC address.
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
);
assert_empty(core_ctx.get_ref().iter_slaac_addrs());
bindings_ctx.timer_ctx().assert_no_timers_installed();
}
#[test_case(AddressRemovedReason::Manual; "manual")]
#[test_case(AddressRemovedReason::DadFailed; "dad failed")]
fn remove_stable_address(reason: AddressRemovedReason) {
let addr_sub = calculate_addr_sub(SUBNET, IID);
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration { enable_stable_addresses: true, ..Default::default() },
Default::default(),
None,
DEFAULT_RETRANS_TIMER,
);
const LIFETIME_SECS: u32 = 1;
// Generate a new SLAAC address.
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS),
);
let now = bindings_ctx.now();
let valid_until = now + Duration::from_secs(LIFETIME_SECS.into());
let entry = SlaacAddressEntry {
addr_sub,
config: SlaacConfig::Static { valid_until: Lifetime::Finite(valid_until) },
deprecated: false,
};
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
let invalidate_timer_id =
InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
core_ctx.state.slaac_state.timers.assert_timers([
(deprecate_timer_id, (), now + Duration::from_secs(LIFETIME_SECS.into())),
(invalidate_timer_id, (), valid_until),
]);
// Remove the address and let SLAAC know the address was removed
let config = {
let SlaacAddressEntry { addr_sub: got_addr_sub, config, deprecated } =
core_ctx.get_mut().slaac_addrs.slaac_addrs.remove(0);
assert_eq!(addr_sub, got_addr_sub);
assert!(!deprecated);
config
};
SlaacHandler::on_address_removed(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
addr_sub,
config,
reason,
);
bindings_ctx.timer_ctx().assert_no_timers_installed();
}
struct RefreshStableAddressTimersTest {
orig_pl_secs: u32,
orig_vl_secs: u32,
new_pl_secs: u32,
new_vl_secs: u32,
effective_new_vl_secs: u32,
}
const ONE_HOUR_AS_SECS: u32 = 60 * 60;
const TWO_HOURS_AS_SECS: u32 = ONE_HOUR_AS_SECS * 2;
const THREE_HOURS_AS_SECS: u32 = ONE_HOUR_AS_SECS * 3;
const INFINITE_LIFETIME: u32 = u32::MAX;
const MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS: u32 =
MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE.as_secs() as u32;
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: 1,
orig_vl_secs: 1,
new_pl_secs: 1,
new_vl_secs: 1,
effective_new_vl_secs: 1,
}; "do nothing")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: 1,
orig_vl_secs: 1,
new_pl_secs: 2,
new_vl_secs: 2,
effective_new_vl_secs: 2,
}; "increase lifetimes")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: 1,
orig_vl_secs: 1,
new_pl_secs: 0,
new_vl_secs: 1,
effective_new_vl_secs: 1,
}; "deprecate address only")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: 0,
orig_vl_secs: 1,
new_pl_secs: 1,
new_vl_secs: 1,
effective_new_vl_secs: 1,
}; "undeprecate address")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: 1,
orig_vl_secs: 1,
new_pl_secs: 0,
new_vl_secs: 0,
effective_new_vl_secs: 1,
}; "deprecate address only with new valid lifetime of zero")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: ONE_HOUR_AS_SECS,
orig_vl_secs: ONE_HOUR_AS_SECS,
new_pl_secs: ONE_HOUR_AS_SECS - 1,
new_vl_secs: ONE_HOUR_AS_SECS - 1,
effective_new_vl_secs: ONE_HOUR_AS_SECS,
}; "decrease preferred lifetime and ignore new valid lifetime if less than 2 hours and remaining lifetime")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: THREE_HOURS_AS_SECS,
orig_vl_secs: THREE_HOURS_AS_SECS,
new_pl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
}; "deprecate address only and bring valid lifetime down to 2 hours at max")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: ONE_HOUR_AS_SECS - 1,
orig_vl_secs: ONE_HOUR_AS_SECS - 1,
new_pl_secs: ONE_HOUR_AS_SECS - 1,
new_vl_secs: ONE_HOUR_AS_SECS,
effective_new_vl_secs: ONE_HOUR_AS_SECS,
}; "increase valid lifetime if more than remaining valid lifetime")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: INFINITE_LIFETIME,
orig_vl_secs: INFINITE_LIFETIME,
new_pl_secs: INFINITE_LIFETIME,
new_vl_secs: INFINITE_LIFETIME,
effective_new_vl_secs: INFINITE_LIFETIME,
}; "infinite lifetimes")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: ONE_HOUR_AS_SECS,
orig_vl_secs: TWO_HOURS_AS_SECS,
new_pl_secs: TWO_HOURS_AS_SECS,
new_vl_secs: INFINITE_LIFETIME,
effective_new_vl_secs: INFINITE_LIFETIME,
}; "update valid lifetime from finite to infinite")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: ONE_HOUR_AS_SECS,
orig_vl_secs: TWO_HOURS_AS_SECS,
new_pl_secs: INFINITE_LIFETIME,
new_vl_secs: INFINITE_LIFETIME,
effective_new_vl_secs: INFINITE_LIFETIME,
}; "update both lifetimes from finite to infinite")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: TWO_HOURS_AS_SECS,
orig_vl_secs: INFINITE_LIFETIME,
new_pl_secs: ONE_HOUR_AS_SECS,
new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
}; "update valid lifetime from infinite to finite")]
#[test_case(RefreshStableAddressTimersTest {
orig_pl_secs: INFINITE_LIFETIME,
orig_vl_secs: INFINITE_LIFETIME,
new_pl_secs: ONE_HOUR_AS_SECS,
new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS - 1,
effective_new_vl_secs: MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE_AS_SECS,
}; "update both lifetimes from infinite to finite")]
fn stable_address_timers(
RefreshStableAddressTimersTest {
orig_pl_secs,
orig_vl_secs,
new_pl_secs,
new_vl_secs,
effective_new_vl_secs,
}: RefreshStableAddressTimersTest,
) {
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration { enable_stable_addresses: true, ..Default::default() },
Default::default(),
None,
DEFAULT_RETRANS_TIMER,
);
let addr_sub = calculate_addr_sub(SUBNET, IID);
let deprecate_timer_id = InnerSlaacTimerId::DeprecateSlaacAddress { addr: addr_sub.addr() };
let invalidate_timer_id =
InnerSlaacTimerId::InvalidateSlaacAddress { addr: addr_sub.addr() };
// Generate a new SLAAC address.
let ndp_pl = NonZeroNdpLifetime::from_u32_with_infinite(orig_pl_secs);
let ndp_vl = NonZeroNdpLifetime::from_u32_with_infinite(orig_vl_secs);
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
ndp_pl,
ndp_vl,
);
let address_created_deprecated = ndp_pl.is_none();
let now = bindings_ctx.now();
let mut expected_timers = Vec::new();
let valid_until = match ndp_vl.expect("this test expects to create an address") {
NonZeroNdpLifetime::Finite(d) => {
let valid_until = now + d.get();
expected_timers.push((invalidate_timer_id, (), valid_until));
Lifetime::Finite(valid_until)
}
NonZeroNdpLifetime::Infinite => Lifetime::Infinite,
};
match ndp_pl {
None | Some(NonZeroNdpLifetime::Infinite) => {}
Some(NonZeroNdpLifetime::Finite(d)) => {
expected_timers.push((deprecate_timer_id, (), now + d.get()))
}
}
let entry = SlaacAddressEntry {
addr_sub,
config: SlaacConfig::Static { valid_until },
deprecated: address_created_deprecated,
};
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
core_ctx.state.slaac_state.timers.assert_timers(expected_timers);
// Refresh timers.
let ndp_pl = NonZeroNdpLifetime::from_u32_with_infinite(new_pl_secs);
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
ndp_pl,
NonZeroNdpLifetime::from_u32_with_infinite(new_vl_secs),
);
let mut expected_timers = Vec::new();
let valid_until = match NonZeroNdpLifetime::from_u32_with_infinite(effective_new_vl_secs)
.expect("this test expects to keep the address")
{
NonZeroNdpLifetime::Finite(d) => {
let valid_until = now + d.get();
expected_timers.push((invalidate_timer_id, (), valid_until));
Lifetime::Finite(valid_until)
}
NonZeroNdpLifetime::Infinite => Lifetime::Infinite,
};
match ndp_pl {
None | Some(NonZeroNdpLifetime::Infinite) => {}
Some(NonZeroNdpLifetime::Finite(d)) => {
expected_timers.push((deprecate_timer_id, (), now + d.get()))
}
}
let entry = SlaacAddressEntry {
config: SlaacConfig::Static { valid_until },
deprecated: ndp_pl.is_none(),
..entry
};
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [entry]);
core_ctx.state.slaac_state.timers.assert_timers(expected_timers);
}
const SECRET_KEY: [u8; STABLE_IID_SECRET_KEY_BYTES] = [1; STABLE_IID_SECRET_KEY_BYTES];
const ONE_HOUR: NonZeroDuration =
const_unwrap::const_unwrap_option(NonZeroDuration::from_secs(ONE_HOUR_AS_SECS as u64));
struct DontGenerateTemporaryAddressTest {
preferred_lifetime_config: NonZeroDuration,
preferred_lifetime_secs: u32,
valid_lifetime_secs: u32,
temp_idgen_retries: u8,
dad_transmits: u16,
retrans_timer: Duration,
enable: bool,
}
impl DontGenerateTemporaryAddressTest {
fn with_pl_less_than_regen_advance(
dad_transmits: u16,
retrans_timer: Duration,
temp_idgen_retries: u8,
) -> Self {
DontGenerateTemporaryAddressTest {
preferred_lifetime_config: ONE_HOUR,
preferred_lifetime_secs: u32::try_from(
(MIN_REGEN_ADVANCE.get()
+ (u32::from(temp_idgen_retries)
* u32::from(dad_transmits)
* retrans_timer))
.as_secs(),
)
.unwrap()
- 1,
valid_lifetime_secs: TWO_HOURS_AS_SECS,
temp_idgen_retries,
dad_transmits,
retrans_timer,
enable: true,
}
}
}
#[test_case(DontGenerateTemporaryAddressTest {
preferred_lifetime_config: ONE_HOUR,
preferred_lifetime_secs: ONE_HOUR_AS_SECS,
valid_lifetime_secs: TWO_HOURS_AS_SECS,
temp_idgen_retries: 0,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
enable: false,
}; "disabled")]
#[test_case(DontGenerateTemporaryAddressTest{
preferred_lifetime_config: ONE_HOUR,
preferred_lifetime_secs: 0,
valid_lifetime_secs: 0,
temp_idgen_retries: 0,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
enable: true,
}; "zero lifetimes")]
#[test_case(DontGenerateTemporaryAddressTest {
preferred_lifetime_config: ONE_HOUR,
preferred_lifetime_secs: TWO_HOURS_AS_SECS,
valid_lifetime_secs: ONE_HOUR_AS_SECS,
temp_idgen_retries: 0,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
enable: true,
}; "preferred larger than valid")]
#[test_case(DontGenerateTemporaryAddressTest {
preferred_lifetime_config: ONE_HOUR,
preferred_lifetime_secs: 0,
valid_lifetime_secs: TWO_HOURS_AS_SECS,
temp_idgen_retries: 0,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
enable: true,
}; "not preferred")]
#[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
0 /* dad_transmits */,
DEFAULT_RETRANS_TIMER /* retrans_timer */,
0 /* temp_idgen_retries */,
); "preferred lifetime less than than regen advance with no DAD transmits")]
#[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
1 /* dad_transmits */,
DEFAULT_RETRANS_TIMER /* retrans_timer */,
0 /* temp_idgen_retries */,
); "preferred lifetime less than than regen advance with DAD transmits")]
#[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
1 /* dad_transmits */,
DEFAULT_RETRANS_TIMER /* retrans_timer */,
1 /* temp_idgen_retries */,
); "preferred lifetime less than than regen advance with DAD transmits and retries")]
#[test_case(DontGenerateTemporaryAddressTest::with_pl_less_than_regen_advance(
2 /* dad_transmits */,
DEFAULT_RETRANS_TIMER + Duration::from_secs(1) /* retrans_timer */,
3 /* temp_idgen_retries */,
); "preferred lifetime less than than regen advance with multiple DAD transmits and multiple retries")]
#[test_case(DontGenerateTemporaryAddressTest {
preferred_lifetime_config: MIN_REGEN_ADVANCE,
preferred_lifetime_secs: ONE_HOUR_AS_SECS,
valid_lifetime_secs: TWO_HOURS_AS_SECS,
temp_idgen_retries: 1,
dad_transmits: 1,
retrans_timer: DEFAULT_RETRANS_TIMER,
enable: true,
}; "configured preferred lifetime less than regen advance")]
fn dont_generate_temporary_address(
DontGenerateTemporaryAddressTest {
preferred_lifetime_config,
preferred_lifetime_secs,
valid_lifetime_secs,
temp_idgen_retries,
dad_transmits,
retrans_timer,
enable,
}: DontGenerateTemporaryAddressTest,
) {
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration {
temporary_address_configuration: enable.then(|| {
TemporarySlaacAddressConfiguration {
temp_valid_lifetime: NonZeroDuration::new(Duration::from_secs(
ONE_HOUR_AS_SECS.into(),
))
.unwrap(),
temp_preferred_lifetime: preferred_lifetime_config,
temp_idgen_retries,
secret_key: SECRET_KEY,
}
}),
..Default::default()
},
Default::default(),
NonZeroU16::new(dad_transmits),
retrans_timer,
);
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs),
NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs),
);
assert_empty(core_ctx.get_ref().iter_slaac_addrs());
bindings_ctx.timer_ctx().assert_no_timers_installed();
}
struct GenerateTemporaryAddressTest {
pl_config: u32,
vl_config: u32,
dad_transmits: u16,
retrans_timer: Duration,
temp_idgen_retries: u8,
pl_ra: u32,
vl_ra: u32,
expected_pl_addr: u32,
expected_vl_addr: u32,
}
#[test_case(GenerateTemporaryAddressTest{
pl_config: ONE_HOUR_AS_SECS,
vl_config: ONE_HOUR_AS_SECS,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
temp_idgen_retries: 0,
pl_ra: ONE_HOUR_AS_SECS,
vl_ra: ONE_HOUR_AS_SECS,
expected_pl_addr: ONE_HOUR_AS_SECS,
expected_vl_addr: ONE_HOUR_AS_SECS,
}; "config and prefix same lifetimes")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: ONE_HOUR_AS_SECS,
vl_config: TWO_HOURS_AS_SECS,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
temp_idgen_retries: 0,
pl_ra: THREE_HOURS_AS_SECS,
vl_ra: THREE_HOURS_AS_SECS,
expected_pl_addr: ONE_HOUR_AS_SECS,
expected_vl_addr: TWO_HOURS_AS_SECS,
}; "config smaller than prefix lifetimes")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: TWO_HOURS_AS_SECS,
vl_config: THREE_HOURS_AS_SECS,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
temp_idgen_retries: 0,
pl_ra: ONE_HOUR_AS_SECS,
vl_ra: TWO_HOURS_AS_SECS,
expected_pl_addr: ONE_HOUR_AS_SECS,
expected_vl_addr: TWO_HOURS_AS_SECS,
}; "config larger than prefix lifetimes")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: TWO_HOURS_AS_SECS,
vl_config: THREE_HOURS_AS_SECS,
dad_transmits: 0,
retrans_timer: DEFAULT_RETRANS_TIMER,
temp_idgen_retries: 0,
pl_ra: INFINITE_LIFETIME,
vl_ra: INFINITE_LIFETIME,
expected_pl_addr: TWO_HOURS_AS_SECS,
expected_vl_addr: THREE_HOURS_AS_SECS,
}; "prefix with infinite lifetimes")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: TWO_HOURS_AS_SECS,
vl_config: THREE_HOURS_AS_SECS,
dad_transmits: 1,
retrans_timer: DEFAULT_RETRANS_TIMER,
temp_idgen_retries: 0,
pl_ra: INFINITE_LIFETIME,
vl_ra: INFINITE_LIFETIME,
expected_pl_addr: TWO_HOURS_AS_SECS,
expected_vl_addr: THREE_HOURS_AS_SECS,
}; "generate_with_dad_enabled")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: TWO_HOURS_AS_SECS,
vl_config: THREE_HOURS_AS_SECS,
dad_transmits: 2,
retrans_timer: Duration::from_secs(5),
temp_idgen_retries: 3,
pl_ra: INFINITE_LIFETIME,
vl_ra: INFINITE_LIFETIME,
expected_pl_addr: TWO_HOURS_AS_SECS,
expected_vl_addr: THREE_HOURS_AS_SECS,
}; "generate_with_dad_enabled_and_retries")]
#[test_case(GenerateTemporaryAddressTest{
pl_config: TWO_HOURS_AS_SECS,
vl_config: THREE_HOURS_AS_SECS,
dad_transmits: 1,
retrans_timer: Duration::from_secs(10),
temp_idgen_retries: 0,
pl_ra: INFINITE_LIFETIME,
vl_ra: INFINITE_LIFETIME,
expected_pl_addr: TWO_HOURS_AS_SECS,
expected_vl_addr: THREE_HOURS_AS_SECS,
}; "generate_with_dad_enabled_but_no_retries")]
fn generate_temporary_address(
GenerateTemporaryAddressTest {
pl_config,
vl_config,
dad_transmits,
retrans_timer,
temp_idgen_retries,
pl_ra,
vl_ra,
expected_pl_addr,
expected_vl_addr,
}: GenerateTemporaryAddressTest,
) {
let pl_config = Duration::from_secs(pl_config.into());
let regen_advance = regen_advance(temp_idgen_retries, retrans_timer, dad_transmits);
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context(
SlaacConfiguration {
temporary_address_configuration: Some(TemporarySlaacAddressConfiguration {
temp_valid_lifetime: NonZeroDuration::new(Duration::from_secs(
vl_config.into(),
))
.unwrap(),
temp_preferred_lifetime: NonZeroDuration::new(pl_config).unwrap(),
temp_idgen_retries,
secret_key: SECRET_KEY,
}),
..Default::default()
},
Default::default(),
NonZeroU16::new(dad_transmits),
retrans_timer,
);
let mut dup_rng = bindings_ctx.rng().deep_clone();
struct AddrProps {
desync_factor: Duration,
valid_until: FakeInstant,
preferred_until: FakeInstant,
entry: SlaacAddressEntry<FakeInstant>,
deprecate_timer_id: InnerSlaacTimerId,
invalidate_timer_id: InnerSlaacTimerId,
regenerate_timer_id: InnerSlaacTimerId,
}
let addr_props = |rng: &mut FakeCryptoRng<_>,
creation_time,
config_greater_than_ra_desync_factor_offset| {
let valid_until = creation_time + Duration::from_secs(expected_vl_addr.into());
let addr_sub =
generate_global_temporary_address(&SUBNET, &IID, rng.next_u64(), &SECRET_KEY);
let desync_factor =
desync_factor(rng, NonZeroDuration::new(pl_config).unwrap(), regen_advance)
.unwrap();
AddrProps {
desync_factor,
valid_until,
preferred_until: {
let d = creation_time + Duration::from_secs(expected_pl_addr.into());
if pl_config.as_secs() > pl_ra.into() {
d + config_greater_than_ra_desync_factor_offset
} else {
d - desync_factor
}
},
entry: SlaacAddressEntry {
addr_sub,
config: SlaacConfig::Temporary(TemporarySlaacConfig {
valid_until,
desync_factor,
creation_time,
dad_counter: 0,
}),
deprecated: false,
},
deprecate_timer_id: InnerSlaacTimerId::DeprecateSlaacAddress {
addr: addr_sub.addr(),
},
invalidate_timer_id: InnerSlaacTimerId::InvalidateSlaacAddress {
addr: addr_sub.addr(),
},
regenerate_timer_id: InnerSlaacTimerId::RegenerateTemporaryAddress {
addr_subnet: addr_sub,
},
}
};
// Generate the first temporary SLAAC address.
SlaacHandler::apply_slaac_update(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
SUBNET,
NonZeroNdpLifetime::from_u32_with_infinite(pl_ra),
NonZeroNdpLifetime::from_u32_with_infinite(vl_ra),
);
let AddrProps {
desync_factor: first_desync_factor,
valid_until: first_valid_until,
preferred_until: first_preferred_until,
entry: first_entry,
deprecate_timer_id: first_deprecate_timer_id,
invalidate_timer_id: first_invalidate_timer_id,
regenerate_timer_id: first_regenerate_timer_id,
} = addr_props(&mut dup_rng, bindings_ctx.now(), Duration::ZERO);
assert_eq!(core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(), [first_entry]);
core_ctx.state.slaac_state.timers.assert_timers([
(first_deprecate_timer_id, (), first_preferred_until),
(first_invalidate_timer_id, (), first_valid_until),
(first_regenerate_timer_id, (), first_preferred_until - regen_advance.get()),
]);
// Trigger the regenerate timer to generate the second temporary SLAAC
// address.
assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()),);
let AddrProps {
desync_factor: second_desync_factor,
valid_until: second_valid_until,
preferred_until: second_preferred_until,
entry: second_entry,
deprecate_timer_id: second_deprecate_timer_id,
invalidate_timer_id: second_invalidate_timer_id,
regenerate_timer_id: second_regenerate_timer_id,
} = addr_props(&mut dup_rng, bindings_ctx.now(), first_desync_factor);
assert_eq!(
core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(),
[first_entry, second_entry]
);
let second_regen_at = second_preferred_until - regen_advance.get();
core_ctx.state.slaac_state.timers.assert_timers([
(first_deprecate_timer_id, (), first_preferred_until),
(first_invalidate_timer_id, (), first_valid_until),
(second_deprecate_timer_id, (), second_preferred_until),
(second_invalidate_timer_id, (), second_valid_until),
(second_regenerate_timer_id, (), second_regen_at),
]);
// Deprecate first address.
assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()),);
let first_entry = SlaacAddressEntry { deprecated: true, ..first_entry };
assert_eq!(
core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(),
[first_entry, second_entry]
);
core_ctx.state.slaac_state.timers.assert_timers([
(first_invalidate_timer_id, (), first_valid_until),
(second_deprecate_timer_id, (), second_preferred_until),
(second_invalidate_timer_id, (), second_valid_until),
(second_regenerate_timer_id, (), second_regen_at),
]);
let third_created_at = {
let expected_timer_order = if first_valid_until > second_regen_at {
[second_regenerate_timer_id, second_deprecate_timer_id, first_invalidate_timer_id]
} else {
[first_invalidate_timer_id, second_regenerate_timer_id, second_deprecate_timer_id]
};
let mut third_created_at = None;
for timer_id in expected_timer_order.iter() {
let timer_id = *timer_id;
core_ctx.state.slaac_state.timers.assert_top(&timer_id, &());
assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(new_timer_id()));
if timer_id == second_regenerate_timer_id {
assert_eq!(third_created_at, None);
third_created_at = Some(bindings_ctx.now());
}
}
third_created_at.unwrap()
};
// Make sure we regenerated the third address, deprecated the second and
// invalidated the first.
let AddrProps {
desync_factor: _,
valid_until: third_valid_until,
preferred_until: third_preferred_until,
entry: third_entry,
deprecate_timer_id: third_deprecate_timer_id,
invalidate_timer_id: third_invalidate_timer_id,
regenerate_timer_id: third_regenerate_timer_id,
} = addr_props(&mut dup_rng, third_created_at, first_desync_factor + second_desync_factor);
let second_entry = SlaacAddressEntry { deprecated: true, ..second_entry };
assert_eq!(
core_ctx.get_ref().iter_slaac_addrs().collect::<Vec<_>>(),
[second_entry, third_entry]
);
core_ctx.state.slaac_state.timers.assert_timers([
(second_invalidate_timer_id, (), second_valid_until),
(third_deprecate_timer_id, (), third_preferred_until),
(third_invalidate_timer_id, (), third_valid_until),
(third_regenerate_timer_id, (), third_preferred_until - regen_advance.get()),
]);
}
fn build_slaac_ra_packet(
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
prefix: Ipv6Addr,
prefix_length: u8,
preferred_lifetime_secs: u32,
valid_lifetime_secs: u32,
) -> Buf<Vec<u8>> {
let p = PrefixInformation::new(
prefix_length,
false, /* on_link_flag */
true, /* autonomous_address_configuration_flag */
valid_lifetime_secs,
preferred_lifetime_secs,
prefix,
);
let options = &[NdpOptionBuilder::PrefixInformation(p)];
OptionSequenceBuilder::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterAdvertisement::new(0, false, false, 0, 0, 0),
))
.encapsulate(Ipv6PacketBuilder::new(
src_ip,
dst_ip,
REQUIRED_NDP_IP_PACKET_HOP_LIMIT,
Ipv6Proto::Icmpv6,
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
#[test]
fn integration_remove_all_addresses_on_ipv6_disable() {
let FakeEventDispatcherConfig {
local_mac,
remote_mac,
local_ip: _,
remote_ip: _,
subnet: _,
} = Ipv6::FAKE_CONFIG;
const ONE_HOUR: NonZeroDuration =
const_unwrap::const_unwrap_option(NonZeroDuration::from_secs(ONE_HOUR_AS_SECS as u64));
const TWO_HOURS: NonZeroDuration =
const_unwrap::const_unwrap_option(NonZeroDuration::from_secs(TWO_HOURS_AS_SECS as u64));
let mut ctx = crate::testutil::FakeCtx::default();
let device_id = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
)
.into();
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
temporary_address_configuration: Some(TemporarySlaacAddressConfiguration {
temp_valid_lifetime: ONE_HOUR,
temp_preferred_lifetime: ONE_HOUR,
temp_idgen_retries: 0,
secret_key: SECRET_KEY,
}),
}),
..Default::default()
},
)
.unwrap();
let set_ip_enabled = |ctx: &mut crate::testutil::FakeCtx, enabled| {
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(enabled),
..Default::default()
},
..Default::default()
},
)
.unwrap();
};
set_ip_enabled(&mut ctx, true /* enabled */);
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
// Generate stable and temporary SLAAC addresses.
ctx.test_api().receive_ip_packet::<Ipv6, _>(
&device_id,
Some(FrameDestination::Multicast),
build_slaac_ra_packet(
remote_mac.to_ipv6_link_local().addr().get(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
SUBNET.network(),
SUBNET.prefix(),
u32::try_from(TWO_HOURS.get().as_secs()).unwrap(),
u32::try_from(TWO_HOURS.get().as_secs()).unwrap(),
),
);
let stable_addr_sub =
calculate_addr_sub(SUBNET, local_mac.to_eui64_with_magic(Mac::DEFAULT_EUI_MAGIC));
let addrs = with_assigned_ipv6_addr_subnets(&mut ctx.core_ctx(), &device_id, |addrs| {
addrs.filter(|a| !a.addr().is_link_local()).collect::<Vec<_>>()
});
let (stable_addr_sub, temp_addr_sub) = assert_matches!(
addrs[..],
[a1, a2] => {
let a1 = a1.to_unicast().add_witness::<NonMappedAddr<_>>().unwrap();
let a2 = a2.to_unicast().add_witness::<NonMappedAddr<_>>().unwrap();
assert_eq!(a1.subnet(), SUBNET);
assert_eq!(a2.subnet(), SUBNET);
assert_ne!(a1, a2);
if a1 == stable_addr_sub {
(a1, a2)
} else {
(a2, a1)
}
}
);
let now = ctx.bindings_ctx.now();
let stable_addr_lifetime_until = now + TWO_HOURS.get();
let temp_addr_lifetime_until = now + ONE_HOUR.get();
// Account for the desync factor:
//
// Per RFC 8981 Section 3.8:
// MAX_DESYNC_FACTOR
// 0.4 * TEMP_PREFERRED_LIFETIME. Upper bound on DESYNC_FACTOR.
//
// | Rationale: Setting MAX_DESYNC_FACTOR to 0.4
// | TEMP_PREFERRED_LIFETIME results in addresses that have
// | statistically different lifetimes, and a maximum of three
// | concurrent temporary addresses when the default values
// | specified in this section are employed.
// DESYNC_FACTOR
// A random value within the range 0 - MAX_DESYNC_FACTOR. It
// is computed each time a temporary address is generated, and
// is associated with the corresponding address. It MUST be
// smaller than (TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE).
let temp_addr_preferred_until_end = now + ONE_HOUR.get();
let temp_addr_preferred_until_start =
temp_addr_preferred_until_end - ((ONE_HOUR.get() * 3) / 5);
let timers =
super::testutil::collect_slaac_timers_integration(&mut ctx.core_ctx(), &device_id);
assert_eq!(
timers.get(&InnerSlaacTimerId::InvalidateSlaacAddress { addr: stable_addr_sub.addr() }),
Some(&stable_addr_lifetime_until)
);
assert_eq!(
timers.get(&InnerSlaacTimerId::DeprecateSlaacAddress { addr: stable_addr_sub.addr() }),
Some(&stable_addr_lifetime_until)
);
assert_eq!(
timers.get(&InnerSlaacTimerId::InvalidateSlaacAddress { addr: temp_addr_sub.addr() }),
Some(&temp_addr_lifetime_until)
);
assert!(timers
.get(&InnerSlaacTimerId::DeprecateSlaacAddress { addr: temp_addr_sub.addr() })
.is_some_and(|time| {
(temp_addr_preferred_until_start..temp_addr_preferred_until_end).contains(time)
}));
assert!(timers
.get(&InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet: temp_addr_sub })
.is_some_and(|time| {
(temp_addr_preferred_until_start - MIN_REGEN_ADVANCE.get()
..temp_addr_preferred_until_end - MIN_REGEN_ADVANCE.get())
.contains(time)
}));
// Disabling IP should remove all the SLAAC addresses.
set_ip_enabled(&mut ctx, false /* enabled */);
let addrs = with_assigned_ipv6_addr_subnets(&mut ctx.core_ctx(), &device_id, |addrs| {
addrs.filter(|a| !a.addr().is_link_local()).collect::<Vec<_>>()
});
assert_matches!(addrs[..], []);
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
}
}