| // 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::{boxed::Box, vec::Vec}; |
| use core::{ |
| convert::TryFrom, |
| num::{NonZeroU64, NonZeroU8}, |
| time::Duration, |
| }; |
| |
| use log::{debug, error, trace}; |
| use net_types::{ |
| ip::{AddrSubnet, IpAddress, Ipv6, Ipv6Addr, Subnet}, |
| UnicastAddr, Witness as _, |
| }; |
| use packet_formats::{icmp::ndp::NonZeroNdpLifetime, utils::NonZeroDuration}; |
| use rand::{distributions::Uniform, Rng as _, RngCore}; |
| |
| use crate::{ |
| algorithm::{ |
| generate_opaque_interface_identifier, OpaqueIidNonce, STABLE_IID_SECRET_KEY_BYTES, |
| }, |
| context::{CounterContext, InstantContext, RngContext, TimerContext, TimerHandler}, |
| error::ExistsError, |
| ip::{ |
| device::state::{DelIpv6AddrReason, Lifetime, SlaacConfig, TemporarySlaacConfig}, |
| IpDeviceIdContext, |
| }, |
| Instant, |
| }; |
| |
| /// 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; |
| |
| // Host constants. |
| |
| #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] |
| enum InnerSlaacTimerId { |
| /// Timer to deprecate an address configured via SLAAC. |
| DeprecateSlaacAddress { addr: UnicastAddr<Ipv6Addr> }, |
| /// Timer to invalidate an address configured via SLAAC. |
| InvalidateSlaacAddress { addr: UnicastAddr<Ipv6Addr> }, |
| /// Timer to generate a new temporary SLAAC address before an existing one |
| /// expires. |
| RegenerateTemporaryAddress { addr_subnet: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>> }, |
| } |
| |
| /// A timer ID for SLAAC. |
| #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] |
| pub(crate) struct SlaacTimerId<DeviceId> { |
| device_id: DeviceId, |
| inner: InnerSlaacTimerId, |
| } |
| |
| impl<DeviceId> SlaacTimerId<DeviceId> { |
| pub(crate) fn new_deprecate_slaac_address( |
| device_id: DeviceId, |
| addr: UnicastAddr<Ipv6Addr>, |
| ) -> SlaacTimerId<DeviceId> { |
| SlaacTimerId { device_id, inner: InnerSlaacTimerId::DeprecateSlaacAddress { addr } } |
| } |
| |
| pub(crate) fn new_invalidate_slaac_address( |
| device_id: DeviceId, |
| addr: UnicastAddr<Ipv6Addr>, |
| ) -> SlaacTimerId<DeviceId> { |
| SlaacTimerId { device_id, inner: InnerSlaacTimerId::InvalidateSlaacAddress { addr } } |
| } |
| |
| pub(crate) fn new_regenerate_temporary_slaac_address( |
| device_id: DeviceId, |
| addr_subnet: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| ) -> SlaacTimerId<DeviceId> { |
| SlaacTimerId { |
| device_id, |
| inner: InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet }, |
| } |
| } |
| } |
| |
| /// The state associated with a SLAAC address. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub(super) struct SlaacAddressEntry<Instant> { |
| pub(super) addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| pub(super) config: SlaacConfig<Instant>, |
| pub(super) deprecated: bool, |
| } |
| |
| /// A mutable view into state associated with a SLAAC address's mutable state. |
| pub(super) struct SlaacAddressEntryMut<'a, Instant> { |
| pub(super) addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| pub(super) config: &'a mut SlaacConfig<Instant>, |
| pub(super) deprecated: &'a mut bool, |
| } |
| |
| /// The state context provided to SLAAC. |
| pub(super) trait SlaacStateContext<C: SlaacNonSyncContext<Self::DeviceId>>: |
| IpDeviceIdContext<Ipv6> |
| { |
| /// Gets the configuration for SLAAC. |
| fn get_config(&self, device_id: Self::DeviceId) -> SlaacConfiguration; |
| |
| /// Returns the number of DAD messages transmitted while performing DAD. |
| /// |
| /// `None` indicates that DAD is disabled. |
| fn dad_transmits(&self, device_id: Self::DeviceId) -> Option<NonZeroU8>; |
| |
| /// Returns the configured NDP retransmission interval for the device. |
| fn retrans_timer(&self, device_id: Self::DeviceId) -> Duration; |
| |
| /// Get the interface identifier for a device as defined by RFC 4291 section 2.5.1. |
| fn get_interface_identifier(&self, device_id: Self::DeviceId) -> [u8; 8]; |
| |
| /// Returns an iterator over the SLAAC addresses on the device. |
| fn iter_slaac_addrs( |
| &self, |
| device_id: Self::DeviceId, |
| ) -> Box<dyn Iterator<Item = SlaacAddressEntry<C::Instant>> + '_>; |
| |
| /// Returns an iterator providing a mutable view of mutable SLAAC address |
| /// state. |
| fn iter_slaac_addrs_mut( |
| &mut self, |
| device_id: Self::DeviceId, |
| ) -> Box<dyn Iterator<Item = SlaacAddressEntryMut<'_, C::Instant>> + '_>; |
| |
| /// Adds a new IPv6 Global Address configured via SLAAC. |
| fn add_slaac_addr_sub( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| slaac_config: SlaacConfig<C::Instant>, |
| ) -> Result<(), ExistsError>; |
| |
| /// Removes a SLAAC address. |
| /// |
| /// # Panics |
| /// |
| /// May panic if `addr` is not an address configured via SLAAC on |
| /// `device_id`. |
| fn remove_slaac_addr( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr: &UnicastAddr<Ipv6Addr>, |
| ); |
| } |
| |
| /// 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 non-synchronized execution context for SLAAC. |
| pub(super) trait SlaacNonSyncContext<DeviceId>: |
| RngContext + TimerContext<SlaacTimerId<DeviceId>> + CounterContext |
| { |
| } |
| impl<DeviceId, C: RngContext + TimerContext<SlaacTimerId<DeviceId>> + CounterContext> |
| SlaacNonSyncContext<DeviceId> for C |
| { |
| } |
| |
| /// The execution context for SLAAC. |
| trait SlaacContext<C: SlaacNonSyncContext<Self::DeviceId>>: SlaacStateContext<C> {} |
| |
| impl<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacStateContext<C>> SlaacContext<C> for SC {} |
| |
| /// An implementation of SLAAC. |
| pub(crate) trait SlaacHandler<C: InstantContext>: IpDeviceIdContext<Ipv6> { |
| /// 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, |
| ctx: &mut C, |
| 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, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| state: SlaacConfig<C::Instant>, |
| reason: DelIpv6AddrReason, |
| ); |
| |
| /// Removes all SLAAC addresses assigned to the device. |
| fn remove_all_slaac_addresses(&mut self, ctx: &mut C, device_id: Self::DeviceId); |
| } |
| |
| impl<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacContext<C>> SlaacHandler<C> for SC { |
| fn apply_slaac_update( |
| &mut self, |
| ctx: &mut C, |
| 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 = ctx.now(); |
| let config = self.get_config(device_id); |
| let dad_transmits = self.dad_transmits(device_id); |
| let retrans_timer = self.retrans_timer(device_id); |
| |
| // Apply the update to each existing address, static or temporary, for the |
| // prefix. |
| for entry in self.iter_slaac_addrs_mut(device_id).filter(|a| a.addr_sub.subnet() == subnet) |
| { |
| let addr_sub = entry.addr_sub; |
| let addr = addr_sub.addr(); |
| let slaac_config = &entry.config; |
| 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 |
| ); |
| |
| // 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, |
| } |
| |
| /// 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, NonZeroU8::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 !*entry.deprecated { |
| *entry.deprecated = true; |
| let _: Option<C::Instant> = ctx.cancel_timer( |
| SlaacTimerId::new_deprecate_slaac_address(device_id, addr), |
| ); |
| let _: Option<C::Instant> = |
| ctx.cancel_timer(SlaacTimerId::new_regenerate_temporary_slaac_address( |
| device_id, addr_sub, |
| )); |
| } |
| } |
| Some((preferred_for, regen_at)) => { |
| if *entry.deprecated { |
| *entry.deprecated = false; |
| } |
| |
| let timer_id = |
| SlaacTimerId::new_deprecate_slaac_address(device_id, addr).into(); |
| let _previously_scheduled_instant: Option<C::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. |
| ctx.schedule_timer_instant( |
| now.checked_add(preferred_for.get()).unwrap(), |
| timer_id, |
| ) |
| } |
| NonZeroNdpLifetime::Infinite => ctx.cancel_timer(timer_id), |
| }; |
| |
| let _prev_regen_at: Option<C::Instant> = match regen_at { |
| Some(regen_at) => ctx.schedule_timer_instant( |
| regen_at, |
| SlaacTimerId::new_regenerate_temporary_slaac_address( |
| device_id, addr_sub, |
| ), |
| ), |
| None => { |
| ctx.cancel_timer(SlaacTimerId::new_regenerate_temporary_slaac_address( |
| device_id, addr_sub, |
| )) |
| } |
| }; |
| } |
| } |
| |
| // 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(entry.config, Lifetime::Finite(valid_until)); |
| |
| let _: Option<C::Instant> = ctx.schedule_timer_instant( |
| valid_until, |
| SlaacTimerId::new_invalidate_slaac_address(device_id, addr).into(), |
| ); |
| } |
| NonZeroNdpLifetime::Infinite => { |
| // Set the valid lifetime for this address. |
| update_slaac_addr_valid_until(entry.config, Lifetime::Infinite); |
| |
| let _: Option<C::Instant> = ctx.cancel_timer( |
| SlaacTimerId::new_invalidate_slaac_address(device_id, addr).into(), |
| ); |
| } |
| }, |
| 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()); |
| } |
| } |
| } |
| |
| // 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 |
| }) |
| .into_iter(), |
| ); |
| |
| for slaac_type in address_types_to_add { |
| add_slaac_addr_sub( |
| self, |
| ctx, |
| device_id, |
| now, |
| SlaacInitConfig::new(slaac_type), |
| valid_lifetime, |
| preferred_lifetime, |
| &subnet, |
| ); |
| } |
| } |
| |
| fn on_address_removed( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| state: SlaacConfig<C::Instant>, |
| reason: DelIpv6AddrReason, |
| ) { |
| let preferred_until = |
| ctx.cancel_timer(SlaacTimerId::new_deprecate_slaac_address(device_id, addr_sub.addr())); |
| let _valid_until: Option<C::Instant> = ctx |
| .cancel_timer(SlaacTimerId::new_invalidate_slaac_address(device_id, addr_sub.addr())); |
| |
| let TemporarySlaacConfig { valid_until, creation_time, desync_factor, dad_counter } = |
| match state { |
| SlaacConfig::Temporary(temporary_config) => { |
| let _regen_at: Option<C::Instant> = ctx.cancel_timer( |
| SlaacTimerId::new_regenerate_temporary_slaac_address(device_id, addr_sub), |
| ); |
| temporary_config |
| } |
| SlaacConfig::Static { .. } => return, |
| }; |
| |
| match reason { |
| DelIpv6AddrReason::ManualAction => return, |
| DelIpv6AddrReason::DadFailed => { |
| // Attempt to regenerate the address. |
| } |
| } |
| |
| let SlaacConfiguration { enable_stable_addresses: _, temporary_address_configuration } = |
| self.get_config(device_id); |
| 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 = 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( |
| self, |
| 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(), |
| ); |
| } |
| |
| fn remove_all_slaac_addresses(&mut self, ctx: &mut C, device_id: Self::DeviceId) { |
| self.iter_slaac_addrs(device_id) |
| .map(|a| a.addr_sub.addr()) |
| .collect::<Vec<_>>() |
| .into_iter() |
| .for_each(|addr| { |
| self.remove_slaac_addr(ctx, device_id, &addr); |
| }) |
| } |
| } |
| |
| impl<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacContext<C>> |
| TimerHandler<C, SlaacTimerId<SC::DeviceId>> for SC |
| { |
| fn handle_timer( |
| &mut self, |
| ctx: &mut C, |
| SlaacTimerId { device_id, inner }: SlaacTimerId<SC::DeviceId>, |
| ) { |
| match inner { |
| InnerSlaacTimerId::DeprecateSlaacAddress { addr } => { |
| set_deprecated_slaac_addr(self, device_id, &addr, true) |
| } |
| InnerSlaacTimerId::InvalidateSlaacAddress { addr } => { |
| self.remove_slaac_addr(ctx, device_id, &addr); |
| } |
| InnerSlaacTimerId::RegenerateTemporaryAddress { addr_subnet } => { |
| regenerate_temporary_slaac_addr(self, ctx, 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], |
| } |
| |
| /// 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, |
| } |
| } |
| } |
| |
| fn set_deprecated_slaac_addr<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacContext<C>>( |
| sync_ctx: &mut SC, |
| device_id: SC::DeviceId, |
| addr: &UnicastAddr<Ipv6Addr>, |
| deprecated: bool, |
| ) { |
| let entry = |
| sync_ctx.iter_slaac_addrs_mut(device_id).find(|a| &a.addr_sub.addr() == addr).unwrap(); |
| *entry.deprecated = deprecated; |
| } |
| |
| /// 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 = |
| NonZeroDuration::from_nonzero_secs(const_unwrap::const_unwrap_option(NonZeroU64::new(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: u8, |
| ) -> 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<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacContext<C>>( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| addr_subnet: &AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| ) { |
| let entry = { |
| let mut subnet_addrs = sync_ctx |
| .iter_slaac_addrs(device_id) |
| .filter(|entry| entry.addr_sub.subnet() == addr_subnet.subnet() && !entry.deprecated); |
| |
| // 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 let Some((entry, regen_at)) = subnet_addrs.find_map(|entry| { |
| ctx.scheduled_instant(SlaacTimerId::new_regenerate_temporary_slaac_address( |
| device_id, |
| entry.addr_sub, |
| )) |
| .map(|instant| (entry, instant)) |
| }) { |
| debug!( |
| "regenerate_temporary_addr: ignoring regen event at {:?} for {:?} since {:?} will regenerate after at {:?}", |
| ctx.now(), addr_subnet, entry.addr_sub.addr(), regen_at); |
| return; |
| } |
| match sync_ctx.iter_slaac_addrs(device_id).find(|entry| &entry.addr_sub == addr_subnet) { |
| Some(entry) => entry, |
| None => unreachable!("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 } = |
| sync_ctx.get_config(device_id); |
| let TemporarySlaacAddressConfiguration { |
| temp_valid_lifetime, |
| temp_preferred_lifetime: _, |
| temp_idgen_retries: _, |
| secret_key: _, |
| } = match temporary_address_configuration { |
| Some(configuration) => configuration, |
| None => return, |
| }; |
| |
| let deprecate_at = ctx |
| .scheduled_instant(SlaacTimerId::new_deprecate_slaac_address(device_id, 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; |
| |
| let now = 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( |
| sync_ctx, |
| ctx, |
| device_id, |
| now, |
| SlaacInitConfig::Temporary { dad_count: 0 }, |
| NonZeroNdpLifetime::Finite(valid_for), |
| NonZeroDuration::new(preferred_for).map(NonZeroNdpLifetime::Finite), |
| &addr_subnet.subnet(), |
| ); |
| } |
| |
| #[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, UnicastAddr<Ipv6Addr>> { |
| 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, UnicastAddr<Ipv6Addr>> { |
| 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<C: SlaacNonSyncContext<SC::DeviceId>, SC: SlaacContext<C>>( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| now: C::Instant, |
| slaac_config: SlaacInitConfig, |
| prefix_valid_for: NonZeroNdpLifetime, |
| prefix_preferred_for: Option<NonZeroNdpLifetime>, |
| subnet: &Subnet<Ipv6Addr>, |
| ) { |
| 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 } = |
| sync_ctx.get_config(device_id); |
| |
| 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/95946): Support regenerating address. |
| either::Either::Left(core::iter::once(generate_global_static_address( |
| &subnet, |
| &sync_ctx.get_interface_identifier(device_id)[..], |
| ))), |
| ) |
| } |
| 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 = ctx.rng_mut().next_u64(); |
| let dad_transmits = sync_ctx.dad_transmits(device_id); |
| |
| // 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, |
| sync_ctx.retrans_timer(device_id), |
| dad_transmits.map_or(0, NonZeroU8::get), |
| ); |
| |
| let iid = sync_ctx.get_interface_identifier(device_id); |
| 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( |
| ctx.rng_mut(), |
| 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. |
| let address = 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/91301): Should bindings be the one to actually |
| // assign the address to maintain a "single source of truth"? |
| match sync_ctx.add_slaac_addr_sub(ctx, device_id, address, slaac_config) { |
| Err(ExistsError) => { |
| trace!("IPv6 SLAAC address {:?} already exists on device {:?}", address, device_id); |
| |
| // Try the next address. |
| // |
| // TODO(https://fxbug.dev/100003): Limit number of attempts. |
| ctx.increment_counter("generated_slaac_addr_exists"); |
| } |
| Ok(()) => break address, |
| } |
| }; |
| |
| trace!("receive_ndp_packet: Successfully configured new IPv6 address {:?} on device {:?} via SLAAC", address, device_id); |
| |
| // 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!( |
| ctx.schedule_timer_instant( |
| valid_until, |
| SlaacTimerId::new_invalidate_slaac_address(device_id, address.addr()).into(), |
| ), |
| None |
| ); |
| } |
| Lifetime::Infinite => {} |
| } |
| |
| let deprecate_timer_id = SlaacTimerId::new_deprecate_slaac_address(device_id, address.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!( |
| ctx.schedule_timer_instant( |
| now.checked_add(preferred_for.get()).unwrap(), |
| deprecate_timer_id.into() |
| ), |
| None |
| ); |
| } |
| NonZeroNdpLifetime::Infinite => {} |
| } |
| |
| match regen_at { |
| Some(regen_at) => assert_eq!( |
| ctx.schedule_timer_instant( |
| regen_at, |
| SlaacTimerId::new_regenerate_temporary_slaac_address(device_id, address) |
| .into() |
| ), |
| None |
| ), |
| None => (), |
| } |
| } |
| None => { |
| set_deprecated_slaac_addr(sync_ctx, device_id, &address.addr(), true); |
| assert_eq!(ctx.cancel_timer(deprecate_timer_id.into()), None); |
| } |
| }; |
| } |
| |
| #[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::Ip as _, LinkLocalAddress as _}; |
| 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::{ |
| DummyCtx, DummyInstant, DummyInstantRange as _v, DummyNonSyncCtx, DummySyncCtx, |
| DummyTimerCtxExt as _, |
| }, |
| device::FrameDestination, |
| ip::{ |
| device::{ |
| get_assigned_ipv6_addr_subnets, integration::REQUIRED_NDP_IP_PACKET_HOP_LIMIT, |
| }, |
| receive_ipv6_packet, DummyDeviceId, |
| }, |
| testutil::{assert_empty, DummyEventDispatcherConfig, FakeCryptoRng, TestIpExt as _}, |
| Ctx, |
| }; |
| |
| struct MockSlaacContext { |
| config: SlaacConfiguration, |
| dad_transmits: Option<NonZeroU8>, |
| retrans_timer: Duration, |
| iid: [u8; 8], |
| slaac_addrs: Vec<SlaacAddressEntry<DummyInstant>>, |
| non_slaac_addr: Option<UnicastAddr<Ipv6Addr>>, |
| } |
| |
| type MockCtx = DummySyncCtx<MockSlaacContext, (), DummyDeviceId>; |
| type MockNonSyncCtx = DummyNonSyncCtx<SlaacTimerId<DummyDeviceId>, (), ()>; |
| |
| impl SlaacStateContext<MockNonSyncCtx> for MockCtx { |
| fn get_config(&self, DummyDeviceId: Self::DeviceId) -> SlaacConfiguration { |
| let MockSlaacContext { |
| config, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs: _, |
| non_slaac_addr: _, |
| } = self.get_ref(); |
| *config |
| } |
| |
| fn dad_transmits(&self, DummyDeviceId: Self::DeviceId) -> Option<NonZeroU8> { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs: _, |
| non_slaac_addr: _, |
| } = self.get_ref(); |
| *dad_transmits |
| } |
| |
| fn retrans_timer(&self, DummyDeviceId: Self::DeviceId) -> Duration { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer, |
| iid: _, |
| slaac_addrs: _, |
| non_slaac_addr: _, |
| } = self.get_ref(); |
| *retrans_timer |
| } |
| |
| fn get_interface_identifier(&self, DummyDeviceId: Self::DeviceId) -> [u8; 8] { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid, |
| slaac_addrs: _, |
| non_slaac_addr: _, |
| } = self.get_ref(); |
| *iid |
| } |
| |
| fn iter_slaac_addrs( |
| &self, |
| DummyDeviceId: Self::DeviceId, |
| ) -> Box<dyn Iterator<Item = SlaacAddressEntry<DummyInstant>> + '_> { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs, |
| non_slaac_addr: _, |
| } = self.get_ref(); |
| Box::new(slaac_addrs.iter().cloned()) |
| } |
| |
| fn iter_slaac_addrs_mut( |
| &mut self, |
| DummyDeviceId: Self::DeviceId, |
| ) -> Box<dyn Iterator<Item = SlaacAddressEntryMut<'_, DummyInstant>> + '_> { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs, |
| non_slaac_addr: _, |
| } = self.get_mut(); |
| Box::new(slaac_addrs.iter_mut().map( |
| |SlaacAddressEntry { addr_sub, config, deprecated }| SlaacAddressEntryMut { |
| addr_sub: *addr_sub, |
| config, |
| deprecated, |
| }, |
| )) |
| } |
| |
| fn add_slaac_addr_sub( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx, |
| DummyDeviceId: Self::DeviceId, |
| addr_sub: AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>>, |
| config: SlaacConfig<DummyInstant>, |
| ) -> Result<(), ExistsError> { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs, |
| non_slaac_addr, |
| } = self.get_mut(); |
| |
| if non_slaac_addr.map_or(false, |a| a == addr_sub.addr()) { |
| return Err(ExistsError); |
| } |
| |
| if slaac_addrs.iter().any(|e| e.addr_sub.addr() == addr_sub.addr()) { |
| return Err(ExistsError); |
| } |
| |
| slaac_addrs.push(SlaacAddressEntry { addr_sub, config, deprecated: false }); |
| Ok(()) |
| } |
| |
| fn remove_slaac_addr( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx, |
| DummyDeviceId: Self::DeviceId, |
| addr: &UnicastAddr<Ipv6Addr>, |
| ) { |
| let MockSlaacContext { |
| config: _, |
| dad_transmits: _, |
| retrans_timer: _, |
| iid: _, |
| slaac_addrs, |
| non_slaac_addr: _, |
| } = self.get_mut(); |
| |
| slaac_addrs.retain(|e| &e.addr_sub.addr() != addr) |
| } |
| } |
| |
| #[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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: SlaacConfiguration { enable_stable_addresses, ..Default::default() }, |
| dad_transmits: None, |
| retrans_timer: DEFAULT_RETRANS_TIMER, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| |
| SlaacHandler::apply_slaac_update( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| SUBNET, |
| NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs), |
| NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs), |
| ); |
| assert_empty(sync_ctx.iter_slaac_addrs(DummyDeviceId)); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| fn calculate_addr_sub( |
| subnet: Subnet<Ipv6Addr>, |
| iid: [u8; 8], |
| ) -> AddrSubnet<Ipv6Addr, UnicastAddr<Ipv6Addr>> { |
| 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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: SlaacConfiguration { enable_stable_addresses: true, ..Default::default() }, |
| dad_transmits: None, |
| retrans_timer: DEFAULT_RETRANS_TIMER, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| |
| 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 sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| 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 = non_sync_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!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [entry],); |
| let deprecate_timer_id = |
| SlaacTimerId::new_deprecate_slaac_address(DummyDeviceId, addr_sub.addr()); |
| let invalidate_timer_id = |
| SlaacTimerId::new_invalidate_slaac_address(DummyDeviceId, addr_sub.addr()); |
| if address_created_deprecated { |
| non_sync_ctx.timer_ctx().assert_timers_installed([(invalidate_timer_id, valid_until)]); |
| } else { |
| non_sync_ctx.timer_ctx().assert_timers_installed([ |
| (deprecate_timer_id, now + Duration::from_secs(preferred_lifetime_secs.into())), |
| (invalidate_timer_id, valid_until), |
| ]); |
| |
| // Trigger the deprecation timer. |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(&mut sync_ctx, TimerHandler::handle_timer), |
| Some(deprecate_timer_id) |
| ); |
| let entry = SlaacAddressEntry { deprecated: true, ..entry }; |
| assert_eq!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [entry],); |
| non_sync_ctx.timer_ctx().assert_timers_installed([(invalidate_timer_id, valid_until)]); |
| } |
| |
| // Trigger the invalidation timer. |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(&mut sync_ctx, TimerHandler::handle_timer), |
| Some(invalidate_timer_id) |
| ); |
| assert_empty(sync_ctx.iter_slaac_addrs(DummyDeviceId)); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| #[test] |
| fn stable_address_conflict() { |
| let addr_sub = calculate_addr_sub(SUBNET, IID); |
| |
| let DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: SlaacConfiguration { enable_stable_addresses: true, ..Default::default() }, |
| dad_transmits: None, |
| retrans_timer: DEFAULT_RETRANS_TIMER, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| // Consider the address we will generate as already assigned without |
| // SLAAC. |
| non_slaac_addr: Some(addr_sub.addr()), |
| })); |
| |
| const LIFETIME_SECS: u32 = 1; |
| |
| // Generate a new SLAAC address. |
| SlaacHandler::apply_slaac_update( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| SUBNET, |
| NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS), |
| NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS), |
| ); |
| assert_empty(sync_ctx.iter_slaac_addrs(DummyDeviceId)); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| #[test_case(DelIpv6AddrReason::ManualAction; "manual action")] |
| #[test_case(DelIpv6AddrReason::DadFailed; "dad failed")] |
| fn remove_stable_address(reason: DelIpv6AddrReason) { |
| let addr_sub = calculate_addr_sub(SUBNET, IID); |
| |
| let DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: SlaacConfiguration { enable_stable_addresses: true, ..Default::default() }, |
| dad_transmits: None, |
| retrans_timer: DEFAULT_RETRANS_TIMER, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| |
| const LIFETIME_SECS: u32 = 1; |
| |
| // Generate a new SLAAC address. |
| SlaacHandler::apply_slaac_update( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| SUBNET, |
| NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS), |
| NonZeroNdpLifetime::from_u32_with_infinite(LIFETIME_SECS), |
| ); |
| let now = non_sync_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!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [entry],); |
| let deprecate_timer_id = |
| SlaacTimerId::new_deprecate_slaac_address(DummyDeviceId, addr_sub.addr()); |
| let invalidate_timer_id = |
| SlaacTimerId::new_invalidate_slaac_address(DummyDeviceId, addr_sub.addr()); |
| non_sync_ctx.timer_ctx().assert_timers_installed([ |
| (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 } = |
| sync_ctx.get_mut().slaac_addrs.remove(0); |
| assert_eq!(addr_sub, got_addr_sub); |
| assert!(!deprecated); |
| config |
| }; |
| SlaacHandler::on_address_removed( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| addr_sub, |
| config, |
| reason, |
| ); |
| non_sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: SlaacConfiguration { enable_stable_addresses: true, ..Default::default() }, |
| dad_transmits: None, |
| retrans_timer: DEFAULT_RETRANS_TIMER, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| |
| let addr_sub = calculate_addr_sub(SUBNET, IID); |
| let deprecate_timer_id = |
| SlaacTimerId::new_deprecate_slaac_address(DummyDeviceId, addr_sub.addr()); |
| let invalidate_timer_id = |
| SlaacTimerId::new_invalidate_slaac_address(DummyDeviceId, 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 sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| SUBNET, |
| ndp_pl, |
| ndp_vl, |
| ); |
| let address_created_deprecated = ndp_pl.is_none(); |
| let now = non_sync_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!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [entry],); |
| non_sync_ctx.timer_ctx().assert_timers_installed(expected_timers); |
| |
| // Refresh timers. |
| let ndp_pl = NonZeroNdpLifetime::from_u32_with_infinite(new_pl_secs); |
| SlaacHandler::apply_slaac_update( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| 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!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [entry],); |
| non_sync_ctx.timer_ctx().assert_timers_installed(expected_timers); |
| } |
| |
| const SECRET_KEY: [u8; STABLE_IID_SECRET_KEY_BYTES] = [1; STABLE_IID_SECRET_KEY_BYTES]; |
| |
| const ONE_HOUR: NonZeroDuration = NonZeroDuration::from_nonzero_secs( |
| const_unwrap::const_unwrap_option(NonZeroU64::new(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: u8, |
| retrans_timer: Duration, |
| enable: bool, |
| } |
| |
| impl DontGenerateTemporaryAddressTest { |
| fn with_pl_less_than_regen_advance( |
| dad_transmits: u8, |
| 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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: 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() |
| }, |
| dad_transmits: NonZeroU8::new(dad_transmits), |
| retrans_timer, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| |
| SlaacHandler::apply_slaac_update( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| SUBNET, |
| NonZeroNdpLifetime::from_u32_with_infinite(preferred_lifetime_secs), |
| NonZeroNdpLifetime::from_u32_with_infinite(valid_lifetime_secs), |
| ); |
| assert_empty(sync_ctx.iter_slaac_addrs(DummyDeviceId)); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| struct GenerateTemporaryAddressTest { |
| pl_config: u32, |
| vl_config: u32, |
| dad_transmits: u8, |
| 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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::with_state(MockSlaacContext { |
| config: 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() |
| }, |
| dad_transmits: NonZeroU8::new(dad_transmits), |
| retrans_timer, |
| iid: IID, |
| slaac_addrs: Default::default(), |
| non_slaac_addr: None, |
| })); |
| let sync_ctx = &mut sync_ctx; |
| |
| let mut dup_rng = non_sync_ctx.rng().clone(); |
| |
| struct AddrProps { |
| desync_factor: Duration, |
| valid_until: DummyInstant, |
| preferred_until: DummyInstant, |
| entry: SlaacAddressEntry<DummyInstant>, |
| deprecate_timer_id: SlaacTimerId<DummyDeviceId>, |
| invalidate_timer_id: SlaacTimerId<DummyDeviceId>, |
| regenerate_timer_id: SlaacTimerId<DummyDeviceId>, |
| } |
| |
| 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: SlaacTimerId::new_deprecate_slaac_address( |
| DummyDeviceId, |
| addr_sub.addr(), |
| ), |
| invalidate_timer_id: SlaacTimerId::new_invalidate_slaac_address( |
| DummyDeviceId, |
| addr_sub.addr(), |
| ), |
| regenerate_timer_id: SlaacTimerId::new_regenerate_temporary_slaac_address( |
| DummyDeviceId, |
| addr_sub, |
| ), |
| } |
| }; |
| |
| // Generate the first temporary SLAAC address. |
| SlaacHandler::apply_slaac_update( |
| sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| 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, non_sync_ctx.now(), Duration::ZERO); |
| assert_eq!(sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), [first_entry]); |
| non_sync_ctx.timer_ctx().assert_timers_installed([ |
| (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!( |
| non_sync_ctx.trigger_next_timer(sync_ctx, TimerHandler::handle_timer), |
| Some(first_regenerate_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, non_sync_ctx.now(), first_desync_factor); |
| assert_eq!( |
| sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), |
| [first_entry, second_entry] |
| ); |
| let second_regen_at = second_preferred_until - regen_advance.get(); |
| non_sync_ctx.timer_ctx().assert_timers_installed([ |
| (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!( |
| non_sync_ctx.trigger_next_timer(sync_ctx, TimerHandler::handle_timer), |
| Some(first_deprecate_timer_id), |
| ); |
| let first_entry = SlaacAddressEntry { deprecated: true, ..first_entry }; |
| assert_eq!( |
| sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), |
| [first_entry, second_entry] |
| ); |
| non_sync_ctx.timer_ctx().assert_timers_installed([ |
| (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; |
| |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(sync_ctx, TimerHandler::handle_timer), |
| Some(timer_id), |
| ); |
| |
| if timer_id == second_regenerate_timer_id { |
| assert_eq!(third_created_at, None); |
| third_created_at = Some(non_sync_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!( |
| sync_ctx.iter_slaac_addrs(DummyDeviceId).collect::<Vec<_>>(), |
| [second_entry, third_entry] |
| ); |
| non_sync_ctx.timer_ctx().assert_some_timers_installed([ |
| (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, &[u8], _>::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 DummyEventDispatcherConfig { |
| local_mac, |
| remote_mac, |
| local_ip: _, |
| remote_ip: _, |
| subnet: _, |
| } = Ipv6::DUMMY_CONFIG; |
| |
| const ONE_HOUR: NonZeroDuration = NonZeroDuration::from_nonzero_secs( |
| const_unwrap::const_unwrap_option(NonZeroU64::new(ONE_HOUR_AS_SECS as u64)), |
| ); |
| const TWO_HOURS: NonZeroDuration = NonZeroDuration::from_nonzero_secs( |
| const_unwrap::const_unwrap_option(NonZeroU64::new(TWO_HOURS_AS_SECS as u64)), |
| ); |
| |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = crate::testutil::DummyCtx::default(); |
| let device_id = |
| sync_ctx.state.device.add_ethernet_device(local_mac, Ipv6::MINIMUM_LINK_MTU.into()); |
| crate::ip::device::update_ipv6_configuration( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device_id, |
| |config| { |
| config.slaac_config = 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, |
| }), |
| }; |
| }, |
| ); |
| |
| let set_ip_enabled = |sync_ctx: &mut crate::testutil::DummySyncCtx, |
| non_sync_ctx: &mut crate::testutil::DummyNonSyncCtx, |
| enabled| { |
| crate::ip::device::update_ipv6_configuration( |
| sync_ctx, |
| non_sync_ctx, |
| device_id, |
| |config| { |
| config.ip_config.ip_enabled = enabled; |
| }, |
| ) |
| }; |
| set_ip_enabled(&mut sync_ctx, &mut non_sync_ctx, true /* enabled */); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| |
| // Generate stable and temporary SLAAC addresses. |
| receive_ipv6_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device_id, |
| 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 = get_assigned_ipv6_addr_subnets(&sync_ctx, device_id) |
| .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(); |
| let a2 = a2.to_unicast(); |
| |
| 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 = non_sync_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); |
| non_sync_ctx.timer_ctx().assert_some_timers_installed([ |
| ( |
| SlaacTimerId::new_invalidate_slaac_address(device_id, stable_addr_sub.addr()) |
| .into(), |
| stable_addr_lifetime_until.as_dyn(), |
| ), |
| ( |
| SlaacTimerId::new_deprecate_slaac_address(device_id, stable_addr_sub.addr()).into(), |
| stable_addr_lifetime_until.as_dyn(), |
| ), |
| ( |
| SlaacTimerId::new_invalidate_slaac_address(device_id, temp_addr_sub.addr()).into(), |
| temp_addr_lifetime_until.as_dyn(), |
| ), |
| ( |
| SlaacTimerId::new_deprecate_slaac_address(device_id, temp_addr_sub.addr()).into(), |
| (temp_addr_preferred_until_start..temp_addr_preferred_until_end).as_dyn(), |
| ), |
| ( |
| SlaacTimerId::new_regenerate_temporary_slaac_address(device_id, temp_addr_sub) |
| .into(), |
| (temp_addr_preferred_until_start - MIN_REGEN_ADVANCE.get() |
| ..temp_addr_preferred_until_end - MIN_REGEN_ADVANCE.get()) |
| .as_dyn(), |
| ), |
| ]); |
| |
| // Disabling IP should remove all the SLAAC addresses. |
| set_ip_enabled(&mut sync_ctx, &mut non_sync_ctx, false /* enabled */); |
| let addrs = get_assigned_ipv6_addr_subnets(&sync_ctx, device_id) |
| .filter(|a| !a.addr().is_link_local()) |
| .collect::<Vec<_>>(); |
| assert_matches!(addrs[..], []); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| } |