blob: f526ea9552b091613b258804eedd6aa62c48e544 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! An IP device.
pub(crate) mod api;
pub(crate) mod config;
pub(crate) mod dad;
pub(crate) mod integration;
pub(crate) mod nud;
pub(crate) mod route_discovery;
pub(crate) mod router_solicitation;
pub(crate) mod slaac;
pub(crate) mod state;
use alloc::{boxed::Box, vec::Vec};
use core::{
fmt::{Debug, Display},
hash::Hash,
num::NonZeroU8,
};
use derivative::Derivative;
use net_types::{
ip::{
AddrSubnet, GenericOverIp, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6SourceAddr,
Mtu, Subnet,
},
MulticastAddr, NonMappedAddr, SpecifiedAddr, UnicastAddr, Witness,
};
use packet::{BufferMut, Serializer};
use packet_formats::{
icmp::{mld::MldPacket, ndp::NonZeroNdpLifetime},
utils::NonZeroDuration,
};
use tracing::info;
use zerocopy::ByteSlice;
use crate::{
context::{
DeferredResourceRemovalContext, EventContext, HandleableTimer, InstantBindingsTypes,
InstantContext, RngContext, TimerContext, TimerHandler,
},
device::{self, AnyDevice, DeviceIdContext},
error::{ExistsError, NotFoundError},
filter::{IpPacket, ProofOfEgressCheck},
inspect::Inspectable,
ip::{
device::{
config::{
IpDeviceConfigurationUpdate, Ipv4DeviceConfigurationUpdate,
Ipv6DeviceConfigurationUpdate,
},
dad::{DadEvent, DadHandler, DadTimerId},
nud::NudIpHandler,
route_discovery::{
Ipv6DiscoveredRoute, Ipv6DiscoveredRouteTimerId, RouteDiscoveryHandler,
},
router_solicitation::{RsHandler, RsTimerId},
slaac::{SlaacHandler, SlaacTimerId},
state::{
IpDeviceConfiguration, IpDeviceFlags, IpDeviceState, IpDeviceStateBindingsTypes,
IpDeviceStateIpExt, Ipv4AddrConfig, Ipv4AddressState, Ipv4DeviceConfiguration,
Ipv4DeviceConfigurationAndFlags, Ipv4DeviceState, Ipv6AddrConfig,
Ipv6AddrManualConfig, Ipv6AddressFlags, Ipv6AddressState, Ipv6DeviceConfiguration,
Ipv6DeviceConfigurationAndFlags, Ipv6DeviceState, Lifetime,
},
},
gmp::{
igmp::{IgmpPacketHandler, IgmpTimerId},
mld::{MldPacketHandler, MldTimerId},
GmpHandler, GmpQueryHandler, GroupJoinResult, GroupLeaveResult,
},
types::IpTypesIpExt,
},
socket::address::SocketIpAddr,
sync::RemoveResourceResultWithContext,
Instant,
};
use self::state::Ipv6NetworkLearnedParameters;
/// An IP device timer.
///
/// This timer is an indirection to the real types defined by the
/// [`IpDeviceIpExt`] trait. Having a concrete type parameterized over IP allows
/// us to provide implementations generic on I for outer timer contexts that
/// handle `IpDeviceTimerId` timers.
#[derive(Derivative, GenericOverIp)]
#[derivative(
Clone(bound = ""),
Eq(bound = ""),
PartialEq(bound = ""),
Hash(bound = ""),
Debug(bound = "")
)]
#[generic_over_ip(I, Ip)]
pub struct IpDeviceTimerId<I: IpDeviceIpExt, D: device::WeakId, A: IpAddressIdSpec>(I::Timer<D, A>);
/// A timer ID for IPv4 devices.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct Ipv4DeviceTimerId<D: device::WeakId>(IgmpTimerId<D>);
impl<D: device::WeakId> Ipv4DeviceTimerId<D> {
/// Gets the device ID from this timer IFF the device hasn't been destroyed.
fn device_id(&self) -> Option<D::Strong> {
let Self(this) = self;
this.device_id().upgrade()
}
}
impl<D: device::WeakId, A: IpAddressIdSpec> From<IpDeviceTimerId<Ipv4, D, A>>
for Ipv4DeviceTimerId<D>
{
fn from(IpDeviceTimerId(inner): IpDeviceTimerId<Ipv4, D, A>) -> Self {
inner
}
}
impl<D: device::WeakId, A: IpAddressIdSpec> From<Ipv4DeviceTimerId<D>>
for IpDeviceTimerId<Ipv4, D, A>
{
fn from(value: Ipv4DeviceTimerId<D>) -> Self {
Self(value)
}
}
impl<D: device::WeakId> From<IgmpTimerId<D>> for Ipv4DeviceTimerId<D> {
fn from(id: IgmpTimerId<D>) -> Ipv4DeviceTimerId<D> {
Ipv4DeviceTimerId(id)
}
}
impl<D: device::WeakId, BC, CC: TimerHandler<BC, IgmpTimerId<D>>> HandleableTimer<CC, BC>
for Ipv4DeviceTimerId<D>
{
fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) {
let Self(id) = self;
core_ctx.handle_timer(bindings_ctx, id);
}
}
impl<I, CC, BC, A> HandleableTimer<CC, BC> for IpDeviceTimerId<I, CC::WeakDeviceId, A>
where
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
A: IpAddressIdSpec,
for<'a> CC::WithIpDeviceConfigurationInnerCtx<'a>:
TimerHandler<BC, I::Timer<CC::WeakDeviceId, A>>,
{
fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) {
let Self(id) = self;
let Some(device_id) = I::timer_device_id(&id) else {
return;
};
core_ctx.with_ip_device_configuration(&device_id, |_state, mut core_ctx| {
TimerHandler::handle_timer(&mut core_ctx, bindings_ctx, id)
})
}
}
/// A timer ID for IPv6 devices.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum Ipv6DeviceTimerId<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> {
Mld(MldTimerId<D>),
Dad(DadTimerId<D, A>),
Rs(RsTimerId<D>),
RouteDiscovery(Ipv6DiscoveredRouteTimerId<D>),
Slaac(SlaacTimerId<D>),
}
impl<D: device::WeakId, A: IpAddressIdSpec> From<IpDeviceTimerId<Ipv6, D, A>>
for Ipv6DeviceTimerId<D, A::WeakV6>
{
fn from(IpDeviceTimerId(inner): IpDeviceTimerId<Ipv6, D, A>) -> Self {
inner
}
}
impl<D: device::WeakId, A: IpAddressIdSpec> From<Ipv6DeviceTimerId<D, A::WeakV6>>
for IpDeviceTimerId<Ipv6, D, A>
{
fn from(value: Ipv6DeviceTimerId<D, A::WeakV6>) -> Self {
Self(value)
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> Ipv6DeviceTimerId<D, A> {
/// Gets the device ID from this timer IFF the device hasn't been destroyed.
fn device_id(&self) -> Option<D::Strong> {
match self {
Self::Mld(id) => id.device_id(),
Self::Dad(id) => id.device_id(),
Self::Rs(id) => id.device_id(),
Self::RouteDiscovery(id) => id.device_id(),
Self::Slaac(id) => id.device_id(),
}
.upgrade()
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> From<MldTimerId<D>>
for Ipv6DeviceTimerId<D, A>
{
fn from(id: MldTimerId<D>) -> Ipv6DeviceTimerId<D, A> {
Ipv6DeviceTimerId::Mld(id)
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> From<DadTimerId<D, A>>
for Ipv6DeviceTimerId<D, A>
{
fn from(id: DadTimerId<D, A>) -> Ipv6DeviceTimerId<D, A> {
Ipv6DeviceTimerId::Dad(id)
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> From<RsTimerId<D>>
for Ipv6DeviceTimerId<D, A>
{
fn from(id: RsTimerId<D>) -> Ipv6DeviceTimerId<D, A> {
Ipv6DeviceTimerId::Rs(id)
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> From<Ipv6DiscoveredRouteTimerId<D>>
for Ipv6DeviceTimerId<D, A>
{
fn from(id: Ipv6DiscoveredRouteTimerId<D>) -> Ipv6DeviceTimerId<D, A> {
Ipv6DeviceTimerId::RouteDiscovery(id)
}
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> From<SlaacTimerId<D>>
for Ipv6DeviceTimerId<D, A>
{
fn from(id: SlaacTimerId<D>) -> Ipv6DeviceTimerId<D, A> {
Ipv6DeviceTimerId::Slaac(id)
}
}
impl<
D: device::WeakId,
A: WeakIpAddressId<Ipv6Addr>,
BC,
CC: TimerHandler<BC, RsTimerId<D>>
+ TimerHandler<BC, Ipv6DiscoveredRouteTimerId<D>>
+ TimerHandler<BC, MldTimerId<D>>
+ TimerHandler<BC, SlaacTimerId<D>>
+ TimerHandler<BC, DadTimerId<D, A>>,
> HandleableTimer<CC, BC> for Ipv6DeviceTimerId<D, A>
{
fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) {
match self {
Ipv6DeviceTimerId::Mld(id) => core_ctx.handle_timer(bindings_ctx, id),
Ipv6DeviceTimerId::Dad(id) => core_ctx.handle_timer(bindings_ctx, id),
Ipv6DeviceTimerId::Rs(id) => core_ctx.handle_timer(bindings_ctx, id),
Ipv6DeviceTimerId::RouteDiscovery(id) => core_ctx.handle_timer(bindings_ctx, id),
Ipv6DeviceTimerId::Slaac(id) => core_ctx.handle_timer(bindings_ctx, id),
}
}
}
/// An extension trait adding IP device properties.
pub trait IpDeviceIpExt: IpDeviceStateIpExt {
type State<BT: IpDeviceStateBindingsTypes>: AsRef<IpDeviceState<Self, BT>>
+ AsMut<IpDeviceState<Self, BT>>;
type Configuration: AsRef<IpDeviceConfiguration> + Clone;
type Timer<D: device::WeakId, A: IpAddressIdSpec>: Into<IpDeviceTimerId<Self, D, A>>
+ From<IpDeviceTimerId<Self, D, A>>
+ Clone
+ Eq
+ PartialEq
+ Debug
+ Hash;
type AssignedWitness: Witness<Self::Addr>
+ Copy
+ Eq
+ PartialEq
+ Debug
+ Display
+ Hash
+ Into<SpecifiedAddr<Self::Addr>>;
type AddressConfig<I: Instant>: Default + Debug;
type ManualAddressConfig<I: Instant>: Default + Debug + Into<Self::AddressConfig<I>>;
type AddressState<I: Instant>: 'static + Inspectable;
type ConfigurationUpdate: From<IpDeviceConfigurationUpdate>
+ AsRef<IpDeviceConfigurationUpdate>
+ Debug;
type ConfigurationAndFlags: From<(Self::Configuration, IpDeviceFlags)>
+ AsRef<IpDeviceConfiguration>
+ AsRef<IpDeviceFlags>
+ AsMut<IpDeviceConfiguration>
+ PartialEq
+ Debug;
fn get_valid_until<I: Instant>(config: &Self::AddressConfig<I>) -> Lifetime<I>;
fn is_addr_assigned<I: Instant>(addr_state: &Self::AddressState<I>) -> bool;
fn timer_device_id<D: device::WeakId, A: IpAddressIdSpec>(
timer: &Self::Timer<D, A>,
) -> Option<D::Strong>;
fn take_addr_config_for_removal<I: Instant>(
addr_state: &mut Self::AddressState<I>,
) -> Option<Self::AddressConfig<I>>;
}
impl IpDeviceIpExt for Ipv4 {
type State<BT: IpDeviceStateBindingsTypes> = Ipv4DeviceState<BT>;
type Configuration = Ipv4DeviceConfiguration;
type Timer<D: device::WeakId, A: IpAddressIdSpec> = Ipv4DeviceTimerId<D>;
type AssignedWitness = SpecifiedAddr<Ipv4Addr>;
type AddressConfig<I: Instant> = Ipv4AddrConfig<I>;
type ManualAddressConfig<I: Instant> = Ipv4AddrConfig<I>;
type AddressState<I: Instant> = Ipv4AddressState<I>;
type ConfigurationUpdate = Ipv4DeviceConfigurationUpdate;
type ConfigurationAndFlags = Ipv4DeviceConfigurationAndFlags;
fn get_valid_until<I: Instant>(config: &Self::AddressConfig<I>) -> Lifetime<I> {
config.valid_until
}
fn is_addr_assigned<I: Instant>(addr_state: &Ipv4AddressState<I>) -> bool {
let Ipv4AddressState { config: _ } = addr_state;
true
}
fn timer_device_id<D: device::WeakId, A: IpAddressIdSpec>(
timer: &Self::Timer<D, A>,
) -> Option<D::Strong> {
timer.device_id()
}
fn take_addr_config_for_removal<I: Instant>(
addr_state: &mut Self::AddressState<I>,
) -> Option<Self::AddressConfig<I>> {
addr_state.config.take()
}
}
impl IpDeviceIpExt for Ipv6 {
type State<BT: IpDeviceStateBindingsTypes> = Ipv6DeviceState<BT>;
type Configuration = Ipv6DeviceConfiguration;
type Timer<D: device::WeakId, A: IpAddressIdSpec> = Ipv6DeviceTimerId<D, A::WeakV6>;
type AssignedWitness = Ipv6DeviceAddr;
type AddressConfig<I: Instant> = Ipv6AddrConfig<I>;
type ManualAddressConfig<I: Instant> = Ipv6AddrManualConfig<I>;
type AddressState<I: Instant> = Ipv6AddressState<I>;
type ConfigurationUpdate = Ipv6DeviceConfigurationUpdate;
type ConfigurationAndFlags = Ipv6DeviceConfigurationAndFlags;
fn get_valid_until<I: Instant>(config: &Self::AddressConfig<I>) -> Lifetime<I> {
config.valid_until()
}
fn is_addr_assigned<I: Instant>(addr_state: &Ipv6AddressState<I>) -> bool {
addr_state.flags.assigned
}
fn timer_device_id<D: device::WeakId, A: IpAddressIdSpec>(
timer: &Self::Timer<D, A>,
) -> Option<D::Strong> {
timer.device_id()
}
fn take_addr_config_for_removal<I: Instant>(
addr_state: &mut Self::AddressState<I>,
) -> Option<Self::AddressConfig<I>> {
addr_state.config.take()
}
}
/// An Ip address that witnesses properties needed to be assigned to a device.
pub(crate) type IpDeviceAddr<A> = SocketIpAddr<A>;
/// An IPv6 address that witnesses properties needed to be assigned to a device.
///
/// Like [`IpDeviceAddr`] but with stricter witnesses that are permitted for
/// IPv6 addresses.
pub(crate) type Ipv6DeviceAddr = NonMappedAddr<UnicastAddr<Ipv6Addr>>;
/// IP address assignment states.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum IpAddressState {
/// The address is unavailable because it's interface is not IP enabled.
Unavailable,
/// The address is assigned to an interface and can be considered bound to
/// it (all packets destined to the address will be accepted).
Assigned,
/// The address is considered unassigned to an interface for normal
/// operations, but has the intention of being assigned in the future (e.g.
/// once Duplicate Address Detection is completed).
Tentative,
}
/// The reason an address was removed.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum AddressRemovedReason {
/// The address was removed in response to external action.
Manual,
/// The address was removed because it was detected as a duplicate via DAD.
DadFailed,
}
#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
#[generic_over_ip(I, Ip)]
/// Events emitted from IP devices.
pub enum IpDeviceEvent<DeviceId, I: Ip, Instant> {
/// Address was assigned.
AddressAdded {
/// The device.
device: DeviceId,
/// The new address.
addr: AddrSubnet<I::Addr>,
/// Initial address state.
state: IpAddressState,
/// The lifetime for which the address is valid.
valid_until: Lifetime<Instant>,
},
/// Address was unassigned.
AddressRemoved {
/// The device.
device: DeviceId,
/// The removed address.
addr: SpecifiedAddr<I::Addr>,
/// The reason the address was removed.
reason: AddressRemovedReason,
},
/// Address state changed.
AddressStateChanged {
/// The device.
device: DeviceId,
/// The address whose state was changed.
addr: SpecifiedAddr<I::Addr>,
/// The new address state.
state: IpAddressState,
},
/// Address properties changed.
AddressPropertiesChanged {
/// The device.
device: DeviceId,
/// The address whose properties were changed.
addr: SpecifiedAddr<I::Addr>,
/// The new `valid_until` lifetime.
valid_until: Lifetime<Instant>,
},
/// IP was enabled/disabled on the device
EnabledChanged {
/// The device.
device: DeviceId,
/// `true` if IP was enabled on the device; `false` if IP was disabled.
ip_enabled: bool,
},
}
impl<
DeviceId,
BC: InstantBindingsTypes + EventContext<IpDeviceEvent<DeviceId, Ipv6, BC::Instant>>,
> EventContext<DadEvent<DeviceId>> for BC
{
fn on_event(&mut self, event: DadEvent<DeviceId>) {
match event {
DadEvent::AddressAssigned { device, addr } => BC::on_event(
self,
IpDeviceEvent::AddressStateChanged {
device,
addr: addr.into_specified(),
state: IpAddressState::Assigned,
},
),
}
}
}
/// The bindings execution context for IP devices.
pub trait IpDeviceBindingsContext<I: IpDeviceIpExt, D: device::StrongId>:
IpDeviceStateBindingsTypes
+ DeferredResourceRemovalContext
+ TimerContext
+ RngContext
+ EventContext<IpDeviceEvent<D, I, <Self as InstantBindingsTypes>::Instant>>
{
}
impl<
D: device::StrongId,
I: IpDeviceIpExt,
BC: IpDeviceStateBindingsTypes
+ DeferredResourceRemovalContext
+ TimerContext
+ RngContext
+ EventContext<IpDeviceEvent<D, I, <Self as InstantBindingsTypes>::Instant>>,
> IpDeviceBindingsContext<I, D> for BC
{
}
/// An IP address ID.
pub trait IpAddressId<A: IpAddress>: Clone + Eq + Debug + Hash {
/// The weak version of this ID.
type Weak: WeakIpAddressId<A>;
/// Downgrades this ID to a weak reference.
fn downgrade(&self) -> Self::Weak;
/// Returns the address this ID represents.
fn addr(&self) -> IpDeviceAddr<A>;
/// Returns the address subnet this ID represents.
fn addr_sub(&self) -> AddrSubnet<A, <A::Version as IpDeviceIpExt>::AssignedWitness>
where
A::Version: IpDeviceIpExt;
}
/// A weak IP address ID.
pub trait WeakIpAddressId<A: IpAddress>: Clone + Eq + Debug + Hash {
/// The strong version of this ID.
type Strong: IpAddressId<A>;
/// Attempts to upgrade this ID to the strong version.
///
/// Upgrading fails if this is no longer a valid assigned IP address.
fn upgrade(&self) -> Option<Self::Strong>;
}
/// Provides the execution context related to address IDs.
pub trait IpDeviceAddressIdContext<I: IpDeviceIpExt>: DeviceIdContext<AnyDevice> {
type AddressId: IpAddressId<I::Addr, Weak = Self::WeakAddressId>;
type WeakAddressId: WeakIpAddressId<I::Addr, Strong = Self::AddressId>;
}
/// A marker trait for a spec of address IDs.
///
/// This allows us to write types that are GenericOverIp that take the spec from
/// some type.
pub trait IpAddressIdSpec {
type WeakV4: WeakIpAddressId<Ipv4Addr>;
type WeakV6: WeakIpAddressId<Ipv6Addr>;
}
/// Ties an [`IpAddressIdSpec`] to a core context implementation.
pub trait IpAddressIdSpecContext:
IpDeviceAddressIdContext<Ipv4> + IpDeviceAddressIdContext<Ipv6>
{
type AddressIdSpec: IpAddressIdSpec<
WeakV4 = <Self as IpDeviceAddressIdContext<Ipv4>>::WeakAddressId,
WeakV6 = <Self as IpDeviceAddressIdContext<Ipv6>>::WeakAddressId,
>;
}
pub trait IpDeviceAddressContext<I: IpDeviceIpExt, BT: InstantBindingsTypes>:
IpDeviceAddressIdContext<I>
{
fn with_ip_address_state<O, F: FnOnce(&I::AddressState<BT::Instant>) -> O>(
&mut self,
device_id: &Self::DeviceId,
addr_id: &Self::AddressId,
cb: F,
) -> O;
fn with_ip_address_state_mut<O, F: FnOnce(&mut I::AddressState<BT::Instant>) -> O>(
&mut self,
device_id: &Self::DeviceId,
addr_id: &Self::AddressId,
cb: F,
) -> O;
}
/// Accessor for IP device state.
pub trait IpDeviceStateContext<I: IpDeviceIpExt, BT: IpDeviceStateBindingsTypes>:
IpDeviceAddressContext<I, BT>
{
type IpDeviceAddressCtx<'a>: IpDeviceAddressContext<
I,
BT,
DeviceId = Self::DeviceId,
AddressId = Self::AddressId,
>;
/// Calls the function with immutable access to the device's flags.
///
/// Note that this trait should only provide immutable access to the flags.
/// Changes to the IP device flags must only be performed while synchronizing
/// with the IP device configuration, so mutable access to the flags is through
/// `WithIpDeviceConfigurationMutInner::with_configuration_and_flags_mut`.
fn with_ip_device_flags<O, F: FnOnce(&IpDeviceFlags) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Adds an IP address for the device.
fn add_ip_address(
&mut self,
device_id: &Self::DeviceId,
addr: AddrSubnet<I::Addr, I::AssignedWitness>,
config: I::AddressConfig<BT::Instant>,
) -> Result<Self::AddressId, ExistsError>;
/// Removes an address from the device identified by the ID.
fn remove_ip_address(
&mut self,
device_id: &Self::DeviceId,
addr: Self::AddressId,
) -> RemoveResourceResultWithContext<AddrSubnet<I::Addr>, BT>;
/// Returns the address ID for the given address value.
fn get_address_id(
&mut self,
device_id: &Self::DeviceId,
addr: SpecifiedAddr<I::Addr>,
) -> Result<Self::AddressId, NotFoundError>;
/// The iterator given to `with_address_ids`.
type AddressIdsIter<'a>: Iterator<Item = Self::AddressId> + 'a;
/// Calls the function with an iterator over all the address IDs associated
/// with the device.
fn with_address_ids<
O,
F: FnOnce(Self::AddressIdsIter<'_>, &mut Self::IpDeviceAddressCtx<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with an immutable reference to the device's default
/// hop limit for this IP version.
fn with_default_hop_limit<O, F: FnOnce(&NonZeroU8) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with a mutable reference to the device's default
/// hop limit for this IP version.
fn with_default_hop_limit_mut<O, F: FnOnce(&mut NonZeroU8) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Joins the link-layer multicast group associated with the given IP
/// multicast group.
fn join_link_multicast_group(
&mut self,
bindings_ctx: &mut BT,
device_id: &Self::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
);
/// Leaves the link-layer multicast group associated with the given IP
/// multicast group.
fn leave_link_multicast_group(
&mut self,
bindings_ctx: &mut BT,
device_id: &Self::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
);
}
/// The context provided to the callback passed to
/// [`IpDeviceConfigurationContext::with_ip_device_configuration_mut`].
pub trait WithIpDeviceConfigurationMutInner<I: IpDeviceIpExt, BT: IpDeviceStateBindingsTypes>:
DeviceIdContext<AnyDevice>
{
type IpDeviceStateCtx<'s>: IpDeviceStateContext<I, BT, DeviceId = Self::DeviceId>
+ GmpHandler<I, BT>
+ NudIpHandler<I, BT>
+ 's
where
Self: 's;
/// Returns an immutable reference to a device's IP configuration and an
/// `IpDeviceStateCtx`.
fn ip_device_configuration_and_ctx(
&mut self,
) -> (&I::Configuration, Self::IpDeviceStateCtx<'_>);
/// Calls the function with a mutable reference to a device's IP
/// configuration and flags.
fn with_configuration_and_flags_mut<
O,
F: FnOnce(&mut I::Configuration, &mut IpDeviceFlags) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
}
/// The execution context for IP devices.
pub trait IpDeviceConfigurationContext<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, Self::DeviceId>,
>: IpDeviceStateContext<I, BC> + DeviceIdContext<AnyDevice>
{
type DevicesIter<'s>: Iterator<Item = Self::DeviceId> + 's;
type WithIpDeviceConfigurationInnerCtx<'s>: IpDeviceStateContext<I, BC, DeviceId = Self::DeviceId, AddressId = Self::AddressId>
+ GmpHandler<I, BC>
+ NudIpHandler<I, BC>
+ DadHandler<I, BC>
+ IpAddressRemovalHandler<I, BC>
+ 's;
type WithIpDeviceConfigurationMutInner<'s>: WithIpDeviceConfigurationMutInner<I, BC, DeviceId = Self::DeviceId>
+ 's;
type DeviceAddressAndGroupsAccessor<'s>: IpDeviceStateContext<I, BC, DeviceId = Self::DeviceId>
+ GmpQueryHandler<I, BC>
+ 's;
/// Calls the function with an immutable reference to the IP device
/// configuration and a `WithIpDeviceConfigurationInnerCtx`.
fn with_ip_device_configuration<
O,
F: FnOnce(&I::Configuration, Self::WithIpDeviceConfigurationInnerCtx<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with a `WithIpDeviceConfigurationMutInner`.
fn with_ip_device_configuration_mut<
O,
F: FnOnce(Self::WithIpDeviceConfigurationMutInner<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with an [`Iterator`] of IDs for all initialized
/// devices and an accessor for device state.
fn with_devices_and_state<
O,
F: FnOnce(Self::DevicesIter<'_>, Self::DeviceAddressAndGroupsAccessor<'_>) -> O,
>(
&mut self,
cb: F,
) -> O;
/// Gets the MTU for a device.
///
/// The MTU is the maximum size of an IP packet.
fn get_mtu(&mut self, device_id: &Self::DeviceId) -> Mtu;
/// Returns the ID of the loopback interface, if one exists on the system
/// and is initialized.
fn loopback_id(&mut self) -> Option<Self::DeviceId>;
}
/// The context provided to the callback passed to
/// [`Ipv6DeviceConfigurationContext::with_ipv6_device_configuration_mut`].
pub trait WithIpv6DeviceConfigurationMutInner<BC: IpDeviceBindingsContext<Ipv6, Self::DeviceId>>:
WithIpDeviceConfigurationMutInner<Ipv6, BC>
{
type Ipv6DeviceStateCtx<'s>: Ipv6DeviceContext<BC, DeviceId = Self::DeviceId>
+ GmpHandler<Ipv6, BC>
+ NudIpHandler<Ipv6, BC>
+ DadHandler<Ipv6, BC>
+ RsHandler<BC>
+ SlaacHandler<BC>
+ RouteDiscoveryHandler<BC>
+ 's
where
Self: 's;
/// Returns an immutable reference to a device's IPv6 configuration and an
/// `Ipv6DeviceStateCtx`.
fn ipv6_device_configuration_and_ctx(
&mut self,
) -> (&Ipv6DeviceConfiguration, Self::Ipv6DeviceStateCtx<'_>);
}
pub trait Ipv6DeviceConfigurationContext<BC: IpDeviceBindingsContext<Ipv6, Self::DeviceId>>:
IpDeviceConfigurationContext<Ipv6, BC>
{
type Ipv6DeviceStateCtx<'s>: Ipv6DeviceContext<BC, DeviceId = Self::DeviceId, AddressId = Self::AddressId>
+ GmpHandler<Ipv6, BC>
+ MldPacketHandler<BC, Self::DeviceId>
+ NudIpHandler<Ipv6, BC>
+ DadHandler<Ipv6, BC>
+ RsHandler<BC>
+ SlaacHandler<BC>
+ RouteDiscoveryHandler<BC>
+ 's;
type WithIpv6DeviceConfigurationMutInner<'s>: WithIpv6DeviceConfigurationMutInner<BC, DeviceId = Self::DeviceId>
+ 's;
/// Calls the function with an immutable reference to the IPv6 device
/// configuration and an `Ipv6DeviceStateCtx`.
fn with_ipv6_device_configuration<
O,
F: FnOnce(&Ipv6DeviceConfiguration, Self::Ipv6DeviceStateCtx<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with a `WithIpv6DeviceConfigurationMutInner`.
fn with_ipv6_device_configuration_mut<
O,
F: FnOnce(Self::WithIpv6DeviceConfigurationMutInner<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
}
/// The execution context for an IPv6 device.
pub trait Ipv6DeviceContext<BC: IpDeviceBindingsContext<Ipv6, Self::DeviceId>>:
IpDeviceStateContext<Ipv6, BC>
{
/// A link-layer address.
type LinkLayerAddr: AsRef<[u8]>;
/// Gets the device's link-layer address bytes, if the device supports
/// link-layer addressing.
fn get_link_layer_addr_bytes(
&mut self,
device_id: &Self::DeviceId,
) -> Option<Self::LinkLayerAddr>;
/// Gets the device's EUI-64 based interface identifier.
///
/// A `None` value indicates the device does not have an EUI-64 based
/// interface identifier.
fn get_eui64_iid(&mut self, device_id: &Self::DeviceId) -> Option<[u8; 8]>;
/// Sets the link MTU for the device.
fn set_link_mtu(&mut self, device_id: &Self::DeviceId, mtu: Mtu);
/// Calls the function with an immutable reference to the retransmit timer.
fn with_network_learned_parameters<O, F: FnOnce(&Ipv6NetworkLearnedParameters) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with a mutable reference to the retransmit timer.
fn with_network_learned_parameters_mut<O, F: FnOnce(&mut Ipv6NetworkLearnedParameters) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
}
/// An implementation of an IP device.
pub(crate) trait IpDeviceHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
fn is_router_device(&mut self, device_id: &Self::DeviceId) -> bool;
fn set_default_hop_limit(&mut self, device_id: &Self::DeviceId, hop_limit: NonZeroU8);
}
impl<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
> IpDeviceHandler<I, BC> for CC
{
fn is_router_device(&mut self, device_id: &Self::DeviceId) -> bool {
is_ip_forwarding_enabled(self, device_id)
}
fn set_default_hop_limit(&mut self, device_id: &Self::DeviceId, hop_limit: NonZeroU8) {
self.with_default_hop_limit_mut(device_id, |default_hop_limit| {
*default_hop_limit = hop_limit
})
}
}
pub(crate) fn receive_igmp_packet<CC, BC, B>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
buffer: B,
) where
CC: IpDeviceConfigurationContext<Ipv4, BC>,
BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
for<'a> CC::WithIpDeviceConfigurationInnerCtx<'a>: IpDeviceStateContext<Ipv4, BC, DeviceId = CC::DeviceId>
+ IgmpPacketHandler<BC, CC::DeviceId>,
B: BufferMut,
{
core_ctx.with_ip_device_configuration(device, |_config, mut core_ctx| {
IgmpPacketHandler::receive_igmp_packet(
&mut core_ctx,
bindings_ctx,
device,
src_ip,
dst_ip,
buffer,
)
})
}
/// An implementation of an IPv6 device.
pub(crate) trait Ipv6DeviceHandler<BC>: IpDeviceHandler<Ipv6, BC> {
/// A link-layer address.
type LinkLayerAddr: AsRef<[u8]>;
/// Gets the device's link-layer address bytes, if the device supports
/// link-layer addressing.
fn get_link_layer_addr_bytes(
&mut self,
device_id: &Self::DeviceId,
) -> Option<Self::LinkLayerAddr>;
/// Sets the discovered retransmit timer for the device.
fn set_discovered_retrans_timer(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
retrans_timer: NonZeroDuration,
);
/// Handles a received neighbor advertisement.
///
/// Takes action in response to a received neighbor advertisement for the
/// specified address. Returns the assignment state of the address on the
/// given interface, if there was one before any action was taken. That is,
/// this method returns `Some(Tentative {..})` when the address was
/// tentatively assigned (and now removed), `Some(Assigned)` if the address
/// was assigned (and so not removed), otherwise `None`.
fn remove_duplicate_tentative_address(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) -> IpAddressState;
/// Sets the link MTU for the device.
fn set_link_mtu(&mut self, device_id: &Self::DeviceId, mtu: Mtu);
/// Updates a discovered IPv6 route.
fn update_discovered_ipv6_route(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
route: Ipv6DiscoveredRoute,
lifetime: Option<NonZeroNdpLifetime>,
);
/// Applies a SLAAC update.
fn apply_slaac_update(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
prefix: Subnet<Ipv6Addr>,
preferred_lifetime: Option<NonZeroNdpLifetime>,
valid_lifetime: Option<NonZeroNdpLifetime>,
);
/// Receives an MLD packet for processing.
fn receive_mld_packet<B: ByteSlice>(
&mut self,
bindings_ctx: &mut BC,
device: &Self::DeviceId,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
packet: MldPacket<B>,
);
}
impl<
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
CC: Ipv6DeviceContext<BC> + Ipv6DeviceConfigurationContext<BC>,
> Ipv6DeviceHandler<BC> for CC
{
type LinkLayerAddr = CC::LinkLayerAddr;
fn get_link_layer_addr_bytes(
&mut self,
device_id: &Self::DeviceId,
) -> Option<CC::LinkLayerAddr> {
Ipv6DeviceContext::get_link_layer_addr_bytes(self, device_id)
}
fn set_discovered_retrans_timer(
&mut self,
_bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
retrans_timer: NonZeroDuration,
) {
self.with_network_learned_parameters_mut(device_id, |state| {
state.retrans_timer = Some(retrans_timer)
})
}
fn remove_duplicate_tentative_address(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) -> IpAddressState {
let addr_id = match self.get_address_id(device_id, addr.into_specified()) {
Ok(o) => o,
Err(NotFoundError) => return IpAddressState::Unavailable,
};
let assigned = self.with_ip_address_state(
device_id,
&addr_id,
|Ipv6AddressState {
flags: Ipv6AddressFlags { deprecated: _, assigned },
config: _,
}| { *assigned },
);
if assigned {
IpAddressState::Assigned
} else {
match del_ip_addr(
self,
bindings_ctx,
device_id,
DelIpAddr::AddressId(addr_id),
AddressRemovedReason::DadFailed,
) {
Ok(result) => {
bindings_ctx.defer_removal_result(result);
IpAddressState::Tentative
}
Err(NotFoundError) => {
// We may have raced with user removal of this address.
IpAddressState::Unavailable
}
}
}
}
fn set_link_mtu(&mut self, device_id: &Self::DeviceId, mtu: Mtu) {
Ipv6DeviceContext::set_link_mtu(self, device_id, mtu)
}
fn update_discovered_ipv6_route(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
route: Ipv6DiscoveredRoute,
lifetime: Option<NonZeroNdpLifetime>,
) {
self.with_ipv6_device_configuration(device_id, |_config, mut core_ctx| {
RouteDiscoveryHandler::update_route(
&mut core_ctx,
bindings_ctx,
device_id,
route,
lifetime,
)
})
}
fn apply_slaac_update(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
prefix: Subnet<Ipv6Addr>,
preferred_lifetime: Option<NonZeroNdpLifetime>,
valid_lifetime: Option<NonZeroNdpLifetime>,
) {
self.with_ipv6_device_configuration(device_id, |_config, mut core_ctx| {
SlaacHandler::apply_slaac_update(
&mut core_ctx,
bindings_ctx,
device_id,
prefix,
preferred_lifetime,
valid_lifetime,
)
})
}
fn receive_mld_packet<B: ByteSlice>(
&mut self,
bindings_ctx: &mut BC,
device: &Self::DeviceId,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
packet: MldPacket<B>,
) {
self.with_ipv6_device_configuration(device, |_config, mut core_ctx| {
MldPacketHandler::receive_mld_packet(
&mut core_ctx,
bindings_ctx,
device,
src_ip,
dst_ip,
packet,
)
})
}
}
/// The execution context for an IP device with a buffer.
pub(crate) trait IpDeviceSendContext<I: IpTypesIpExt, BC>:
DeviceIdContext<AnyDevice>
{
/// Sends an IP packet through the device.
fn send_ip_frame<S>(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
local_addr: SpecifiedAddr<I::Addr>,
body: S,
broadcast: Option<I::BroadcastMarker>,
egress_proof: ProofOfEgressCheck,
) -> Result<(), S>
where
S: Serializer + IpPacket<I>,
S::Buffer: BufferMut;
}
fn enable_ipv6_device_with_config<
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
CC: Ipv6DeviceContext<BC> + GmpHandler<Ipv6, BC> + RsHandler<BC> + DadHandler<Ipv6, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
config: &Ipv6DeviceConfiguration,
) {
// All nodes should join the all-nodes multicast group.
join_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS,
config,
);
GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id);
// Perform DAD for all addresses when enabling a device.
//
// We have to do this for all addresses (including ones that had DAD
// performed) as while the device was disabled, another node could have
// assigned the address and we wouldn't have responded to its DAD
// solicitations.
core_ctx
.with_address_ids(device_id, |addrs, _core_ctx| addrs.collect::<Vec<_>>())
.into_iter()
.for_each(|addr_id| {
bindings_ctx.on_event(IpDeviceEvent::AddressStateChanged {
device: device_id.clone(),
addr: addr_id.addr().into(),
state: IpAddressState::Tentative,
});
DadHandler::start_duplicate_address_detection(
core_ctx,
bindings_ctx,
device_id,
&addr_id,
);
});
// TODO(https://fxbug.dev/42178008): Generate link-local address with opaque
// IIDs.
if config.slaac_config.enable_stable_addresses {
if let Some(iid) = core_ctx.get_eui64_iid(device_id) {
let link_local_addr_sub = {
let mut addr = [0; 16];
addr[0..2].copy_from_slice(&[0xfe, 0x80]);
addr[(Ipv6::UNICAST_INTERFACE_IDENTIFIER_BITS / 8) as usize..]
.copy_from_slice(&iid);
AddrSubnet::new(
Ipv6Addr::from(addr),
Ipv6Addr::BYTES * 8 - Ipv6::UNICAST_INTERFACE_IDENTIFIER_BITS,
)
.expect("valid link-local address")
};
match add_ip_addr_subnet_with_config(
core_ctx,
bindings_ctx,
device_id,
link_local_addr_sub,
Ipv6AddrConfig::SLAAC_LINK_LOCAL,
config,
) {
Ok(_) => {}
Err(ExistsError) => {
// The address may have been added by admin action so it is safe
// to swallow the exists error.
}
}
}
}
// As per RFC 4861 section 6.3.7,
//
// A host sends Router Solicitations to the all-routers multicast
// address.
//
// If we are operating as a router, we do not solicit routers.
if !config.ip_config.forwarding_enabled {
RsHandler::start_router_solicitation(core_ctx, bindings_ctx, device_id);
}
}
fn disable_ipv6_device_with_config<
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
CC: Ipv6DeviceContext<BC>
+ GmpHandler<Ipv6, BC>
+ RsHandler<BC>
+ DadHandler<Ipv6, BC>
+ RouteDiscoveryHandler<BC>
+ SlaacHandler<BC>
+ NudIpHandler<Ipv6, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
device_config: &Ipv6DeviceConfiguration,
) {
NudIpHandler::flush_neighbor_table(core_ctx, bindings_ctx, device_id);
SlaacHandler::remove_all_slaac_addresses(core_ctx, bindings_ctx, device_id);
RouteDiscoveryHandler::invalidate_routes(core_ctx, bindings_ctx, device_id);
RsHandler::stop_router_solicitation(core_ctx, bindings_ctx, device_id);
// Delete the link-local address generated when enabling the device and stop
// DAD on the other addresses.
core_ctx
.with_address_ids(device_id, |addrs, core_ctx| {
addrs
.map(|addr_id| {
core_ctx.with_ip_address_state(
device_id,
&addr_id,
|Ipv6AddressState { flags: _, config }| (addr_id.clone(), *config),
)
})
.collect::<Vec<_>>()
})
.into_iter()
.for_each(|(addr_id, config)| {
if config == Some(Ipv6AddrConfig::SLAAC_LINK_LOCAL) {
del_ip_addr_inner_and_notify_handler(
core_ctx,
bindings_ctx,
device_id,
DelIpAddr::AddressId(addr_id),
AddressRemovedReason::Manual,
device_config,
)
.map(|remove_result| {
bindings_ctx.defer_removal_result(remove_result);
})
.unwrap_or_else(|NotFoundError| {
// We're not holding locks on the addresses anymore we must
// allow a NotFoundError since the address can be removed as
// we release the lock.
})
} else {
DadHandler::stop_duplicate_address_detection(
core_ctx,
bindings_ctx,
device_id,
&addr_id,
);
bindings_ctx.on_event(IpDeviceEvent::AddressStateChanged {
device: device_id.clone(),
addr: addr_id.addr().into(),
state: IpAddressState::Unavailable,
});
}
});
GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id);
leave_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS,
device_config,
);
}
fn enable_ipv4_device_with_config<
BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
CC: IpDeviceStateContext<Ipv4, BC> + GmpHandler<Ipv4, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
config: &Ipv4DeviceConfiguration,
) {
// All systems should join the all-systems multicast group.
join_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS,
config,
);
GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id);
core_ctx.with_address_ids(device_id, |addrs, _core_ctx| {
addrs.for_each(|addr| {
bindings_ctx.on_event(IpDeviceEvent::AddressStateChanged {
device: device_id.clone(),
addr: addr.addr().into(),
state: IpAddressState::Assigned,
});
})
})
}
fn disable_ipv4_device_with_config<
BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
CC: IpDeviceStateContext<Ipv4, BC> + GmpHandler<Ipv4, BC> + NudIpHandler<Ipv4, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
config: &Ipv4DeviceConfiguration,
) {
NudIpHandler::flush_neighbor_table(core_ctx, bindings_ctx, device_id);
GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id);
leave_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS,
config,
);
core_ctx.with_address_ids(device_id, |addrs, _core_ctx| {
addrs.for_each(|addr| {
bindings_ctx.on_event(IpDeviceEvent::AddressStateChanged {
device: device_id.clone(),
addr: addr.addr().into(),
state: IpAddressState::Unavailable,
});
})
})
}
/// Gets the IPv4 address and subnet pairs associated with this device.
///
/// Returns an [`Iterator`] of `AddrSubnet`.
pub(crate) fn with_assigned_ipv4_addr_subnets<
BT: IpDeviceStateBindingsTypes,
CC: IpDeviceStateContext<Ipv4, BT>,
O,
F: FnOnce(Box<dyn Iterator<Item = AddrSubnet<Ipv4Addr>> + '_>) -> O,
>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
cb: F,
) -> O {
core_ctx
.with_address_ids(device_id, |addrs, _core_ctx| cb(Box::new(addrs.map(|a| a.addr_sub()))))
}
/// Gets a single IPv4 address and subnet for a device.
pub(crate) fn get_ipv4_addr_subnet<
BT: IpDeviceStateBindingsTypes,
CC: IpDeviceStateContext<Ipv4, BT>,
>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
) -> Option<AddrSubnet<Ipv4Addr>> {
with_assigned_ipv4_addr_subnets(core_ctx, device_id, |mut addrs| addrs.nth(0))
}
/// Gets the hop limit for new IPv6 packets that will be sent out from `device`.
pub(crate) fn get_ipv6_hop_limit<
BT: IpDeviceStateBindingsTypes,
CC: IpDeviceStateContext<Ipv6, BT>,
>(
core_ctx: &mut CC,
device: &CC::DeviceId,
) -> NonZeroU8 {
core_ctx.with_default_hop_limit(device, Clone::clone)
}
/// Is IP packet forwarding enabled?
pub(crate) fn is_ip_forwarding_enabled<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
) -> bool {
core_ctx.with_ip_device_configuration(device_id, |state, _ctx| {
AsRef::<IpDeviceConfiguration>::as_ref(state).forwarding_enabled
})
}
fn join_ip_multicast_with_config<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC> + GmpHandler<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
// Not used but required to make sure that the caller is currently holding a
// a reference to the IP device's IP configuration as a way to prove that
// caller has synchronized this operation with other accesses to the IP
// device configuration.
_config: &I::Configuration,
) {
match core_ctx.gmp_join_group(bindings_ctx, device_id, multicast_addr) {
GroupJoinResult::Joined(()) => {
core_ctx.join_link_multicast_group(bindings_ctx, device_id, multicast_addr)
}
GroupJoinResult::AlreadyMember => {}
}
}
/// Adds `device_id` to a multicast group `multicast_addr`.
///
/// Calling `join_ip_multicast` multiple times is completely safe. A counter
/// will be kept for the number of times `join_ip_multicast` has been called
/// with the same `device_id` and `multicast_addr` pair. To completely leave a
/// multicast group, [`leave_ip_multicast`] must be called the same number of
/// times `join_ip_multicast` has been called for the same `device_id` and
/// `multicast_addr` pair. The first time `join_ip_multicast` is called for a
/// new `device` and `multicast_addr` pair, the device will actually join the
/// multicast group.
pub(crate) fn join_ip_multicast<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
) {
core_ctx.with_ip_device_configuration(device_id, |config, mut core_ctx| {
join_ip_multicast_with_config(
&mut core_ctx,
bindings_ctx,
device_id,
multicast_addr,
config,
)
})
}
fn leave_ip_multicast_with_config<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC> + GmpHandler<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
// Not used but required to make sure that the caller is currently holding a
// a reference to the IP device's IP configuration as a way to prove that
// caller has synchronized this operation with other accesses to the IP
// device configuration.
_config: &I::Configuration,
) {
match core_ctx.gmp_leave_group(bindings_ctx, device_id, multicast_addr) {
GroupLeaveResult::Left(()) => {
core_ctx.leave_link_multicast_group(bindings_ctx, device_id, multicast_addr)
}
GroupLeaveResult::StillMember => {}
GroupLeaveResult::NotMember => panic!(
"attempted to leave IP multicast group we were not a member of: {}",
multicast_addr,
),
}
}
/// Removes `device_id` from a multicast group `multicast_addr`.
///
/// `leave_ip_multicast` will attempt to remove `device_id` from a multicast
/// group `multicast_addr`. `device_id` may have "joined" the same multicast
/// address multiple times, so `device_id` will only leave the multicast group
/// once `leave_ip_multicast` has been called for each corresponding
/// [`join_ip_multicast`]. That is, if `join_ip_multicast` gets called 3
/// times and `leave_ip_multicast` gets called two times (after all 3
/// `join_ip_multicast` calls), `device_id` will still be in the multicast
/// group until the next (final) call to `leave_ip_multicast`.
///
/// # Panics
///
/// If `device_id` is not currently in the multicast group `multicast_addr`.
pub(crate) fn leave_ip_multicast<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
) {
core_ctx.with_ip_device_configuration(device_id, |config, mut core_ctx| {
leave_ip_multicast_with_config(
&mut core_ctx,
bindings_ctx,
device_id,
multicast_addr,
config,
)
})
}
fn add_ip_addr_subnet_with_config<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC> + GmpHandler<I, BC> + DadHandler<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
addr_sub: AddrSubnet<I::Addr, I::AssignedWitness>,
addr_config: I::AddressConfig<BC::Instant>,
// Not used but required to make sure that the caller is currently holding a
// a reference to the IP device's IP configuration as a way to prove that
// caller has synchronized this operation with other accesses to the IP
// device configuration.
_device_config: &I::Configuration,
) -> Result<CC::AddressId, ExistsError> {
info!("adding addr {addr_sub:?} config {addr_config:?} to device {device_id:?}");
let valid_until = I::get_valid_until(&addr_config);
let addr_id = core_ctx.add_ip_address(device_id, addr_sub, addr_config)?;
assert_eq!(addr_id.addr().addr(), addr_sub.addr().get());
let ip_enabled =
core_ctx.with_ip_device_flags(device_id, |IpDeviceFlags { ip_enabled }| *ip_enabled);
let state = match ip_enabled {
true => CC::INITIAL_ADDRESS_STATE,
false => IpAddressState::Unavailable,
};
bindings_ctx.on_event(IpDeviceEvent::AddressAdded {
device: device_id.clone(),
addr: addr_sub.to_witness(),
state,
valid_until,
});
if ip_enabled {
// NB: We don't start DAD if the device is disabled. DAD will be
// performed when the device is enabled for all addressed.
DadHandler::start_duplicate_address_detection(core_ctx, bindings_ctx, device_id, &addr_id)
}
Ok(addr_id)
}
/// A handler to abstract side-effects of removing IP device addresses.
pub trait IpAddressRemovalHandler<I: IpDeviceIpExt, BC: InstantBindingsTypes>:
DeviceIdContext<AnyDevice>
{
/// Notifies the handler that the addr `addr` with `config` has been removed
/// from `device_id` with `reason`.
fn on_address_removed(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr_sub: AddrSubnet<I::Addr, I::AssignedWitness>,
config: I::AddressConfig<BC::Instant>,
reason: AddressRemovedReason,
);
}
/// There's no special action to be taken for removed IPv4 addresses.
impl<CC: DeviceIdContext<AnyDevice>, BC: InstantBindingsTypes> IpAddressRemovalHandler<Ipv4, BC>
for CC
{
fn on_address_removed(
&mut self,
_bindings_ctx: &mut BC,
_device_id: &Self::DeviceId,
_addr_sub: AddrSubnet<Ipv4Addr, SpecifiedAddr<Ipv4Addr>>,
_config: Ipv4AddrConfig<BC::Instant>,
_reason: AddressRemovedReason,
) {
// Nothing to do.
}
}
/// Provide the IPv6 implementation for all [`SlaacHandler`] implementations.
impl<CC: SlaacHandler<BC>, BC: InstantContext> IpAddressRemovalHandler<Ipv6, BC> for CC {
fn on_address_removed(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>,
config: Ipv6AddrConfig<BC::Instant>,
reason: AddressRemovedReason,
) {
match config {
Ipv6AddrConfig::Slaac(s) => {
SlaacHandler::on_address_removed(self, bindings_ctx, device_id, addr_sub, s, reason)
}
Ipv6AddrConfig::Manual(_manual_config) => (),
}
}
}
pub(crate) enum DelIpAddr<Id, A> {
SpecifiedAddr(SpecifiedAddr<A>),
AddressId(Id),
}
impl<Id: IpAddressId<A>, A: IpAddress> Display for DelIpAddr<Id, A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DelIpAddr::SpecifiedAddr(addr) => write!(f, "{}", *addr),
DelIpAddr::AddressId(id) => write!(f, "{}", id.addr()),
}
}
}
// Deletes an IP address from a device, returning the address and its
// configuration if it was removed.
fn del_ip_addr_inner<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC> + GmpHandler<I, BC> + DadHandler<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
addr: DelIpAddr<CC::AddressId, I::Addr>,
reason: AddressRemovedReason,
// Require configuration lock to do this.
_config: &I::Configuration,
) -> Result<
(
AddrSubnet<I::Addr, I::AssignedWitness>,
I::AddressConfig<BC::Instant>,
RemoveResourceResultWithContext<AddrSubnet<I::Addr>, BC>,
),
NotFoundError,
> {
let addr_id = match addr {
DelIpAddr::SpecifiedAddr(addr) => core_ctx.get_address_id(device_id, addr)?,
DelIpAddr::AddressId(id) => id,
};
DadHandler::stop_duplicate_address_detection(core_ctx, bindings_ctx, device_id, &addr_id);
// Extract the configuration out of the address to properly mark it as ready
// for deletion. If the configuration has already been taken, consider as if
// the address is already removed.
let addr_config = core_ctx
.with_ip_address_state_mut(device_id, &addr_id, |addr_state| {
I::take_addr_config_for_removal(addr_state)
})
.ok_or(NotFoundError)?;
let addr_sub = addr_id.addr_sub();
let result = core_ctx.remove_ip_address(device_id, addr_id);
bindings_ctx.on_event(IpDeviceEvent::AddressRemoved {
device: device_id.clone(),
addr: addr_sub.addr().into(),
reason,
});
Ok((addr_sub, addr_config, result))
}
/// Removes an IP address and associated subnet from this device.
fn del_ip_addr<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceConfigurationContext<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
addr: DelIpAddr<CC::AddressId, I::Addr>,
reason: AddressRemovedReason,
) -> Result<RemoveResourceResultWithContext<AddrSubnet<I::Addr>, BC>, NotFoundError> {
info!("removing addr {addr} from device {device_id:?}");
core_ctx.with_ip_device_configuration(device_id, |config, mut core_ctx| {
del_ip_addr_inner_and_notify_handler(
&mut core_ctx,
bindings_ctx,
device_id,
addr,
reason,
config,
)
})
}
/// Removes an IP address and associated subnet from this device and notifies
/// the address removal handler.
fn del_ip_addr_inner_and_notify_handler<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC>
+ GmpHandler<I, BC>
+ DadHandler<I, BC>
+ IpAddressRemovalHandler<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
addr: DelIpAddr<CC::AddressId, I::Addr>,
reason: AddressRemovedReason,
config: &I::Configuration,
) -> Result<RemoveResourceResultWithContext<AddrSubnet<I::Addr>, BC>, NotFoundError> {
del_ip_addr_inner(core_ctx, bindings_ctx, device_id, addr, reason, config).map(
|(addr_sub, config, result)| {
core_ctx.on_address_removed(bindings_ctx, device_id, addr_sub, config, reason);
result
},
)
}
pub(super) fn is_ip_device_enabled<
I: IpDeviceIpExt,
BC: IpDeviceBindingsContext<I, CC::DeviceId>,
CC: IpDeviceStateContext<I, BC>,
>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
) -> bool {
core_ctx.with_ip_device_flags(device_id, |flags| flags.ip_enabled)
}
/// Removes IPv4 state for the device without emitting events.
pub(crate) fn clear_ipv4_device_state<
BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
CC: IpDeviceConfigurationContext<Ipv4, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
) {
core_ctx.with_ip_device_configuration_mut(device_id, |mut core_ctx| {
let ip_enabled = core_ctx.with_configuration_and_flags_mut(device_id, |_config, flags| {
// Start by force-disabling IPv4 so we're sure we won't handle
// any more packets.
let IpDeviceFlags { ip_enabled } = flags;
core::mem::replace(ip_enabled, false)
});
let (config, mut core_ctx) = core_ctx.ip_device_configuration_and_ctx();
let core_ctx = &mut core_ctx;
if ip_enabled {
disable_ipv4_device_with_config(core_ctx, bindings_ctx, device_id, config);
}
})
}
/// Removes IPv6 state for the device without emitting events.
pub(crate) fn clear_ipv6_device_state<
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
CC: Ipv6DeviceConfigurationContext<BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
) {
core_ctx.with_ipv6_device_configuration_mut(device_id, |mut core_ctx| {
let ip_enabled = core_ctx.with_configuration_and_flags_mut(device_id, |_config, flags| {
// Start by force-disabling IPv6 so we're sure we won't handle
// any more packets.
let IpDeviceFlags { ip_enabled } = flags;
core::mem::replace(ip_enabled, false)
});
let (config, mut core_ctx) = core_ctx.ipv6_device_configuration_and_ctx();
let core_ctx = &mut core_ctx;
if ip_enabled {
disable_ipv6_device_with_config(core_ctx, bindings_ctx, device_id, config);
}
})
}
#[cfg(test)]
pub(crate) mod testutil {
use super::*;
use crate::{
device::DeviceId,
testutil::{FakeBindingsCtx, FakeCtx},
};
/// Gets the IPv6 address and subnet pairs associated with this device which are
/// in the assigned state.
///
/// Tentative IP addresses (addresses which are not yet fully bound to a device)
/// and deprecated IP addresses (addresses which have been assigned but should
/// no longer be used for new connections) will not be returned by
/// `get_assigned_ipv6_addr_subnets`.
///
/// Returns an [`Iterator`] of `AddrSubnet`.
///
/// See [`Tentative`] and [`AddrSubnet`] for more information.
pub(crate) fn with_assigned_ipv6_addr_subnets<
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
CC: Ipv6DeviceContext<BC>,
O,
F: FnOnce(Box<dyn Iterator<Item = AddrSubnet<Ipv6Addr>> + '_>) -> O,
>(
core_ctx: &mut CC,
device_id: &CC::DeviceId,
cb: F,
) -> O {
core_ctx.with_address_ids(device_id, |addrs, core_ctx| {
cb(Box::new(addrs.filter_map(|addr_id| {
core_ctx
.with_ip_address_state(
device_id,
&addr_id,
|Ipv6AddressState {
flags: Ipv6AddressFlags { deprecated: _, assigned },
config: _,
}| { *assigned },
)
.then(|| addr_id.addr_sub().to_witness())
})))
})
}
#[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)]
pub(crate) fn set_ip_device_enabled<I: crate::IpExt>(
ctx: &mut FakeCtx,
device: &DeviceId<FakeBindingsCtx>,
enabled: bool,
prev_enabled: bool,
) {
let make_update = |enabled| IpDeviceConfigurationUpdate {
ip_enabled: Some(enabled),
..Default::default()
};
let prev = ctx
.core_api()
.device_ip::<I>()
.update_configuration(device, make_update(enabled).into())
.unwrap();
assert_eq!(prev.as_ref(), &make_update(prev_enabled),);
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct FakeWeakAddressId<T>(pub T);
impl<A: IpAddress, T: IpAddressId<A>> WeakIpAddressId<A> for FakeWeakAddressId<T> {
type Strong = T;
fn upgrade(&self) -> Option<Self::Strong> {
let Self(inner) = self;
Some(inner.clone())
}
}
impl<A: IpAddress> IpAddressId<A> for AddrSubnet<A, <A::Version as IpDeviceIpExt>::AssignedWitness>
where
A::Version: IpDeviceIpExt,
SpecifiedAddr<A>: From<<A::Version as IpDeviceIpExt>::AssignedWitness>,
{
type Weak = FakeWeakAddressId<Self>;
fn downgrade(&self) -> Self::Weak {
FakeWeakAddressId(self.clone())
}
fn addr(&self) -> IpDeviceAddr<A> {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct WrapIn<I: IpDeviceIpExt>(I::AssignedWitness);
A::Version::map_ip(
WrapIn(self.addr()),
|WrapIn(v4_addr)| IpDeviceAddr::new_ipv4_specified(v4_addr),
|WrapIn(v6_addr)| IpDeviceAddr::new_from_ipv6_device_addr(v6_addr),
)
}
fn addr_sub(&self) -> AddrSubnet<A, <A::Version as IpDeviceIpExt>::AssignedWitness> {
self.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use core::{num::NonZeroU16, time::Duration};
use assert_matches::assert_matches;
use fakealloc::collections::HashSet;
use ip_test_macro::ip_test;
use test_case::test_case;
use net_declare::{net_ip_v4, net_ip_v6, net_mac};
use net_types::LinkLocalAddr;
use crate::{
context::testutil::FakeInstant,
device::{
ethernet::{EthernetCreationProperties, EthernetLinkDevice, MaxEthernetFrameSize},
loopback::{LoopbackCreationProperties, LoopbackDevice},
DeviceId,
},
ip::{
device::{
api::SetIpAddressPropertiesError, config::UpdateIpConfigurationError,
nud::LinkResolutionResult, slaac::SlaacConfiguration,
testutil::set_ip_device_enabled,
},
gmp::GmpDelayedReportTimerId,
},
state::StackStateBuilder,
testutil::{
assert_empty, Ctx, DispatchedEvent, FakeBindingsCtx, FakeCtx, TestIpExt as _,
DEFAULT_INTERFACE_METRIC, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
time::TimerIdInner,
TimerId,
};
#[test]
fn enable_disable_ipv4() {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
let local_mac = Ipv4::FAKE_CONFIG.local_mac;
let ethernet_device_id =
ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: MaxEthernetFrameSize::from_mtu(Ipv4::MINIMUM_LINK_MTU).unwrap(),
},
DEFAULT_INTERFACE_METRIC,
);
let device_id = ethernet_device_id.clone().into();
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
let set_ipv4_enabled = |ctx: &mut FakeCtx, enabled, expected_prev| {
set_ip_device_enabled::<Ipv4>(ctx, &device_id, enabled, expected_prev)
};
set_ipv4_enabled(&mut ctx, true, false);
let weak_device_id = device_id.downgrade();
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: true,
})]
);
let addr = SpecifiedAddr::new(net_ip_v4!("192.0.2.1")).expect("addr should be unspecified");
let mac = net_mac!("02:23:45:67:89:ab");
ctx.core_api()
.neighbor::<Ipv4, EthernetLinkDevice>()
.insert_static_entry(&ethernet_device_id, *addr, mac)
.unwrap();
assert_eq!(
ctx.bindings_ctx.take_events(),
[DispatchedEvent::NeighborIpv4(nud::Event {
device: ethernet_device_id.downgrade(),
addr,
kind: nud::EventKind::Added(nud::EventState::Static(mac)),
at: ctx.bindings_ctx.now(),
})]
);
assert_matches!(
ctx.core_api().neighbor::<Ipv4, EthernetLinkDevice>().resolve_link_addr(
&ethernet_device_id,
&addr,
),
LinkResolutionResult::Resolved(got) => assert_eq!(got, mac)
);
set_ipv4_enabled(&mut ctx, false, true);
assert_eq!(
ctx.bindings_ctx.take_events(),
[
DispatchedEvent::NeighborIpv4(nud::Event {
device: ethernet_device_id.downgrade(),
addr,
kind: nud::EventKind::Removed,
at: ctx.bindings_ctx.now(),
}),
DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: false,
})
]
);
// Assert that static ARP entries are flushed on link down.
nud::testutil::assert_neighbor_unknown::<Ipv4, _, _, _>(
&mut ctx.core_ctx(),
ethernet_device_id,
addr,
);
let ipv4_addr_subnet = AddrSubnet::new(Ipv4Addr::new([192, 168, 0, 1]), 24).unwrap();
ctx.core_api()
.device_ip::<Ipv4>()
.add_ip_addr_subnet(&device_id, ipv4_addr_subnet.clone())
.expect("failed to add IPv4 Address");
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::AddressAdded {
device: weak_device_id.clone(),
addr: ipv4_addr_subnet.clone(),
state: IpAddressState::Unavailable,
valid_until: Lifetime::Infinite,
})]
);
set_ipv4_enabled(&mut ctx, true, false);
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::AddressStateChanged {
device: weak_device_id.clone(),
addr: ipv4_addr_subnet.addr(),
state: IpAddressState::Assigned,
}),
DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: true,
}),
]
);
// Verify that a redundant "enable" does not generate any events.
set_ipv4_enabled(&mut ctx, true, true);
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
let valid_until = Lifetime::Finite(FakeInstant::from(Duration::from_secs(1)));
ctx.core_api()
.device_ip::<Ipv4>()
.set_addr_properties(&device_id, ipv4_addr_subnet.addr(), valid_until)
.expect("set properties should succeed");
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::AddressPropertiesChanged {
device: weak_device_id.clone(),
addr: ipv4_addr_subnet.addr(),
valid_until
})]
);
// Verify that a redundant "set properties" does not generate any events.
ctx.core_api()
.device_ip::<Ipv4>()
.set_addr_properties(&device_id, ipv4_addr_subnet.addr(), valid_until)
.expect("set properties should succeed");
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
set_ipv4_enabled(&mut ctx, false, true);
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::AddressStateChanged {
device: weak_device_id.clone(),
addr: ipv4_addr_subnet.addr(),
state: IpAddressState::Unavailable,
}),
DispatchedEvent::IpDeviceIpv4(IpDeviceEvent::EnabledChanged {
device: weak_device_id,
ip_enabled: false,
}),
]
);
// Verify that a redundant "disable" does not generate any events.
set_ipv4_enabled(&mut ctx, false, false);
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
}
fn enable_ipv6_device(
ctx: &mut FakeCtx,
device_id: &DeviceId<FakeBindingsCtx>,
ll_addr: AddrSubnet<Ipv6Addr, LinkLocalAddr<UnicastAddr<Ipv6Addr>>>,
expected_prev: bool,
) {
set_ip_device_enabled::<Ipv6>(ctx, device_id, true, expected_prev);
assert_eq!(
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
&mut ctx.core_ctx(),
device_id,
|addrs, _core_ctx| {
addrs.map(|addr_id| addr_id.addr_sub().addr().get()).collect::<HashSet<_>>()
}
),
HashSet::from([ll_addr.ipv6_unicast_addr()]),
"enabled device expected to generate link-local address"
);
}
#[test]
fn enable_disable_ipv6() {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv6::FAKE_CONFIG.local_mac;
let ethernet_device_id =
ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
);
let device_id = ethernet_device_id.clone().into();
let ll_addr = local_mac.to_ipv6_link_local();
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
// Doesn't matter as long as we perform DAD and router
// solicitation.
dad_transmits: Some(NonZeroU16::new(1)),
max_router_solicitations: Some(NonZeroU8::new(1)),
// Auto-generate a link-local address.
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
..Default::default()
}),
ip_config: IpDeviceConfigurationUpdate {
gmp_enabled: Some(true),
..Default::default()
},
..Default::default()
},
)
.unwrap();
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
// Enable the device and observe an auto-generated link-local address,
// router solicitation and DAD for the auto-generated address.
let test_enable_device = |ctx: &mut FakeCtx, expected_prev| {
enable_ipv6_device(ctx, &device_id, ll_addr, expected_prev);
let timers = vec![
(
TimerId(TimerIdInner::Ipv6Device(
Ipv6DeviceTimerId::Rs(RsTimerId::new(device_id.downgrade())).into(),
)),
..,
),
(
TimerId(TimerIdInner::Ipv6Device(
Ipv6DeviceTimerId::Dad(DadTimerId {
device_id: device_id.downgrade(),
addr: IpDeviceStateContext::<Ipv6, _>::get_address_id(
&mut ctx.core_ctx(),
&device_id,
ll_addr.ipv6_unicast_addr().into(),
)
.unwrap()
.downgrade(),
})
.into(),
)),
..,
),
(
TimerId(TimerIdInner::Ipv6Device(
Ipv6DeviceTimerId::Mld(MldTimerId(GmpDelayedReportTimerId {
device: device_id.downgrade(),
_marker: Default::default(),
}))
.into(),
)),
..,
),
];
ctx.bindings_ctx.timer_ctx().assert_timers_installed_range(timers);
};
test_enable_device(&mut ctx, false);
let weak_device_id = device_id.downgrade();
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressAdded {
device: weak_device_id.clone(),
addr: ll_addr.to_witness(),
state: IpAddressState::Tentative,
valid_until: Lifetime::Infinite,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: true,
})
]
);
// Because the added address is from SLAAC, setting its lifetime should fail.
let valid_until = Lifetime::Finite(FakeInstant::from(Duration::from_secs(1)));
assert_matches!(
ctx.core_api().device_ip::<Ipv6>().set_addr_properties(
&device_id,
ll_addr.addr().into(),
valid_until,
),
Err(SetIpAddressPropertiesError::NotManual)
);
let addr =
SpecifiedAddr::new(net_ip_v6!("2001:db8::1")).expect("addr should be unspecified");
let mac = net_mac!("02:23:45:67:89:ab");
ctx.core_api()
.neighbor::<Ipv6, _>()
.insert_static_entry(&ethernet_device_id, *addr, mac)
.unwrap();
assert_eq!(
ctx.bindings_ctx.take_events(),
[DispatchedEvent::NeighborIpv6(nud::Event {
device: ethernet_device_id.downgrade(),
addr,
kind: nud::EventKind::Added(nud::EventState::Static(mac)),
at: ctx.bindings_ctx.now(),
})]
);
assert_matches!(
ctx.core_api().neighbor::<Ipv6, _>().resolve_link_addr(
&ethernet_device_id,
&addr,
),
LinkResolutionResult::Resolved(got) => assert_eq!(got, mac)
);
let test_disable_device = |ctx: &mut FakeCtx, expected_prev| {
set_ip_device_enabled::<Ipv6>(ctx, &device_id, false, expected_prev);
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
};
test_disable_device(&mut ctx, true);
assert_eq!(
ctx.bindings_ctx.take_events(),
[
DispatchedEvent::NeighborIpv6(nud::Event {
device: ethernet_device_id.downgrade(),
addr,
kind: nud::EventKind::Removed,
at: ctx.bindings_ctx.now(),
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressRemoved {
device: weak_device_id.clone(),
addr: ll_addr.addr().into(),
reason: AddressRemovedReason::Manual,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: false,
})
]
);
let mut core_ctx = ctx.core_ctx();
let core_ctx = &mut core_ctx;
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
core_ctx,
&device_id,
|addrs, _core_ctx| {
assert_empty(addrs);
},
);
// Assert that static NDP entry was removed on link down.
nud::testutil::assert_neighbor_unknown::<Ipv6, _, _, _>(core_ctx, ethernet_device_id, addr);
ctx.core_api()
.device_ip::<Ipv6>()
.add_ip_addr_subnet(&device_id, ll_addr.replace_witness().unwrap())
.expect("add MAC based IPv6 link-local address");
assert_eq!(
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
&mut ctx.core_ctx(),
&device_id,
|addrs, _core_ctx| {
addrs.map(|addr_id| addr_id.addr_sub().addr().get()).collect::<HashSet<_>>()
}
),
HashSet::from([ll_addr.ipv6_unicast_addr()])
);
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressAdded {
device: weak_device_id.clone(),
addr: ll_addr.to_witness(),
state: IpAddressState::Unavailable,
valid_until: Lifetime::Infinite,
})]
);
test_enable_device(&mut ctx, false);
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressStateChanged {
device: weak_device_id.clone(),
addr: ll_addr.addr().into(),
state: IpAddressState::Tentative,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: true,
})
]
);
ctx.core_api()
.device_ip::<Ipv6>()
.set_addr_properties(&device_id, ll_addr.addr().into(), valid_until)
.expect("set properties should succeed");
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressPropertiesChanged {
device: weak_device_id.clone(),
addr: ll_addr.addr().into(),
valid_until
})]
);
// Verify that a redundant "set properties" does not generate any events.
ctx.core_api()
.device_ip::<Ipv6>()
.set_addr_properties(&device_id, ll_addr.addr().into(), valid_until)
.expect("set properties should succeed");
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
test_disable_device(&mut ctx, true);
// The address was manually added, don't expect it to be removed.
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressStateChanged {
device: weak_device_id.clone(),
addr: ll_addr.addr().into(),
state: IpAddressState::Unavailable,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: false,
})
]
);
// Verify that a redundant "disable" does not generate any events.
test_disable_device(&mut ctx, false);
let (mut core_ctx, bindings_ctx) = ctx.contexts();
let core_ctx = &mut core_ctx;
assert_eq!(bindings_ctx.take_events()[..], []);
assert_eq!(
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
core_ctx,
&device_id,
|addrs, _core_ctx| {
addrs.map(|addr_id| addr_id.addr_sub().addr().get()).collect::<HashSet<_>>()
}
),
HashSet::from([ll_addr.ipv6_unicast_addr()]),
"manual addresses should not be removed on device disable"
);
test_enable_device(&mut ctx, false);
assert_eq!(
ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressStateChanged {
device: weak_device_id.clone(),
addr: ll_addr.addr().into(),
state: IpAddressState::Tentative,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id,
ip_enabled: true,
})
]
);
// Verify that a redundant "enable" does not generate any events.
test_enable_device(&mut ctx, true);
assert_eq!(ctx.bindings_ctx.take_events()[..], []);
// Disable device again so timers are cancelled.
test_disable_device(&mut ctx, true);
let _ = ctx.bindings_ctx.take_events();
}
#[test]
fn notify_on_dad_failure_ipv6() {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv6::FAKE_CONFIG.local_mac;
let device_id = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
)
.into();
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(NonZeroU16::new(1)),
max_router_solicitations: Some(NonZeroU8::new(1)),
// Auto-generate a link-local address.
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
..Default::default()
}),
ip_config: IpDeviceConfigurationUpdate {
gmp_enabled: Some(true),
..Default::default()
},
..Default::default()
},
)
.unwrap();
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let ll_addr = local_mac.to_ipv6_link_local();
enable_ipv6_device(&mut ctx, &device_id, ll_addr, false);
let weak_device_id = device_id.downgrade();
assert_eq!(
&ctx.bindings_ctx.take_events()[..],
[
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressAdded {
device: weak_device_id.clone(),
addr: ll_addr.to_witness(),
state: IpAddressState::Tentative,
valid_until: Lifetime::Infinite,
}),
DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::EnabledChanged {
device: weak_device_id.clone(),
ip_enabled: true,
}),
]
);
let assigned_addr = AddrSubnet::new(net_ip_v6!("fe80::1"), 64).unwrap();
ctx.core_api()
.device_ip::<Ipv6>()
.add_ip_addr_subnet(&device_id, assigned_addr)
.expect("add succeeds");
let Ctx { core_ctx, bindings_ctx } = &mut ctx;
assert_eq!(
bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressAdded {
device: weak_device_id.clone(),
addr: assigned_addr.to_witness(),
state: IpAddressState::Tentative,
valid_until: Lifetime::Infinite,
}),]
);
// When DAD fails, an event should be emitted and the address should be
// removed.
assert_eq!(
Ipv6DeviceHandler::remove_duplicate_tentative_address(
&mut core_ctx.context(),
bindings_ctx,
&device_id,
assigned_addr.ipv6_unicast_addr()
),
IpAddressState::Tentative,
);
assert_eq!(
bindings_ctx.take_events()[..],
[DispatchedEvent::IpDeviceIpv6(IpDeviceEvent::AddressRemoved {
device: weak_device_id,
addr: assigned_addr.addr(),
reason: AddressRemovedReason::DadFailed,
}),]
);
assert_eq!(
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
&mut core_ctx.context(),
&device_id,
|addrs, _core_ctx| {
addrs.map(|addr_id| addr_id.addr_sub().addr().get()).collect::<HashSet<_>>()
}
),
HashSet::from([ll_addr.ipv6_unicast_addr()]),
"manual addresses should be removed on DAD failure"
);
// Disable device and take all events to cleanup references.
set_ip_device_enabled::<Ipv6>(&mut ctx, &device_id, false, true);
let _ = ctx.bindings_ctx.take_events();
}
#[ip_test]
#[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)]
fn update_ip_device_configuration_err<I: Ip + crate::IpExt>() {
let mut ctx = FakeCtx::default();
let loopback_device_id = ctx
.core_api()
.device::<LoopbackDevice>()
.add_device_with_default_state(
LoopbackCreationProperties { mtu: Mtu::new(u16::MAX as u32) },
DEFAULT_INTERFACE_METRIC,
)
.into();
let mut api = ctx.core_api().device_ip::<I>();
let original_state = api.get_configuration(&loopback_device_id);
assert_eq!(
api.update_configuration(
&loopback_device_id,
IpDeviceConfigurationUpdate {
ip_enabled: Some(!AsRef::<IpDeviceFlags>::as_ref(&original_state).ip_enabled),
gmp_enabled: Some(
!AsRef::<IpDeviceConfiguration>::as_ref(&original_state).gmp_enabled
),
forwarding_enabled: Some(true),
}
.into(),
)
.unwrap_err(),
UpdateIpConfigurationError::ForwardingNotSupported,
);
assert_eq!(original_state, api.get_configuration(&loopback_device_id));
}
#[test]
fn update_ipv4_configuration_return() {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv4::FAKE_CONFIG.local_mac;
let device_id = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: MaxEthernetFrameSize::from_mtu(Ipv4::MINIMUM_LINK_MTU).unwrap(),
},
DEFAULT_INTERFACE_METRIC,
)
.into();
let mut api = ctx.core_api().device_ip::<Ipv4>();
// Perform no update.
assert_eq!(
api.update_configuration(&device_id, Ipv4DeviceConfigurationUpdate::default()),
Ok(Ipv4DeviceConfigurationUpdate::default()),
);
// Enable all but forwarding. All features are initially disabled.
assert_eq!(
api.update_configuration(
&device_id,
Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(false),
gmp_enabled: Some(true),
},
},
),
Ok(Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(false),
forwarding_enabled: Some(false),
gmp_enabled: Some(false),
},
}),
);
// Change forwarding config.
assert_eq!(
api.update_configuration(
&device_id,
Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(true),
gmp_enabled: None,
},
},
),
Ok(Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(false),
gmp_enabled: None,
},
}),
);
// No update to anything (GMP enabled set to already set
// value).
assert_eq!(
api.update_configuration(
&device_id,
Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: None,
forwarding_enabled: None,
gmp_enabled: Some(true),
},
},
),
Ok(Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: None,
forwarding_enabled: None,
gmp_enabled: Some(true),
},
}),
);
// Disable/change everything.
assert_eq!(
api.update_configuration(
&device_id,
Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(false),
forwarding_enabled: Some(false),
gmp_enabled: Some(false),
},
},
),
Ok(Ipv4DeviceConfigurationUpdate {
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(true),
gmp_enabled: Some(true),
},
}),
);
}
#[test]
fn update_ipv6_configuration_return() {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv6::FAKE_CONFIG.local_mac;
let device_id = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: MaxEthernetFrameSize::from_mtu(Ipv6::MINIMUM_LINK_MTU).unwrap(),
},
DEFAULT_INTERFACE_METRIC,
)
.into();
let mut api = ctx.core_api().device_ip::<Ipv6>();
// Perform no update.
assert_eq!(
api.update_configuration(&device_id, Ipv6DeviceConfigurationUpdate::default()),
Ok(Ipv6DeviceConfigurationUpdate::default()),
);
// Enable all but forwarding. All features are initially disabled.
assert_eq!(
api.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(NonZeroU16::new(1)),
max_router_solicitations: Some(NonZeroU8::new(2)),
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
temporary_address_configuration: None
}),
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(false),
gmp_enabled: Some(true),
},
},
),
Ok(Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(None),
max_router_solicitations: Some(None),
slaac_config: Some(SlaacConfiguration::default()),
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(false),
forwarding_enabled: Some(false),
gmp_enabled: Some(false),
},
}),
);
// Change forwarding config.
assert_eq!(
api.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
dad_transmits: None,
max_router_solicitations: None,
slaac_config: None,
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(true),
gmp_enabled: None,
},
},
),
Ok(Ipv6DeviceConfigurationUpdate {
dad_transmits: None,
max_router_solicitations: None,
slaac_config: None,
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(false),
gmp_enabled: None,
},
}),
);
// No update to anything (GMP enabled set to already set
// value).
assert_eq!(
api.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
dad_transmits: None,
max_router_solicitations: None,
slaac_config: None,
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: None,
forwarding_enabled: None,
gmp_enabled: Some(true),
},
},
),
Ok(Ipv6DeviceConfigurationUpdate {
dad_transmits: None,
max_router_solicitations: None,
slaac_config: None,
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: None,
forwarding_enabled: None,
gmp_enabled: Some(true),
},
}),
);
// Disable/change everything.
assert_eq!(
api.update_configuration(
&device_id,
Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(None),
max_router_solicitations: Some(None),
slaac_config: Some(SlaacConfiguration::default()),
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(false),
forwarding_enabled: Some(false),
gmp_enabled: Some(false),
},
},
),
Ok(Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(NonZeroU16::new(1)),
max_router_solicitations: Some(NonZeroU8::new(2)),
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
temporary_address_configuration: None
}),
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
forwarding_enabled: Some(true),
gmp_enabled: Some(true),
},
}),
);
}
#[test_case(false; "stable addresses enabled generates link local")]
#[test_case(true; "stable addresses disabled does not generate link local")]
fn configure_link_local_address_generation(enable_stable_addresses: bool) {
let mut ctx = FakeCtx::new_with_builder(StackStateBuilder::default());
ctx.bindings_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv6::FAKE_CONFIG.local_mac;
let device_id = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: local_mac,
max_frame_size: MaxEthernetFrameSize::from_mtu(Ipv4::MINIMUM_LINK_MTU).unwrap(),
},
DEFAULT_INTERFACE_METRIC,
)
.into();
let new_config = Ipv6DeviceConfigurationUpdate {
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses,
..Default::default()
}),
..Default::default()
};
let _prev: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(&device_id, new_config)
.unwrap();
set_ip_device_enabled::<Ipv6>(&mut ctx, &device_id, true, false);
let expected_addrs = if enable_stable_addresses {
HashSet::from([local_mac.to_ipv6_link_local().ipv6_unicast_addr()])
} else {
HashSet::new()
};
assert_eq!(
IpDeviceStateContext::<Ipv6, _>::with_address_ids(
&mut ctx.core_ctx(),
&device_id,
|addrs, _core_ctx| {
addrs.map(|addr_id| addr_id.addr_sub().addr().get()).collect::<HashSet<_>>()
}
),
expected_addrs,
);
}
}