blob: 283fb4da69fc4900854d42218d442e3ce534a6b9 [file] [log] [blame]
// Copyright 2024 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.
//! IP Device configuration.
use core::num::{NonZeroU16, NonZeroU8};
use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
use crate::{
device::{AnyDevice, DeviceIdContext},
ip::{
self,
device::{
router_solicitation::RsHandler,
slaac::SlaacConfiguration,
state::{IpDeviceFlags, Ipv4DeviceConfiguration},
IpDeviceBindingsContext, IpDeviceConfigurationContext, IpDeviceEvent, IpDeviceIpExt,
Ipv6DeviceConfigurationContext, WithIpDeviceConfigurationMutInner as _,
WithIpv6DeviceConfigurationMutInner as _,
},
gmp::GmpHandler,
},
};
/// A trait abstracting configuration between IPv4 and IPv6.
///
/// Configuration is different enough between IPv4 and IPv6 that the
/// implementations are completely disjoint. This trait allows us to implement
/// these completely separately but still offer a unified configuration update
/// API.
pub trait IpDeviceConfigurationHandler<I: IpDeviceIpExt, BC>: DeviceIdContext<AnyDevice> {
/// Applies the [`PendingIpDeviceConfigurationUpdate`].
fn apply_configuration(
&mut self,
bindings_ctx: &mut BC,
config: PendingIpDeviceConfigurationUpdate<'_, I, Self::DeviceId>,
) -> I::ConfigurationUpdate;
}
/// An update to IP device configuration.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, GenericOverIp)]
#[generic_over_ip()]
pub struct IpDeviceConfigurationUpdate {
/// A change in IP enabled.
pub ip_enabled: Option<bool>,
/// A change in forwarding enabled.
pub forwarding_enabled: Option<bool>,
/// A change in Group Messaging Protocol (GMP) enabled.
pub gmp_enabled: Option<bool>,
}
/// An update to IPv4 device configuration.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Ipv4DeviceConfigurationUpdate {
/// A change in the IP device configuration.
pub ip_config: IpDeviceConfigurationUpdate,
}
impl From<IpDeviceConfigurationUpdate> for Ipv4DeviceConfigurationUpdate {
fn from(ip_config: IpDeviceConfigurationUpdate) -> Self {
Self { ip_config, ..Default::default() }
}
}
impl AsRef<IpDeviceConfigurationUpdate> for Ipv4DeviceConfigurationUpdate {
fn as_ref(&self) -> &IpDeviceConfigurationUpdate {
&self.ip_config
}
}
/// Errors observed from updating a device's IP configuration.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum UpdateIpConfigurationError {
/// Forwarding is not supported in the target interface.
ForwardingNotSupported,
}
/// A validated and pending IP device configuration update.
///
/// This type is a witness for a valid IP configuration for a device ID `D`.
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
pub struct PendingIpDeviceConfigurationUpdate<'a, I: IpDeviceIpExt, D>(
I::ConfigurationUpdate,
&'a D,
);
impl<'a, I: IpDeviceIpExt, D: crate::device::Id> PendingIpDeviceConfigurationUpdate<'a, I, D> {
/// Creates a new [`PendingIpDeviceConfigurationUpdate`] if `config` is
/// valid for `device`.
pub(crate) fn new(
config: I::ConfigurationUpdate,
device_id: &'a D,
) -> Result<Self, UpdateIpConfigurationError> {
let IpDeviceConfigurationUpdate { ip_enabled: _, gmp_enabled: _, forwarding_enabled } =
config.as_ref();
if device_id.is_loopback() {
if forwarding_enabled.unwrap_or(false) {
return Err(UpdateIpConfigurationError::ForwardingNotSupported);
}
}
Ok(Self(config, device_id))
}
}
impl<CC, BC> IpDeviceConfigurationHandler<Ipv4, BC> for CC
where
CC: IpDeviceConfigurationContext<Ipv4, BC>,
BC: IpDeviceBindingsContext<Ipv4, CC::DeviceId>,
{
fn apply_configuration(
&mut self,
bindings_ctx: &mut BC,
config: PendingIpDeviceConfigurationUpdate<'_, Ipv4, Self::DeviceId>,
) -> Ipv4DeviceConfigurationUpdate {
let PendingIpDeviceConfigurationUpdate(
Ipv4DeviceConfigurationUpdate { ip_config },
device_id,
) = config;
let device_id: &CC::DeviceId = device_id;
// NB: Extracted to prevent deep nesting which breaks rustfmt.
let handle_config_and_flags =
|config: &mut Ipv4DeviceConfiguration, flags: &mut IpDeviceFlags| {
let IpDeviceConfigurationUpdate { ip_enabled, gmp_enabled, forwarding_enabled } =
ip_config;
let ip_enabled_updates =
get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled);
let gmp_enable_updates =
get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled);
let forwarding_enabled_updates = get_prev_next_and_update(
&mut config.ip_config.forwarding_enabled,
forwarding_enabled,
);
(ip_enabled_updates, gmp_enable_updates, forwarding_enabled_updates)
};
self.with_ip_device_configuration_mut(device_id, |mut inner| {
let (ip_enabled_updates, gmp_enabled_updates, forwarding_enabled_updates) =
inner.with_configuration_and_flags_mut(device_id, handle_config_and_flags);
let (config, mut core_ctx) = inner.ip_device_configuration_and_ctx();
let core_ctx = &mut core_ctx;
let ip_enabled = handle_change_and_get_prev(ip_enabled_updates, |next| {
if next {
ip::device::enable_ipv4_device_with_config(
core_ctx,
bindings_ctx,
device_id,
config,
)
} else {
ip::device::disable_ipv4_device_with_config(
core_ctx,
bindings_ctx,
device_id,
config,
)
}
bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
device: device_id.clone(),
ip_enabled: next,
})
});
let gmp_enabled = handle_change_and_get_prev(gmp_enabled_updates, |next| {
if next {
GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
} else {
GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
}
});
let forwarding_enabled = dont_handle_change_and_get_prev(forwarding_enabled_updates);
let ip_config =
IpDeviceConfigurationUpdate { ip_enabled, gmp_enabled, forwarding_enabled };
Ipv4DeviceConfigurationUpdate { ip_config }
})
}
}
/// An update to IPv6 device configuration.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Ipv6DeviceConfigurationUpdate {
/// A change in DAD transmits.
pub dad_transmits: Option<Option<NonZeroU16>>,
/// A change in maximum router solicitations.
pub max_router_solicitations: Option<Option<NonZeroU8>>,
/// A change in SLAAC configuration.
pub slaac_config: Option<SlaacConfiguration>,
/// A change in the IP device configuration.
pub ip_config: IpDeviceConfigurationUpdate,
}
impl From<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
fn from(ip_config: IpDeviceConfigurationUpdate) -> Self {
Self { ip_config, ..Default::default() }
}
}
impl AsRef<IpDeviceConfigurationUpdate> for Ipv6DeviceConfigurationUpdate {
fn as_ref(&self) -> &IpDeviceConfigurationUpdate {
&self.ip_config
}
}
impl<CC, BC> IpDeviceConfigurationHandler<Ipv6, BC> for CC
where
CC: Ipv6DeviceConfigurationContext<BC>,
BC: IpDeviceBindingsContext<Ipv6, CC::DeviceId>,
{
fn apply_configuration(
&mut self,
bindings_ctx: &mut BC,
config: PendingIpDeviceConfigurationUpdate<'_, Ipv6, Self::DeviceId>,
) -> Ipv6DeviceConfigurationUpdate {
let PendingIpDeviceConfigurationUpdate(
Ipv6DeviceConfigurationUpdate {
dad_transmits,
max_router_solicitations,
slaac_config,
ip_config,
},
device_id,
) = config;
self.with_ipv6_device_configuration_mut(device_id, |mut inner| {
let (
dad_transmits_updates,
max_router_solicitations_updates,
slaac_config_updates,
ip_enabled_updates,
gmp_enabled_updates,
forwarding_enabled_updates,
) = inner.with_configuration_and_flags_mut(device_id, |config, flags| {
let IpDeviceConfigurationUpdate { ip_enabled, gmp_enabled, forwarding_enabled } =
ip_config;
(
get_prev_next_and_update(&mut config.dad_transmits, dad_transmits),
get_prev_next_and_update(
&mut config.max_router_solicitations,
max_router_solicitations,
),
get_prev_next_and_update(&mut config.slaac_config, slaac_config),
get_prev_next_and_update(&mut flags.ip_enabled, ip_enabled),
get_prev_next_and_update(&mut config.ip_config.gmp_enabled, gmp_enabled),
get_prev_next_and_update(
&mut config.ip_config.forwarding_enabled,
forwarding_enabled,
),
)
});
let (config, mut core_ctx) = inner.ipv6_device_configuration_and_ctx();
let core_ctx = &mut core_ctx;
let dad_transmits = dont_handle_change_and_get_prev(dad_transmits_updates);
let max_router_solicitations =
dont_handle_change_and_get_prev(max_router_solicitations_updates);
let slaac_config = dont_handle_change_and_get_prev(slaac_config_updates);
let ip_config = IpDeviceConfigurationUpdate {
ip_enabled: handle_change_and_get_prev(ip_enabled_updates, |next| {
if next {
ip::device::enable_ipv6_device_with_config(
core_ctx,
bindings_ctx,
device_id,
config,
)
} else {
ip::device::disable_ipv6_device_with_config(
core_ctx,
bindings_ctx,
device_id,
config,
)
}
bindings_ctx.on_event(IpDeviceEvent::EnabledChanged {
device: device_id.clone(),
ip_enabled: next,
})
}),
gmp_enabled: handle_change_and_get_prev(gmp_enabled_updates, |next| {
if next {
GmpHandler::gmp_handle_maybe_enabled(core_ctx, bindings_ctx, device_id)
} else {
GmpHandler::gmp_handle_disabled(core_ctx, bindings_ctx, device_id)
}
}),
forwarding_enabled: handle_change_and_get_prev(
forwarding_enabled_updates,
|next| {
if next {
RsHandler::stop_router_solicitation(core_ctx, bindings_ctx, device_id);
ip::device::join_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
config,
);
} else {
ip::device::leave_ip_multicast_with_config(
core_ctx,
bindings_ctx,
device_id,
Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
config,
);
RsHandler::start_router_solicitation(core_ctx, bindings_ctx, device_id);
}
},
),
};
Ipv6DeviceConfigurationUpdate {
dad_transmits,
max_router_solicitations,
slaac_config,
ip_config,
}
})
}
}
struct Delta<T> {
prev: T,
next: T,
}
fn get_prev_next_and_update<T: Copy>(field: &mut T, next: Option<T>) -> Option<Delta<T>> {
next.map(|next| Delta { prev: core::mem::replace(field, next), next })
}
fn handle_change_and_get_prev<T: PartialEq>(
delta: Option<Delta<T>>,
f: impl FnOnce(T),
) -> Option<T> {
delta.map(|Delta { prev, next }| {
if prev != next {
f(next)
}
prev
})
}
fn dont_handle_change_and_get_prev<T: PartialEq>(delta: Option<Delta<T>>) -> Option<T> {
handle_change_and_get_prev(delta, |_: T| {})
}