blob: 3d07889f8b5b44a4149adcfccc84792947577d06 [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 dad;
mod integration;
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::num::NonZeroU8;
#[cfg(test)]
use net_types::ip::IpVersion;
use net_types::{
ip::{AddrSubnet, Ip, IpAddress as _, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr},
MulticastAddr, SpecifiedAddr, UnicastAddr,
};
use packet::{BufferMut, EmptyBuf, Serializer};
use packet_formats::utils::NonZeroDuration;
use crate::{
context::{EventContext, InstantContext, RngContext, TimerContext, TimerHandler},
error::{ExistsError, NotFoundError},
ip::{
device::{
dad::{DadHandler, DadTimerId},
route_discovery::{Ipv6DiscoveredRouteTimerId, RouteDiscoveryHandler},
router_solicitation::{RsHandler, RsTimerId},
slaac::{SlaacHandler, SlaacTimerId},
state::{
AddrConfig, AddressState, DelIpv6AddrReason, IpDeviceConfiguration, IpDeviceState,
IpDeviceStateIpExt, Ipv4DeviceConfiguration, Ipv4DeviceState, Ipv6AddressEntry,
Ipv6DeviceConfiguration, Ipv6DeviceState,
},
},
gmp::{
igmp::IgmpTimerId, mld::MldDelayedReportTimerId, GmpHandler, GroupJoinResult,
GroupLeaveResult,
},
IpDeviceIdContext,
},
Instant,
};
#[cfg(test)]
use crate::{error::NotSupportedError, ip::IpDeviceId as _};
/// A timer ID for IPv4 devices.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub(crate) struct Ipv4DeviceTimerId<DeviceId>(IgmpTimerId<DeviceId>);
impl<DeviceId> From<IgmpTimerId<DeviceId>> for Ipv4DeviceTimerId<DeviceId> {
fn from(id: IgmpTimerId<DeviceId>) -> Ipv4DeviceTimerId<DeviceId> {
Ipv4DeviceTimerId(id)
}
}
// If we are provided with an impl of `TimerContext<Ipv4DeviceTimerId<_>>`, then
// we can in turn provide an impl of `TimerContext` for IGMP.
impl_timer_context!(
DeviceId,
Ipv4DeviceTimerId<DeviceId>,
IgmpTimerId<DeviceId>,
Ipv4DeviceTimerId(id),
id
);
/// Handle an IPv4 device timer firing.
pub(crate) fn handle_ipv4_timer<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: BufferIpDeviceContext<Ipv4, C, EmptyBuf>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
Ipv4DeviceTimerId(id): Ipv4DeviceTimerId<SC::DeviceId>,
) {
TimerHandler::handle_timer(sync_ctx, ctx, id)
}
/// A timer ID for IPv6 devices.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub(crate) enum Ipv6DeviceTimerId<DeviceId> {
Mld(MldDelayedReportTimerId<DeviceId>),
Dad(DadTimerId<DeviceId>),
Rs(RsTimerId<DeviceId>),
RouteDiscovery(Ipv6DiscoveredRouteTimerId<DeviceId>),
Slaac(SlaacTimerId<DeviceId>),
}
impl<DeviceId> From<MldDelayedReportTimerId<DeviceId>> for Ipv6DeviceTimerId<DeviceId> {
fn from(id: MldDelayedReportTimerId<DeviceId>) -> Ipv6DeviceTimerId<DeviceId> {
Ipv6DeviceTimerId::Mld(id)
}
}
impl<DeviceId> From<DadTimerId<DeviceId>> for Ipv6DeviceTimerId<DeviceId> {
fn from(id: DadTimerId<DeviceId>) -> Ipv6DeviceTimerId<DeviceId> {
Ipv6DeviceTimerId::Dad(id)
}
}
impl<DeviceId> From<RsTimerId<DeviceId>> for Ipv6DeviceTimerId<DeviceId> {
fn from(id: RsTimerId<DeviceId>) -> Ipv6DeviceTimerId<DeviceId> {
Ipv6DeviceTimerId::Rs(id)
}
}
impl<DeviceId> From<Ipv6DiscoveredRouteTimerId<DeviceId>> for Ipv6DeviceTimerId<DeviceId> {
fn from(id: Ipv6DiscoveredRouteTimerId<DeviceId>) -> Ipv6DeviceTimerId<DeviceId> {
Ipv6DeviceTimerId::RouteDiscovery(id)
}
}
impl<DeviceId> From<SlaacTimerId<DeviceId>> for Ipv6DeviceTimerId<DeviceId> {
fn from(id: SlaacTimerId<DeviceId>) -> Ipv6DeviceTimerId<DeviceId> {
Ipv6DeviceTimerId::Slaac(id)
}
}
// If we are provided with an impl of `TimerContext<Ipv6DeviceTimerId<_>>`, then
// we can in turn provide an impl of `TimerContext` for MLD and DAD.
impl_timer_context!(
DeviceId,
Ipv6DeviceTimerId<DeviceId>,
MldDelayedReportTimerId<DeviceId>,
Ipv6DeviceTimerId::Mld(id),
id
);
impl_timer_context!(
DeviceId,
Ipv6DeviceTimerId<DeviceId>,
DadTimerId<DeviceId>,
Ipv6DeviceTimerId::Dad(id),
id
);
impl_timer_context!(
DeviceId,
Ipv6DeviceTimerId<DeviceId>,
RsTimerId<DeviceId>,
Ipv6DeviceTimerId::Rs(id),
id
);
impl_timer_context!(
DeviceId,
Ipv6DeviceTimerId<DeviceId>,
Ipv6DiscoveredRouteTimerId<DeviceId>,
Ipv6DeviceTimerId::RouteDiscovery(id),
id
);
impl_timer_context!(
DeviceId,
Ipv6DeviceTimerId<DeviceId>,
SlaacTimerId<DeviceId>,
Ipv6DeviceTimerId::Slaac(id),
id
);
/// Handle an IPv6 device timer firing.
pub(crate) fn handle_ipv6_timer<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: BufferIpDeviceContext<Ipv6, C, EmptyBuf>
+ DadHandler<C>
+ RsHandler<C>
+ TimerHandler<C, Ipv6DiscoveredRouteTimerId<SC::DeviceId>>
+ TimerHandler<C, MldDelayedReportTimerId<SC::DeviceId>>
+ TimerHandler<C, SlaacTimerId<SC::DeviceId>>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
id: Ipv6DeviceTimerId<SC::DeviceId>,
) {
match id {
Ipv6DeviceTimerId::Mld(id) => TimerHandler::handle_timer(sync_ctx, ctx, id),
Ipv6DeviceTimerId::Dad(id) => DadHandler::handle_timer(sync_ctx, ctx, id),
Ipv6DeviceTimerId::Rs(id) => RsHandler::handle_timer(sync_ctx, ctx, id),
Ipv6DeviceTimerId::RouteDiscovery(id) => TimerHandler::handle_timer(sync_ctx, ctx, id),
Ipv6DeviceTimerId::Slaac(id) => TimerHandler::handle_timer(sync_ctx, ctx, id),
}
}
/// An extension trait adding IP device properties.
pub(crate) trait IpDeviceIpExt<Instant, DeviceId>: IpDeviceStateIpExt<Instant> {
type State: AsRef<IpDeviceState<Instant, Self>> + AsRef<IpDeviceConfiguration>;
type Timer;
}
impl<I: Instant, DeviceId> IpDeviceIpExt<I, DeviceId> for Ipv4 {
type State = Ipv4DeviceState<I>;
type Timer = Ipv4DeviceTimerId<DeviceId>;
}
impl<I: Instant, DeviceId> IpDeviceIpExt<I, DeviceId> for Ipv6 {
type State = Ipv6DeviceState<I>;
type Timer = Ipv6DeviceTimerId<DeviceId>;
}
/// IP address assignment states.
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum IpAddressState {
/// 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,
}
impl From<AddressState> for IpAddressState {
fn from(state: AddressState) -> IpAddressState {
match state {
AddressState::Assigned => IpAddressState::Assigned,
AddressState::Tentative { .. } => IpAddressState::Tentative,
}
}
}
#[derive(Debug)]
/// Events emitted from IP devices.
pub enum IpDeviceEvent<DeviceId, I: Ip> {
/// Address was assigned.
AddressAdded {
/// The device.
device: DeviceId,
/// The new address.
addr: AddrSubnet<I::Addr>,
/// Initial address state.
state: IpAddressState,
},
/// Address was unassigned.
AddressRemoved {
/// The device.
device: DeviceId,
/// The removed address.
addr: I::Addr,
},
/// Address state changed.
AddressStateChanged {
/// The device.
device: DeviceId,
/// The address whose state was changed.
addr: I::Addr,
/// The new address state.
state: IpAddressState,
},
}
/// The non-synchronized execution context for IP devices.
pub(crate) trait IpDeviceNonSyncContext<
I: IpDeviceIpExt<<Self as InstantContext>::Instant, DeviceId>,
DeviceId,
>: RngContext + TimerContext<I::Timer> + EventContext<IpDeviceEvent<DeviceId, I>>
{
}
impl<
DeviceId,
I: IpDeviceIpExt<<C as InstantContext>::Instant, DeviceId>,
C: RngContext + TimerContext<I::Timer> + EventContext<IpDeviceEvent<DeviceId, I>>,
> IpDeviceNonSyncContext<I, DeviceId> for C
{
}
/// The execution context for IP devices.
pub(crate) trait IpDeviceContext<
I: IpDeviceIpExt<C::Instant, Self::DeviceId>,
C: IpDeviceNonSyncContext<I, Self::DeviceId>,
>: IpDeviceIdContext<I>
{
/// Gets immutable access to an IP device's state.
fn get_ip_device_state(&self, device_id: Self::DeviceId) -> &I::State;
/// Gets mutable access to an IP device's state.
fn get_ip_device_state_mut(&mut self, device_id: Self::DeviceId) -> &mut I::State;
/// Returns an [`Iterator`] of IDs for all initialized devices.
fn iter_devices(&self) -> Box<dyn Iterator<Item = Self::DeviceId> + '_>;
/// Gets the MTU for a device.
///
/// The MTU is the maximum size of an IP packet.
fn get_mtu(&self, device_id: Self::DeviceId) -> u32;
/// Joins the link-layer multicast group associated with the given IP
/// multicast group.
fn join_link_multicast_group(
&mut self,
ctx: &mut C,
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,
ctx: &mut C,
device_id: Self::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
);
}
/// The execution context for an IPv6 device.
pub(crate) trait Ipv6DeviceContext<C: IpDeviceNonSyncContext<Ipv6, Self::DeviceId>>:
IpDeviceContext<Ipv6, C>
{
/// Gets the device's link-layer address bytes, if the device supports
/// link-layer addressing.
fn get_link_layer_addr_bytes(&self, device_id: Self::DeviceId) -> Option<&[u8]>;
/// 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(&self, device_id: Self::DeviceId) -> Option<[u8; 8]>;
}
/// An implementation of an IP device.
pub(crate) trait IpDeviceHandler<I: Ip, C>: IpDeviceIdContext<I> {
fn is_router_device(&self, device_id: Self::DeviceId) -> bool;
}
impl<
I: IpDeviceIpExt<C::Instant, SC::DeviceId>,
C: IpDeviceNonSyncContext<I, SC::DeviceId>,
SC: IpDeviceContext<I, C>,
> IpDeviceHandler<I, C> for SC
{
fn is_router_device(&self, device_id: Self::DeviceId) -> bool {
AsRef::<IpDeviceState<_, _>>::as_ref(self.get_ip_device_state(device_id)).routing_enabled
}
}
/// An implementation of an IPv6 device.
pub(crate) trait Ipv6DeviceHandler<C>: IpDeviceHandler<Ipv6, C> {
/// Gets the device's link-layer address bytes, if the device supports
/// link-layer addressing.
fn get_link_layer_addr_bytes(&self, device_id: Self::DeviceId) -> Option<&[u8]>;
/// Sets the discovered retransmit timer for the device.
fn set_discovered_retrans_timer(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
retrans_timer: NonZeroDuration,
);
/// Removes a tentative address as a result of duplicate address detection.
///
/// Returns whether or not the address was removed. That is, this method
/// returns `Ok(true)` when the address was tentatively assigned and
/// `Ok(false)` if the address is fully assigned.
fn remove_duplicate_tentative_address(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) -> Result<bool, NotFoundError>;
}
impl<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + DadHandler<C> + SlaacHandler<C>,
> Ipv6DeviceHandler<C> for SC
{
fn get_link_layer_addr_bytes(&self, device_id: Self::DeviceId) -> Option<&[u8]> {
Ipv6DeviceContext::get_link_layer_addr_bytes(self, device_id)
}
fn set_discovered_retrans_timer(
&mut self,
_ctx: &mut C,
device_id: Self::DeviceId,
retrans_timer: NonZeroDuration,
) {
self.get_ip_device_state_mut(device_id).retrans_timer = retrans_timer;
}
fn remove_duplicate_tentative_address(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) -> Result<bool, NotFoundError> {
let address_state = self
.get_ip_device_state(device_id)
.ip_state
.iter_addrs()
.find_map(
|Ipv6AddressEntry { addr_sub, state: address_state, config: _, deprecated: _ }| {
(addr_sub.addr() == addr).then(|| (*address_state).into())
},
)
.ok_or(NotFoundError)?;
Ok(match address_state {
IpAddressState::Tentative => {
del_ipv6_addr_with_reason(
self,
ctx,
device_id,
&addr.into_specified(),
DelIpv6AddrReason::DadFailed,
)
.unwrap();
true
}
IpAddressState::Assigned => false,
})
}
}
/// The execution context for an IP device with a buffer.
pub(crate) trait BufferIpDeviceContext<
I: IpDeviceIpExt<C::Instant, Self::DeviceId>,
C: IpDeviceNonSyncContext<I, Self::DeviceId>,
B: BufferMut,
>: IpDeviceContext<I, C>
{
/// Sends an IP packet through the device.
fn send_ip_frame<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
local_addr: SpecifiedAddr<I::Addr>,
body: S,
) -> Result<(), S>;
}
fn enable_ipv6_device<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + RsHandler<C> + DadHandler<C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
) {
let Ipv6DeviceState {
ip_state: _,
config:
Ipv6DeviceConfiguration {
dad_transmits,
max_router_solicitations: _,
slaac_config: _,
ip_config: IpDeviceConfiguration { ip_enabled: _, gmp_enabled: _ },
},
retrans_timer: _,
router_soliciations_remaining: _,
route_discovery: _,
} = sync_ctx.get_ip_device_state_mut(device_id);
let dad_transmits = *dad_transmits;
// All nodes should join the all-nodes multicast group.
join_ip_multicast(sync_ctx, ctx, device_id, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
GmpHandler::gmp_handle_maybe_enabled(sync_ctx, 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.
sync_ctx
.get_ip_device_state_mut(device_id)
.ip_state
.iter_addrs_mut()
.map(|Ipv6AddressEntry { addr_sub, state, config: _, deprecated: _ }| {
*state = AddressState::Tentative { dad_transmits_remaining: dad_transmits };
addr_sub.ipv6_unicast_addr()
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|addr| {
ctx.on_event(IpDeviceEvent::AddressStateChanged {
device: device_id,
addr: *addr,
state: IpAddressState::Tentative,
});
DadHandler::do_duplicate_address_detection(sync_ctx, ctx, device_id, addr);
});
// TODO(https://fxbug.dev/95946): Generate link-local address with opaque
// IIDs.
if let Some(iid) = sync_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_ipv6_addr_subnet(
sync_ctx,
ctx,
device_id,
link_local_addr_sub,
AddrConfig::SLAAC_LINK_LOCAL,
) {
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 !is_ipv6_routing_enabled(sync_ctx, device_id) {
RsHandler::start_router_solicitation(sync_ctx, ctx, device_id);
}
}
fn disable_ipv6_device<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C>
+ GmpHandler<Ipv6, C>
+ RsHandler<C>
+ DadHandler<C>
+ RouteDiscoveryHandler<C>
+ SlaacHandler<C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
) {
SlaacHandler::remove_all_slaac_addresses(sync_ctx, ctx, device_id);
RouteDiscoveryHandler::invalidate_routes(sync_ctx, ctx, device_id);
RsHandler::stop_router_solicitation(sync_ctx, ctx, device_id);
// Delete the link-local address generated when enabling the device and stop
// DAD on the other addresses.
sync_ctx
.get_ip_device_state(device_id)
.ip_state
.iter_addrs()
.map(|Ipv6AddressEntry { addr_sub, state: _, config, deprecated: _ }| {
(addr_sub.ipv6_unicast_addr(), *config)
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|(addr, config)| {
if config == AddrConfig::SLAAC_LINK_LOCAL {
del_ipv6_addr_with_reason(
sync_ctx,
ctx,
device_id,
&addr.into_specified(),
DelIpv6AddrReason::ManualAction,
)
.expect("delete listed address")
} else {
DadHandler::stop_duplicate_address_detection(sync_ctx, ctx, device_id, addr)
}
});
GmpHandler::gmp_handle_disabled(sync_ctx, ctx, device_id);
leave_ip_multicast(sync_ctx, ctx, device_id, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
}
fn enable_ipv4_device<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C> + GmpHandler<Ipv4, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
) {
// All systems should join the all-systems multicast group.
join_ip_multicast(sync_ctx, ctx, device_id, Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS);
GmpHandler::gmp_handle_maybe_enabled(sync_ctx, ctx, device_id);
}
fn disable_ipv4_device<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C> + GmpHandler<Ipv4, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
) {
GmpHandler::gmp_handle_disabled(sync_ctx, ctx, device_id);
leave_ip_multicast(sync_ctx, ctx, device_id, Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS);
}
/// Gets the IPv4 address and subnet pairs associated with this device.
///
/// Returns an [`Iterator`] of `AddrSubnet`.
pub(crate) fn get_assigned_ipv4_addr_subnets<
'a,
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &'a SC,
device_id: SC::DeviceId,
) -> impl Iterator<Item = AddrSubnet<Ipv4Addr>> + 'a {
sync_ctx.get_ip_device_state(device_id).ip_state.iter_addrs().cloned()
}
/// 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 get_assigned_ipv6_addr_subnets<
'a,
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &'a SC,
device_id: SC::DeviceId,
) -> impl Iterator<Item = AddrSubnet<Ipv6Addr>> + 'a {
sync_ctx.get_ip_device_state(device_id).ip_state.iter_addrs().filter_map(|a| {
if a.state.is_assigned() {
Some((*a.addr_sub()).to_witness())
} else {
None
}
})
}
/// Gets a single IPv4 address and subnet for a device.
pub(super) fn get_ipv4_addr_subnet<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> Option<AddrSubnet<Ipv4Addr>> {
get_assigned_ipv4_addr_subnets(sync_ctx, device_id).nth(0)
}
/// Gets the state associated with an IPv4 device.
pub(crate) fn get_ipv4_device_state<
'a,
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &'a SC,
device_id: SC::DeviceId,
) -> &'a IpDeviceState<C::Instant, Ipv4> {
&sync_ctx.get_ip_device_state(device_id).ip_state
}
/// Gets the state associated with an IPv6 device.
pub(crate) fn get_ipv6_device_state<
'a,
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &'a SC,
device_id: SC::DeviceId,
) -> &'a IpDeviceState<C::Instant, Ipv6> {
&sync_ctx.get_ip_device_state(device_id).ip_state
}
/// Gets the hop limit for new IPv6 packets that will be sent out from `device`.
pub(crate) fn get_ipv6_hop_limit<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &SC,
device: SC::DeviceId,
) -> NonZeroU8 {
get_ipv6_device_state(sync_ctx, device).default_hop_limit
}
/// Iterates over all of the IPv4 devices in the stack.
pub(super) fn iter_ipv4_devices<
'a,
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &'a SC,
) -> impl Iterator<Item = (SC::DeviceId, &'a IpDeviceState<C::Instant, Ipv4>)> + 'a {
sync_ctx.iter_devices().map(move |device| (device, get_ipv4_device_state(sync_ctx, device)))
}
/// Iterates over all of the IPv6 devices in the stack.
pub(super) fn iter_ipv6_devices<
'a,
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &'a SC,
) -> impl Iterator<Item = (SC::DeviceId, &'a IpDeviceState<C::Instant, Ipv6>)> + 'a {
sync_ctx.iter_devices().map(move |device| (device, get_ipv6_device_state(sync_ctx, device)))
}
/// Is IPv4 packet routing enabled on `device`?
pub(crate) fn is_ipv4_routing_enabled<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> bool {
get_ipv4_device_state(sync_ctx, device_id).routing_enabled
}
/// Is IPv6 packet routing enabled on `device`?
pub(crate) fn is_ipv6_routing_enabled<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> bool {
get_ipv6_device_state(sync_ctx, device_id).routing_enabled
}
/// Enables or disables IP packet routing on `device`.
#[cfg(test)]
pub(crate) fn set_routing_enabled<
C: IpDeviceNonSyncContext<Ipv4, <SC as IpDeviceIdContext<Ipv4>>::DeviceId>
+ IpDeviceNonSyncContext<Ipv6, <SC as IpDeviceIdContext<Ipv6>>::DeviceId>,
SC: IpDeviceContext<Ipv4, C> + Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + RsHandler<C>,
I: Ip,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: <SC as IpDeviceIdContext<Ipv6>>::DeviceId,
enabled: bool,
) -> Result<(), NotSupportedError>
where
SC: IpDeviceIdContext<Ipv6, DeviceId = <SC as IpDeviceIdContext<Ipv4>>::DeviceId>,
{
match I::VERSION {
IpVersion::V4 => set_ipv4_routing_enabled(sync_ctx, ctx, device, enabled),
IpVersion::V6 => set_ipv6_routing_enabled(sync_ctx, ctx, device, enabled),
}
}
/// Enables or disables IPv4 packet routing on `device_id`.
#[cfg(test)]
fn set_ipv4_routing_enabled<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &mut SC,
_ctx: &mut C,
device_id: SC::DeviceId,
enabled: bool,
) -> Result<(), NotSupportedError> {
if device_id.is_loopback() {
return Err(NotSupportedError);
}
sync_ctx.get_ip_device_state_mut(device_id).ip_state.routing_enabled = enabled;
Ok(())
}
/// Enables or disables IPv4 packet routing on `device_id`.
///
/// When routing is enabled/disabled, the interface will leave/join the all
/// routers link-local multicast group and stop/start soliciting routers.
///
/// Does nothing if the routing status does not change as a consequence of this
/// call.
#[cfg(test)]
pub(crate) fn set_ipv6_routing_enabled<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + RsHandler<C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
enabled: bool,
) -> Result<(), NotSupportedError> {
if device_id.is_loopback() {
return Err(NotSupportedError);
}
if is_ipv6_routing_enabled(sync_ctx, device_id) == enabled {
return Ok(());
}
if enabled {
RsHandler::stop_router_solicitation(sync_ctx, ctx, device_id);
sync_ctx.get_ip_device_state_mut(device_id).ip_state.routing_enabled = true;
join_ip_multicast(sync_ctx, ctx, device_id, Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS);
} else {
leave_ip_multicast(
sync_ctx,
ctx,
device_id,
Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
);
sync_ctx.get_ip_device_state_mut(device_id).ip_state.routing_enabled = false;
RsHandler::start_router_solicitation(sync_ctx, ctx, device_id);
}
Ok(())
}
/// 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<C::Instant, SC::DeviceId>,
C: IpDeviceNonSyncContext<I, SC::DeviceId>,
SC: IpDeviceContext<I, C> + GmpHandler<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
) {
match sync_ctx.gmp_join_group(ctx, device_id, multicast_addr) {
GroupJoinResult::Joined(()) => {
sync_ctx.join_link_multicast_group(ctx, device_id, multicast_addr)
}
GroupJoinResult::AlreadyMember => {}
}
}
/// 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<C::Instant, SC::DeviceId>,
C: IpDeviceNonSyncContext<I, SC::DeviceId>,
SC: IpDeviceContext<I, C> + GmpHandler<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
multicast_addr: MulticastAddr<I::Addr>,
) {
match sync_ctx.gmp_leave_group(ctx, device_id, multicast_addr) {
GroupLeaveResult::Left(()) => {
sync_ctx.leave_link_multicast_group(ctx, device_id, multicast_addr)
}
GroupLeaveResult::StillMember => {}
GroupLeaveResult::NotMember => panic!(
"attempted to leave IP multicast group we were not a member of: {}",
multicast_addr,
),
}
}
/// Adds an IPv4 address and associated subnet to this device.
pub(crate) fn add_ipv4_addr_subnet<
SC: BufferIpDeviceContext<Ipv4, C, EmptyBuf>,
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
addr_sub: AddrSubnet<Ipv4Addr>,
) -> Result<(), ExistsError> {
sync_ctx.get_ip_device_state_mut(device_id).ip_state.add_addr(addr_sub).map(|()| {
ctx.on_event(IpDeviceEvent::AddressAdded {
device: device_id,
addr: addr_sub,
state: IpAddressState::Assigned,
})
})
}
/// Adds an IPv6 address (with duplicate address detection) and associated
/// subnet to this device and joins the address's solicited-node multicast
/// group.
///
/// `config` is the way this address is being configured. See [`AddrConfig`]
/// for more details.
pub(crate) fn add_ipv6_addr_subnet<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + DadHandler<C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
addr_sub: AddrSubnet<Ipv6Addr>,
config: AddrConfig<C::Instant>,
) -> Result<(), ExistsError> {
let Ipv6DeviceState {
ref mut ip_state,
config:
Ipv6DeviceConfiguration {
dad_transmits,
max_router_solicitations: _,
slaac_config: _,
ip_config: IpDeviceConfiguration { ip_enabled, gmp_enabled: _ },
},
retrans_timer: _,
router_soliciations_remaining: _,
route_discovery: _,
} = sync_ctx.get_ip_device_state_mut(device_id);
let ip_enabled = *ip_enabled;
let addr_sub = addr_sub.to_unicast();
ip_state
.add_addr(Ipv6AddressEntry::new(
addr_sub,
AddressState::Tentative { dad_transmits_remaining: *dad_transmits },
config,
))
.map(|()| {
// As per RFC 4861 section 5.6.2,
//
// Before sending a Neighbor Solicitation, an interface MUST join
// the all-nodes multicast address and the solicited-node
// multicast address of the tentative address.
//
// Note that we join the all-nodes multicast address on interface
// enable.
join_ip_multicast(
sync_ctx,
ctx,
device_id,
addr_sub.addr().to_solicited_node_address(),
);
ctx.on_event(IpDeviceEvent::AddressAdded {
device: device_id,
addr: addr_sub.to_witness(),
state: IpAddressState::Tentative,
});
// NB: We don't start DAD if the device is disabled. DAD will be
// performed when the device is enabled for all addressed.
if ip_enabled {
DadHandler::do_duplicate_address_detection(
sync_ctx,
ctx,
device_id,
addr_sub.addr(),
);
}
})
}
/// Removes an IPv4 address and associated subnet from this device.
pub(crate) fn del_ipv4_addr<
SC: BufferIpDeviceContext<Ipv4, C, EmptyBuf>,
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
addr: &SpecifiedAddr<Ipv4Addr>,
) -> Result<(), NotFoundError> {
sync_ctx.get_ip_device_state_mut(device_id).ip_state.remove_addr(&addr).map(|addr| {
ctx.on_event(IpDeviceEvent::AddressRemoved { device: device_id, addr: *addr.addr() })
})
}
/// Removes an IPv6 address and associated subnet from this device.
pub(crate) fn del_ipv6_addr_with_reason<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C> + GmpHandler<Ipv6, C> + DadHandler<C> + SlaacHandler<C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
addr: &SpecifiedAddr<Ipv6Addr>,
reason: DelIpv6AddrReason,
) -> Result<(), NotFoundError> {
let Ipv6AddressEntry { addr_sub, state: _, config, deprecated: _ } =
sync_ctx.get_ip_device_state_mut(device_id).ip_state.remove_addr(&addr)?;
let addr = addr_sub.addr();
DadHandler::stop_duplicate_address_detection(sync_ctx, ctx, device_id, addr);
leave_ip_multicast(sync_ctx, ctx, device_id, addr.to_solicited_node_address());
match config {
AddrConfig::Slaac(s) => {
SlaacHandler::on_address_removed(sync_ctx, ctx, device_id, addr_sub, s, reason)
}
AddrConfig::Manual => {}
}
ctx.on_event(IpDeviceEvent::AddressRemoved { device: device_id, addr: *addr_sub.addr() });
Ok(())
}
/// Sends an IP packet through the device.
pub(crate) fn send_ip_frame<
I: IpDeviceIpExt<C::Instant, SC::DeviceId>,
C: IpDeviceNonSyncContext<I, SC::DeviceId>,
SC: BufferIpDeviceContext<I, C, B>,
B: BufferMut,
S: Serializer<Buffer = B>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
local_addr: SpecifiedAddr<I::Addr>,
body: S,
) -> Result<(), S> {
is_ip_device_enabled(sync_ctx, device_id)
.then(|| sync_ctx.send_ip_frame(ctx, device_id, local_addr, body))
.unwrap_or(Ok(()))
}
pub(crate) fn get_ipv4_configuration<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> Ipv4DeviceConfiguration {
sync_ctx.get_ip_device_state(device_id).config.clone()
}
pub(crate) fn get_ipv6_configuration<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpDeviceContext<Ipv6, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> Ipv6DeviceConfiguration {
sync_ctx.get_ip_device_state(device_id).config.clone()
}
/// Updates the IPv4 Configuration for the device.
pub(crate) fn update_ipv4_configuration<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpDeviceContext<Ipv4, C> + GmpHandler<Ipv4, C>,
F: FnOnce(&mut Ipv4DeviceConfiguration),
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
update_cb: F,
) {
let config = &mut sync_ctx.get_ip_device_state_mut(device_id).config;
let Ipv4DeviceConfiguration {
ip_config:
IpDeviceConfiguration { ip_enabled: prev_ip_enabled, gmp_enabled: prev_gmp_enabled },
} = *config;
update_cb(config);
let Ipv4DeviceConfiguration {
ip_config:
IpDeviceConfiguration { ip_enabled: next_ip_enabled, gmp_enabled: next_gmp_enabled },
} = *config;
if !prev_ip_enabled && next_ip_enabled {
enable_ipv4_device(sync_ctx, ctx, device_id);
} else if prev_ip_enabled && !next_ip_enabled {
disable_ipv4_device(sync_ctx, ctx, device_id);
}
if !prev_gmp_enabled && next_gmp_enabled {
GmpHandler::gmp_handle_maybe_enabled(sync_ctx, ctx, device_id);
} else if prev_gmp_enabled && !next_gmp_enabled {
GmpHandler::gmp_handle_disabled(sync_ctx, ctx, device_id);
}
}
pub(super) fn is_ip_device_enabled<
I: IpDeviceIpExt<C::Instant, SC::DeviceId>,
C: IpDeviceNonSyncContext<I, SC::DeviceId>,
SC: IpDeviceContext<I, C>,
>(
sync_ctx: &SC,
device_id: SC::DeviceId,
) -> bool {
AsRef::<IpDeviceConfiguration>::as_ref(sync_ctx.get_ip_device_state(device_id)).ip_enabled
}
/// Updates the IPv6 Configuration for the device.
pub(crate) fn update_ipv6_configuration<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId>,
SC: Ipv6DeviceContext<C>
+ GmpHandler<Ipv6, C>
+ RsHandler<C>
+ DadHandler<C>
+ RouteDiscoveryHandler<C>
+ SlaacHandler<C>,
F: FnOnce(&mut Ipv6DeviceConfiguration),
>(
sync_ctx: &mut SC,
ctx: &mut C,
device_id: SC::DeviceId,
update_cb: F,
) {
let config = &mut sync_ctx.get_ip_device_state_mut(device_id).config;
let Ipv6DeviceConfiguration {
dad_transmits: _,
max_router_solicitations: _,
slaac_config: _,
ip_config:
IpDeviceConfiguration { ip_enabled: prev_ip_enabled, gmp_enabled: prev_gmp_enabled },
} = *config;
update_cb(config);
let Ipv6DeviceConfiguration {
dad_transmits: _,
max_router_solicitations: _,
slaac_config: _,
ip_config:
IpDeviceConfiguration { ip_enabled: next_ip_enabled, gmp_enabled: next_gmp_enabled },
} = *config;
if !prev_ip_enabled && next_ip_enabled {
enable_ipv6_device(sync_ctx, ctx, device_id);
} else if prev_ip_enabled && !next_ip_enabled {
disable_ipv6_device(sync_ctx, ctx, device_id);
}
if !prev_gmp_enabled && next_gmp_enabled {
GmpHandler::gmp_handle_maybe_enabled(sync_ctx, ctx, device_id);
} else if prev_gmp_enabled && !next_gmp_enabled {
GmpHandler::gmp_handle_disabled(sync_ctx, ctx, device_id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use fakealloc::collections::HashSet;
use net_types::ip::Ipv6;
use crate::{
ip::gmp::GmpDelayedReportTimerId,
testutil::{assert_empty, DummyCtx, DummyNonSyncCtx, DummySyncCtx, TestIpExt as _},
Ctx, StackStateBuilder, TimerId, TimerIdInner,
};
#[test]
fn enable_disable_ipv6() {
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
Ctx::new(StackStateBuilder::default().build());
non_sync_ctx.timer_ctx().assert_no_timers_installed();
let local_mac = Ipv6::DUMMY_CONFIG.local_mac;
let device_id =
sync_ctx.state.device.add_ethernet_device(local_mac, Ipv6::MINIMUM_LINK_MTU.into());
update_ipv6_configuration(&mut sync_ctx, &mut non_sync_ctx, device_id, |config| {
config.ip_config.gmp_enabled = true;
// Doesn't matter as long as we perform DAD and router
// solicitation.
config.dad_transmits = NonZeroU8::new(1);
config.max_router_solicitations = NonZeroU8::new(1);
});
non_sync_ctx.timer_ctx().assert_no_timers_installed();
let ll_addr = local_mac.to_ipv6_link_local();
// Enable the device and observe an auto-generated link-local address,
// router solicitation and DAD for the auto-generated address.
let test_enable_device =
|sync_ctx: &mut DummySyncCtx,
non_sync_ctx: &mut DummyNonSyncCtx,
extra_group: Option<MulticastAddr<Ipv6Addr>>| {
update_ipv6_configuration(sync_ctx, non_sync_ctx, device_id, |config| {
config.ip_config.ip_enabled = true;
});
assert_eq!(
IpDeviceContext::<Ipv6, _>::get_ip_device_state(sync_ctx, device_id)
.ip_state
.iter_addrs()
.map(|Ipv6AddressEntry { addr_sub, state: _, config: _, deprecated: _ }| {
addr_sub.ipv6_unicast_addr()
})
.collect::<HashSet<_>>(),
HashSet::from([ll_addr.ipv6_unicast_addr()]),
"enabled device expected to generate link-local address"
);
let mut timers = vec![
(
TimerId(TimerIdInner::Ipv6Device(Ipv6DeviceTimerId::Rs(RsTimerId {
device_id,
}))),
..,
),
(
TimerId(TimerIdInner::Ipv6Device(Ipv6DeviceTimerId::Dad(DadTimerId {
device_id,
addr: ll_addr.ipv6_unicast_addr(),
}))),
..,
),
(
TimerId(TimerIdInner::Ipv6Device(Ipv6DeviceTimerId::Mld(
MldDelayedReportTimerId(GmpDelayedReportTimerId {
device: device_id,
group_addr: local_mac
.to_ipv6_link_local()
.addr()
.to_solicited_node_address(),
})
.into(),
))),
..,
),
];
if let Some(group_addr) = extra_group {
timers.push((
TimerId(TimerIdInner::Ipv6Device(Ipv6DeviceTimerId::Mld(
MldDelayedReportTimerId(GmpDelayedReportTimerId {
device: device_id,
group_addr,
})
.into(),
))),
..,
))
}
non_sync_ctx.timer_ctx().assert_timers_installed(timers);
};
test_enable_device(&mut sync_ctx, &mut non_sync_ctx, None);
let test_disable_device =
|sync_ctx: &mut DummySyncCtx, non_sync_ctx: &mut DummyNonSyncCtx| {
update_ipv6_configuration(sync_ctx, non_sync_ctx, device_id, |config| {
config.ip_config.ip_enabled = false;
});
non_sync_ctx.timer_ctx().assert_no_timers_installed();
};
test_disable_device(&mut sync_ctx, &mut non_sync_ctx);
assert_empty(
IpDeviceContext::<Ipv6, _>::get_ip_device_state(&sync_ctx, device_id)
.ip_state
.iter_addrs(),
);
let multicast_addr = Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS;
join_ip_multicast::<Ipv6, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device_id,
multicast_addr,
);
add_ipv6_addr_subnet(
&mut sync_ctx,
&mut non_sync_ctx,
device_id,
ll_addr.to_witness(),
AddrConfig::Manual,
)
.expect("add MAC based IPv6 link-local address");
assert_eq!(
IpDeviceContext::<Ipv6, _>::get_ip_device_state(&sync_ctx, device_id)
.ip_state
.iter_addrs()
.map(|Ipv6AddressEntry { addr_sub, state: _, config: _, deprecated: _ }| {
addr_sub.ipv6_unicast_addr()
})
.collect::<HashSet<_>>(),
HashSet::from([ll_addr.ipv6_unicast_addr()])
);
test_enable_device(&mut sync_ctx, &mut non_sync_ctx, Some(multicast_addr));
test_disable_device(&mut sync_ctx, &mut non_sync_ctx);
assert_eq!(
IpDeviceContext::<Ipv6, _>::get_ip_device_state(&sync_ctx, device_id)
.ip_state
.iter_addrs()
.map(|Ipv6AddressEntry { addr_sub, state: _, config: _, deprecated: _ }| {
addr_sub.ipv6_unicast_addr()
})
.collect::<HashSet<_>>(),
HashSet::from([ll_addr.ipv6_unicast_addr()]),
"manual addresses should not be removed on device disable"
);
leave_ip_multicast::<Ipv6, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device_id,
multicast_addr,
);
test_enable_device(&mut sync_ctx, &mut non_sync_ctx, None);
}
}