use alloc::{collections::HashMap, vec::Vec};
use core::{
fmt::{Debug, Display},
marker::PhantomData,
num::NonZeroU64,
};
use derivative::Derivative;
use lock_order::{lock::UnlockedAccess, wrap::prelude::*};
use net_types::{
ethernet::Mac,
ip::{Ip, IpVersion, Ipv4, Ipv6},
BroadcastAddr, MulticastAddr,
};
use packet::Buf;
use crate::{
context::{CounterContext, InstantContext, TimerBindingsTypes, TimerHandler},
counters::Counter,
device::{
arp::ArpCounters,
ethernet::{EthernetLinkDevice, EthernetTimerId},
id::{
BaseDeviceId, BasePrimaryDeviceId, DeviceId, EthernetDeviceId, EthernetPrimaryDeviceId,
EthernetWeakDeviceId, StrongId, WeakId,
},
loopback::{LoopbackDeviceId, LoopbackPrimaryDeviceId},
pure_ip::{PureIpDeviceId, PureIpPrimaryDeviceId},
socket::{self, HeldSockets},
state::{DeviceStateSpec, IpLinkDeviceStateInner},
},
filter::FilterBindingsTypes,
inspect::Inspectable,
ip::{
device::{
nud::{LinkResolutionContext, NudCounters},
state::IpDeviceFlags,
IpDeviceIpExt, IpDeviceStateContext,
},
forwarding::IpForwardingDeviceContext,
types::RawMetric,
},
sync::RwLock,
BindingsContext, CoreCtx, Inspector, StackState,
};
pub trait Device: 'static {}
pub enum AnyDevice {}
impl Device for AnyDevice {}
pub trait DeviceIdContext<D: Device> {
type DeviceId: StrongId<Weak = Self::WeakDeviceId> + 'static;
type WeakDeviceId: WeakId<Strong = Self::DeviceId> + 'static;
}
pub trait DeviceIdAnyCompatContext<D: Device>:
DeviceIdContext<D>
+ DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId_, WeakDeviceId = Self::WeakDeviceId_>
{
type DeviceId_: StrongId<Weak = Self::WeakDeviceId_>
+ From<<Self as DeviceIdContext<D>>::DeviceId>;
type WeakDeviceId_: WeakId<Strong = Self::DeviceId_>
+ From<<Self as DeviceIdContext<D>>::WeakDeviceId>;
}
impl<CC, D> DeviceIdAnyCompatContext<D> for CC
where
D: Device,
CC: DeviceIdContext<D> + DeviceIdContext<AnyDevice>,
<CC as DeviceIdContext<AnyDevice>>::WeakDeviceId:
From<<CC as DeviceIdContext<D>>::WeakDeviceId>,
<CC as DeviceIdContext<AnyDevice>>::DeviceId: From<<CC as DeviceIdContext<D>>::DeviceId>,
{
type DeviceId_ = <CC as DeviceIdContext<AnyDevice>>::DeviceId;
type WeakDeviceId_ = <CC as DeviceIdContext<AnyDevice>>::WeakDeviceId;
}
pub(super) struct RecvIpFrameMeta<D, I: Ip> {
pub(super) device: D,
pub(super) frame_dst: Option<FrameDestination>,
pub(super) _marker: PhantomData<I>,
}
impl<D, I: Ip> RecvIpFrameMeta<D, I> {
pub(super) fn new(device: D, frame_dst: Option<FrameDestination>) -> RecvIpFrameMeta<D, I> {
RecvIpFrameMeta { device, frame_dst, _marker: PhantomData }
}
}
pub struct DevicesIter<'s, BC: BindingsContext> {
pub(super) ethernet:
alloc::collections::hash_map::Values<'s, EthernetDeviceId<BC>, EthernetPrimaryDeviceId<BC>>,
pub(super) pure_ip:
alloc::collections::hash_map::Values<'s, PureIpDeviceId<BC>, PureIpPrimaryDeviceId<BC>>,
pub(super) loopback: core::option::Iter<'s, LoopbackPrimaryDeviceId<BC>>,
}
impl<'s, BC: BindingsContext> Iterator for DevicesIter<'s, BC> {
type Item = DeviceId<BC>;
fn next(&mut self) -> Option<Self::Item> {
let Self { ethernet, pure_ip, loopback } = self;
ethernet
.map(|primary| primary.clone_strong().into())
.chain(pure_ip.map(|primary| primary.clone_strong().into()))
.chain(loopback.map(|primary| primary.clone_strong().into()))
.next()
}
}
impl<I: IpDeviceIpExt, BC: BindingsContext, L> IpForwardingDeviceContext<I> for CoreCtx<'_, BC, L>
where
Self: IpDeviceStateContext<I, BC, DeviceId = DeviceId<BC>>,
{
fn get_routing_metric(&mut self, device_id: &Self::DeviceId) -> RawMetric {
crate::device::integration::with_ip_device_state(self, device_id, |state| {
*state.unlocked_access::<crate::lock_ordering::RoutingMetric>()
})
}
fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool {
IpDeviceStateContext::<I, _>::with_ip_device_flags(
self,
device_id,
|IpDeviceFlags { ip_enabled }| *ip_enabled,
)
}
}
pub enum Ipv6DeviceLinkLayerAddr {
Mac(Mac),
}
impl AsRef<[u8]> for Ipv6DeviceLinkLayerAddr {
fn as_ref(&self) -> &[u8] {
match self {
Ipv6DeviceLinkLayerAddr::Mac(a) => a.as_ref(),
}
}
}
#[derive(Derivative)]
#[derivative(
Clone(bound = ""),
Eq(bound = ""),
PartialEq(bound = ""),
Hash(bound = ""),
Debug(bound = "")
)]
pub(crate) struct DeviceLayerTimerId<BT: DeviceLayerTypes>(DeviceLayerTimerIdInner<BT>);
#[derive(Derivative)]
#[derivative(
Clone(bound = ""),
Eq(bound = ""),
PartialEq(bound = ""),
Hash(bound = ""),
Debug(bound = "")
)]
enum DeviceLayerTimerIdInner<BT: DeviceLayerTypes> {
Ethernet(EthernetTimerId<EthernetWeakDeviceId<BT>>),
}
impl<BT: DeviceLayerTypes> From<EthernetTimerId<EthernetWeakDeviceId<BT>>>
for DeviceLayerTimerId<BT>
{
fn from(id: EthernetTimerId<EthernetWeakDeviceId<BT>>) -> DeviceLayerTimerId<BT> {
DeviceLayerTimerId(DeviceLayerTimerIdInner::Ethernet(id))
}
}
impl<CC, BT> TimerHandler<BT, DeviceLayerTimerId<BT>> for CC
where
BT: DeviceLayerTypes,
CC: TimerHandler<BT, EthernetTimerId<EthernetWeakDeviceId<BT>>>,
{
fn handle_timer(
&mut self,
bindings_ctx: &mut BT,
DeviceLayerTimerId(id): DeviceLayerTimerId<BT>,
) {
match id {
DeviceLayerTimerIdInner::Ethernet(id) => self.handle_timer(bindings_ctx, id),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FrameDestination {
Individual {
local: bool,
},
Multicast,
Broadcast,
}
impl FrameDestination {
pub(crate) fn is_broadcast(self) -> bool {
self == FrameDestination::Broadcast
}
pub(crate) fn from_dest(destination: Mac, local_mac: Mac) -> Self {
BroadcastAddr::new(destination)
.map(Into::into)
.or_else(|| MulticastAddr::new(destination).map(Into::into))
.unwrap_or_else(|| FrameDestination::Individual { local: destination == local_mac })
}
}
impl From<BroadcastAddr<Mac>> for FrameDestination {
fn from(_value: BroadcastAddr<Mac>) -> Self {
Self::Broadcast
}
}
impl From<MulticastAddr<Mac>> for FrameDestination {
fn from(_value: MulticastAddr<Mac>) -> Self {
Self::Multicast
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct Devices<BT: DeviceLayerTypes> {
pub(super) ethernet: HashMap<EthernetDeviceId<BT>, EthernetPrimaryDeviceId<BT>>,
pub(super) pure_ip: HashMap<PureIpDeviceId<BT>, PureIpPrimaryDeviceId<BT>>,
pub(super) loopback: Option<LoopbackPrimaryDeviceId<BT>>,
}
pub(crate) struct DeviceLayerState<BT: DeviceLayerTypes> {
pub(super) devices: RwLock<Devices<BT>>,
pub(super) origin: OriginTracker,
pub(super) shared_sockets: HeldSockets<BT>,
pub(super) counters: DeviceCounters,
pub(super) ethernet_counters: EthernetDeviceCounters,
pub(super) pure_ip_counters: PureIpDeviceCounters,
pub(super) nud_v4_counters: NudCounters<Ipv4>,
pub(super) nud_v6_counters: NudCounters<Ipv6>,
pub(super) arp_counters: ArpCounters,
}
impl<BT: DeviceLayerTypes> DeviceLayerState<BT> {
pub(crate) fn counters(&self) -> &DeviceCounters {
&self.counters
}
pub(crate) fn ethernet_counters(&self) -> &EthernetDeviceCounters {
&self.ethernet_counters
}
pub(crate) fn pure_ip_counters(&self) -> &PureIpDeviceCounters {
&self.pure_ip_counters
}
pub(crate) fn nud_counters<I: Ip>(&self) -> &NudCounters<I> {
I::map_ip((), |()| &self.nud_v4_counters, |()| &self.nud_v6_counters)
}
pub(crate) fn arp_counters(&self) -> &ArpCounters {
&self.arp_counters
}
}
#[derive(Default)]
pub struct EthernetDeviceCounters {
pub recv_ethernet_other_dest: Counter,
pub recv_unsupported_ethertype: Counter,
pub recv_no_ethertype: Counter,
}
impl Inspectable for EthernetDeviceCounters {
fn record<I: Inspector>(&self, inspector: &mut I) {
inspector.record_child("Ethernet", |inspector| {
crate::counters::inspect_ethernet_device_counters(inspector, self)
})
}
}
#[derive(Default)]
pub struct PureIpDeviceCounters {}
impl Inspectable for PureIpDeviceCounters {
fn record<I: Inspector>(&self, _inspector: &mut I) {}
}
#[derive(Default)]
pub struct DeviceCounters {
pub send_total_frames: Counter,
pub send_frame: Counter,
pub send_queue_full: Counter,
pub send_serialize_error: Counter,
pub recv_frame: Counter,
pub recv_parse_error: Counter,
pub recv_ipv4_delivered: Counter,
pub recv_ipv6_delivered: Counter,
pub send_ipv4_frame: Counter,
pub send_ipv6_frame: Counter,
pub send_dropped_no_queue: Counter,
}
impl Inspectable for DeviceCounters {
fn record<I: Inspector>(&self, inspector: &mut I) {
crate::counters::inspect_device_counters(inspector, self)
}
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::DeviceCounters> for StackState<BC> {
type Data = DeviceCounters;
type Guard<'l> = &'l DeviceCounters where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
self.device_counters()
}
}
impl<T, BC: BindingsContext> UnlockedAccess<crate::lock_ordering::DeviceCounters>
for IpLinkDeviceStateInner<T, BC>
{
type Data = DeviceCounters;
type Guard<'l> = &'l DeviceCounters where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
&self.counters
}
}
impl<BC: BindingsContext, L> CounterContext<DeviceCounters> for CoreCtx<'_, BC, L> {
fn with_counters<O, F: FnOnce(&DeviceCounters) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::DeviceCounters>())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OriginTracker(#[cfg(debug_assertions)] u64);
impl OriginTracker {
#[cfg_attr(not(debug_assertions), inline)]
fn new() -> Self {
Self(
#[cfg(debug_assertions)]
{
static COUNTER: core::sync::atomic::AtomicU64 =
core::sync::atomic::AtomicU64::new(0);
COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
},
)
}
}
pub trait OriginTrackerContext {
fn origin_tracker(&mut self) -> OriginTracker;
}
pub trait DeviceCollectionContext<D: Device + DeviceStateSpec, BT: DeviceLayerTypes>:
DeviceIdContext<D>
{
fn insert(&mut self, device: BasePrimaryDeviceId<D, BT>);
fn remove(&mut self, device: &BaseDeviceId<D, BT>) -> Option<BasePrimaryDeviceId<D, BT>>;
}
pub trait DeviceReceiveFrameSpec {
type FrameMetadata<D>;
}
impl<BC: DeviceLayerTypes + socket::DeviceSocketBindingsContext<DeviceId<BC>>>
DeviceLayerState<BC>
{
pub(crate) fn new() -> Self {
Self {
devices: Default::default(),
origin: OriginTracker::new(),
shared_sockets: Default::default(),
counters: Default::default(),
ethernet_counters: EthernetDeviceCounters::default(),
pure_ip_counters: PureIpDeviceCounters::default(),
nud_v4_counters: Default::default(),
nud_v6_counters: Default::default(),
arp_counters: Default::default(),
}
}
}
pub trait DeviceLayerStateTypes: InstantContext + FilterBindingsTypes {
type LoopbackDeviceState: Send + Sync + DeviceClassMatcher<Self::DeviceClass>;
type EthernetDeviceState: Send + Sync + DeviceClassMatcher<Self::DeviceClass>;
type PureIpDeviceState: Send + Sync + DeviceClassMatcher<Self::DeviceClass>;
type DeviceIdentifier: Send + Sync + Debug + Display + DeviceIdAndNameMatcher;
}
pub trait DeviceClassMatcher<DeviceClass> {
fn device_class_matches(&self, device_class: &DeviceClass) -> bool;
}
pub trait DeviceIdAndNameMatcher {
fn id_matches(&self, id: &NonZeroU64) -> bool;
fn name_matches(&self, name: &str) -> bool;
}
pub trait DeviceLayerTypes:
DeviceLayerStateTypes
+ socket::DeviceSocketTypes
+ LinkResolutionContext<EthernetLinkDevice>
+ TimerBindingsTypes
+ 'static
{
}
impl<
BC: DeviceLayerStateTypes
+ socket::DeviceSocketTypes
+ LinkResolutionContext<EthernetLinkDevice>
+ TimerBindingsTypes
+ 'static,
> DeviceLayerTypes for BC
{
}
pub trait DeviceLayerEventDispatcher: DeviceLayerTypes + Sized {
fn wake_rx_task(&mut self, device: &LoopbackDeviceId<Self>);
fn wake_tx_task(&mut self, device: &DeviceId<Self>);
fn send_ethernet_frame(
&mut self,
device: &EthernetDeviceId<Self>,
frame: Buf<Vec<u8>>,
) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>>;
fn send_ip_packet(
&mut self,
device: &PureIpDeviceId<Self>,
packet: Buf<Vec<u8>>,
ip_version: IpVersion,
) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>>;
}
#[derive(Debug, PartialEq, Eq)]
pub enum DeviceSendFrameError<T> {
DeviceNotReady(T),
}
#[cfg(any(test, feature = "testutils"))]
pub(crate) mod testutil {
use super::*;
#[cfg(test)]
use alloc::sync::Arc;
#[cfg(test)]
use core::sync::atomic::AtomicBool;
use crate::ip::device::config::{
IpDeviceConfigurationUpdate, Ipv4DeviceConfigurationUpdate, Ipv6DeviceConfigurationUpdate,
};
#[cfg(test)]
use crate::testutil::Ctx;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct FakeWeakDeviceId<D>(pub(crate) D);
impl<D: PartialEq> PartialEq<D> for FakeWeakDeviceId<D> {
fn eq(&self, other: &D) -> bool {
let Self(this) = self;
this == other
}
}
impl<D: FakeStrongDeviceId> WeakId for FakeWeakDeviceId<D> {
type Strong = D;
fn upgrade(&self) -> Option<D> {
let Self(inner) = self;
inner.is_alive().then(|| inner.clone())
}
}
impl<D: crate::device::Id> crate::device::Id for FakeWeakDeviceId<D> {
fn is_loopback(&self) -> bool {
let Self(inner) = self;
inner.is_loopback()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub(crate) struct FakeDeviceId;
impl StrongId for FakeDeviceId {
type Weak = FakeWeakDeviceId<Self>;
fn downgrade(&self) -> Self::Weak {
FakeWeakDeviceId(self.clone())
}
}
impl crate::device::Id for FakeDeviceId {
fn is_loopback(&self) -> bool {
false
}
}
impl crate::filter::InterfaceProperties<()> for FakeDeviceId {
fn id_matches(&self, _: &core::num::NonZeroU64) -> bool {
unimplemented!()
}
fn name_matches(&self, _: &str) -> bool {
unimplemented!()
}
fn device_class_matches(&self, _: &()) -> bool {
unimplemented!()
}
}
impl FakeStrongDeviceId for FakeDeviceId {
fn is_alive(&self) -> bool {
true
}
}
#[derive(Clone, Debug, Default)]
#[cfg(test)]
pub(crate) struct FakeReferencyDeviceId {
removed: Arc<AtomicBool>,
}
#[cfg(test)]
impl core::hash::Hash for FakeReferencyDeviceId {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let Self { removed } = self;
core::ptr::hash(alloc::sync::Arc::as_ptr(removed), state)
}
}
#[cfg(test)]
impl core::cmp::Eq for FakeReferencyDeviceId {}
#[cfg(test)]
impl core::cmp::PartialEq for FakeReferencyDeviceId {
fn eq(&self, Self { removed: other }: &Self) -> bool {
let Self { removed } = self;
alloc::sync::Arc::ptr_eq(removed, other)
}
}
#[cfg(test)]
impl core::cmp::Ord for FakeReferencyDeviceId {
fn cmp(&self, Self { removed: other }: &Self) -> core::cmp::Ordering {
let Self { removed } = self;
alloc::sync::Arc::as_ptr(removed).cmp(&alloc::sync::Arc::as_ptr(other))
}
}
#[cfg(test)]
impl core::cmp::PartialOrd for FakeReferencyDeviceId {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
impl FakeReferencyDeviceId {
pub(crate) fn mark_removed(&self) {
self.removed.store(true, core::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(test)]
impl StrongId for FakeReferencyDeviceId {
type Weak = FakeWeakDeviceId<Self>;
fn downgrade(&self) -> Self::Weak {
FakeWeakDeviceId(self.clone())
}
}
#[cfg(test)]
impl crate::device::Id for FakeReferencyDeviceId {
fn is_loopback(&self) -> bool {
false
}
}
#[cfg(test)]
impl crate::filter::InterfaceProperties<()> for FakeReferencyDeviceId {
fn id_matches(&self, _: &core::num::NonZeroU64) -> bool {
unimplemented!()
}
fn name_matches(&self, _: &str) -> bool {
unimplemented!()
}
fn device_class_matches(&self, _: &()) -> bool {
unimplemented!()
}
}
#[cfg(test)]
impl FakeStrongDeviceId for FakeReferencyDeviceId {
fn is_alive(&self) -> bool {
!self.removed.load(core::sync::atomic::Ordering::Relaxed)
}
}
pub trait FakeStrongDeviceId: StrongId<Weak = FakeWeakDeviceId<Self>> + 'static + Ord {
fn is_alive(&self) -> bool;
}
pub fn enable_device<BC: BindingsContext>(
ctx: &mut crate::testutil::Ctx<BC>,
device: &DeviceId<BC>,
) {
let ip_config =
IpDeviceConfigurationUpdate { ip_enabled: Some(true), ..Default::default() };
let _: Ipv4DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv4>()
.update_configuration(
device,
Ipv4DeviceConfigurationUpdate { ip_config, ..Default::default() },
)
.unwrap();
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
device,
Ipv6DeviceConfigurationUpdate { ip_config, ..Default::default() },
)
.unwrap();
}
#[cfg(test)]
#[netstack3_macros::context_ip_bounds(I, BC, crate)]
pub(crate) fn set_forwarding_enabled<BC: BindingsContext, I: crate::IpExt>(
ctx: &mut Ctx<BC>,
device: &DeviceId<BC>,
enabled: bool,
) {
let _config = ctx
.core_api()
.device_ip::<I>()
.update_configuration(
device,
IpDeviceConfigurationUpdate {
forwarding_enabled: Some(enabled),
..Default::default()
}
.into(),
)
.unwrap();
}
#[cfg(test)]
#[netstack3_macros::context_ip_bounds(I, BC, crate)]
pub(crate) fn is_forwarding_enabled<BC: BindingsContext, I: crate::IpExt>(
ctx: &mut Ctx<BC>,
device: &DeviceId<BC>,
) -> bool {
let configuration = ctx.core_api().device_ip::<I>().get_configuration(device);
let crate::ip::device::state::IpDeviceConfiguration { forwarding_enabled, .. } =
configuration.as_ref();
*forwarding_enabled
}
#[cfg(test)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
pub(crate) enum MultipleDevicesId {
A,
B,
C,
}
#[cfg(test)]
impl MultipleDevicesId {
pub(crate) fn all() -> [Self; 3] {
[Self::A, Self::B, Self::C]
}
}
#[cfg(test)]
impl crate::device::Id for MultipleDevicesId {
fn is_loopback(&self) -> bool {
false
}
}
#[cfg(test)]
impl StrongId for MultipleDevicesId {
type Weak = FakeWeakDeviceId<Self>;
fn downgrade(&self) -> Self::Weak {
FakeWeakDeviceId(self.clone())
}
}
#[cfg(test)]
impl FakeStrongDeviceId for MultipleDevicesId {
fn is_alive(&self) -> bool {
true
}
}
#[cfg(test)]
impl crate::filter::InterfaceProperties<()> for MultipleDevicesId {
fn id_matches(&self, _: &core::num::NonZeroU64) -> bool {
unimplemented!()
}
fn name_matches(&self, _: &str) -> bool {
unimplemented!()
}
fn device_class_matches(&self, _: &()) -> bool {
unimplemented!()
}
}
}
#[cfg(test)]
mod tests {
use core::{
num::{NonZeroU16, NonZeroU8},
time::Duration,
};
use const_unwrap::const_unwrap_option;
use net_declare::net_mac;
use net_types::{
ip::{AddrSubnet, Mtu},
SpecifiedAddr, UnicastAddr, Witness as _,
};
use test_case::test_case;
use super::*;
use crate::{
context::testutil::FakeInstant,
device::{
ethernet::{EthernetCreationProperties, MaxEthernetFrameSize},
loopback::{LoopbackCreationProperties, LoopbackDevice},
queue::tx::TransmitQueueConfiguration,
DeviceProvider,
},
error, for_any_device_id,
ip::device::{
api::AddIpAddrSubnetError,
config::{
IpDeviceConfigurationUpdate, Ipv4DeviceConfigurationUpdate,
Ipv6DeviceConfigurationUpdate,
},
slaac::SlaacConfiguration,
state::{Ipv4AddrConfig, Ipv6AddrManualConfig, Lifetime},
},
testutil::{TestIpExt, DEFAULT_INTERFACE_METRIC, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE},
work_queue::WorkQueueReport,
};
#[test]
fn test_origin_tracker() {
let tracker = OriginTracker::new();
if cfg!(debug_assertions) {
assert_ne!(tracker, OriginTracker::new());
} else {
assert_eq!(tracker, OriginTracker::new());
}
assert_eq!(tracker.clone(), tracker);
}
#[test]
fn frame_destination_from_dest() {
const LOCAL_ADDR: Mac = net_mac!("88:88:88:88:88:88");
assert_eq!(
FrameDestination::from_dest(
UnicastAddr::new(net_mac!("00:11:22:33:44:55")).unwrap().get(),
LOCAL_ADDR
),
FrameDestination::Individual { local: false }
);
assert_eq!(
FrameDestination::from_dest(LOCAL_ADDR, LOCAL_ADDR),
FrameDestination::Individual { local: true }
);
assert_eq!(
FrameDestination::from_dest(Mac::BROADCAST, LOCAL_ADDR),
FrameDestination::Broadcast,
);
assert_eq!(
FrameDestination::from_dest(
MulticastAddr::new(net_mac!("11:11:11:11:11:11")).unwrap().get(),
LOCAL_ADDR
),
FrameDestination::Multicast
);
}
#[test]
fn test_no_default_routes() {
let mut ctx = crate::testutil::FakeCtx::default();
let _loopback_device: LoopbackDeviceId<_> =
ctx.core_api().device::<LoopbackDevice>().add_device_with_default_state(
LoopbackCreationProperties { mtu: Mtu::new(55) },
DEFAULT_INTERFACE_METRIC,
);
assert_eq!(ctx.core_api().routes_any().get_all_routes(), []);
let _ethernet_device: EthernetDeviceId<_> =
ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state(
EthernetCreationProperties {
mac: UnicastAddr::new(net_mac!("aa:bb:cc:dd:ee:ff")).expect("MAC is unicast"),
max_frame_size: MaxEthernetFrameSize::MIN,
},
DEFAULT_INTERFACE_METRIC,
);
assert_eq!(ctx.core_api().routes_any().get_all_routes(), []);
}
#[test]
fn remove_ethernet_device_disables_timers() {
let mut ctx = crate::testutil::FakeCtx::default();
let ethernet_device =
ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state(
EthernetCreationProperties {
mac: UnicastAddr::new(net_mac!("aa:bb:cc:dd:ee:ff")).expect("MAC is unicast"),
max_frame_size: MaxEthernetFrameSize::from_mtu(Mtu::new(1500)).unwrap(),
},
DEFAULT_INTERFACE_METRIC,
);
{
let device = ethernet_device.clone().into();
let ip_config = IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
gmp_enabled: Some(true),
..Default::default()
};
let _: Ipv4DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv4>()
.update_configuration(&device, ip_config.into())
.unwrap();
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device,
Ipv6DeviceConfigurationUpdate {
max_router_solicitations: Some(Some(const_unwrap_option(NonZeroU8::new(
2,
)))),
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
..Default::default()
}),
ip_config,
..Default::default()
},
)
.unwrap();
}
ctx.core_api().device().remove_device(ethernet_device).into_removed();
assert_eq!(ctx.bindings_ctx.timer_ctx().timers(), &[]);
}
fn add_ethernet(
ctx: &mut crate::testutil::FakeCtx,
) -> DeviceId<crate::testutil::FakeBindingsCtx> {
ctx.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: Ipv6::FAKE_CONFIG.local_mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
)
.into()
}
fn add_loopback(
ctx: &mut crate::testutil::FakeCtx,
) -> DeviceId<crate::testutil::FakeBindingsCtx> {
let device = ctx
.core_api()
.device::<LoopbackDevice>()
.add_device_with_default_state(
LoopbackCreationProperties { mtu: Ipv6::MINIMUM_LINK_MTU },
DEFAULT_INTERFACE_METRIC,
)
.into();
ctx.core_api()
.device_ip::<Ipv6>()
.add_ip_addr_subnet(
&device,
AddrSubnet::from_witness(Ipv6::LOOPBACK_ADDRESS, Ipv6::LOOPBACK_SUBNET.prefix())
.unwrap(),
)
.unwrap();
device
}
fn check_transmitted_ethernet(
bindings_ctx: &mut crate::testutil::FakeBindingsCtx,
_device_id: &DeviceId<crate::testutil::FakeBindingsCtx>,
count: usize,
) {
assert_eq!(bindings_ctx.take_ethernet_frames().len(), count);
}
fn check_transmitted_loopback(
bindings_ctx: &mut crate::testutil::FakeBindingsCtx,
device_id: &DeviceId<crate::testutil::FakeBindingsCtx>,
count: usize,
) {
let rx_available = core::mem::take(&mut bindings_ctx.state_mut().rx_available);
if count == 0 {
assert_eq!(rx_available, <[LoopbackDeviceId::<_>; 0]>::default());
} else {
assert_eq!(
rx_available.into_iter().map(DeviceId::Loopback).collect::<Vec<_>>(),
[device_id.clone()]
);
}
}
#[test_case(add_ethernet, check_transmitted_ethernet, true; "ethernet with queue")]
#[test_case(add_ethernet, check_transmitted_ethernet, false; "ethernet without queue")]
#[test_case(add_loopback, check_transmitted_loopback, true; "loopback with queue")]
#[test_case(add_loopback, check_transmitted_loopback, false; "loopback without queue")]
fn tx_queue(
add_device: fn(&mut crate::testutil::FakeCtx) -> DeviceId<crate::testutil::FakeBindingsCtx>,
check_transmitted: fn(
&mut crate::testutil::FakeBindingsCtx,
&DeviceId<crate::testutil::FakeBindingsCtx>,
usize,
),
with_tx_queue: bool,
) {
let mut ctx = crate::testutil::FakeCtx::default();
let device = add_device(&mut ctx);
if with_tx_queue {
for_any_device_id!(DeviceId, DeviceProvider, D, &device, device => {
ctx.core_api().transmit_queue::<D>()
.set_configuration(device, TransmitQueueConfiguration::Fifo)
})
}
let _: Ipv6DeviceConfigurationUpdate = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(
&device,
Ipv6DeviceConfigurationUpdate {
dad_transmits: Some(Some(const_unwrap_option(NonZeroU16::new(1)))),
slaac_config: Some(SlaacConfiguration {
enable_stable_addresses: true,
..Default::default()
}),
ip_config: IpDeviceConfigurationUpdate {
ip_enabled: Some(true),
..Default::default()
},
..Default::default()
},
)
.unwrap();
if with_tx_queue {
check_transmitted(&mut ctx.bindings_ctx, &device, 0);
assert_eq!(
core::mem::take(&mut ctx.bindings_ctx.state_mut().tx_available),
[device.clone()]
);
let result = for_any_device_id!(
DeviceId, DeviceProvider, D, &device, device => {
ctx.core_api().transmit_queue::<D>().transmit_queued_frames(device)
}
);
assert_eq!(result, Ok(WorkQueueReport::AllDone));
}
check_transmitted(&mut ctx.bindings_ctx, &device, 1);
assert_eq!(ctx.bindings_ctx.state_mut().tx_available, <[DeviceId::<_>; 0]>::default());
for_any_device_id!(
DeviceId,
device,
device => ctx.core_api().device().remove_device(device).into_removed()
)
}
#[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)]
fn test_add_remove_ip_addresses<I: Ip + TestIpExt + crate::IpExt>(
addr_config: Option<I::ManualAddressConfig<FakeInstant>>,
) {
let config = I::FAKE_CONFIG;
let mut ctx = crate::testutil::FakeCtx::default();
let device = ctx
.core_api()
.device::<EthernetLinkDevice>()
.add_device_with_default_state(
EthernetCreationProperties {
mac: config.local_mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
)
.into();
crate::device::testutil::enable_device(&mut ctx, &device);
let ip = I::get_other_ip_address(1).get();
let prefix = config.subnet.prefix();
let addr_subnet = AddrSubnet::new(ip, prefix).unwrap();
let check_contains_addr = |ctx: &mut crate::testutil::FakeCtx| {
ctx.core_api()
.device_ip::<I>()
.get_assigned_ip_addr_subnets(&device)
.contains(&addr_subnet)
};
assert_eq!(check_contains_addr(&mut ctx), false);
ctx.core_api()
.device_ip::<I>()
.add_ip_addr_subnet_with_config(&device, addr_subnet, addr_config.unwrap_or_default())
.unwrap();
assert_eq!(check_contains_addr(&mut ctx), true);
assert_eq!(
ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, addr_subnet),
Err(AddIpAddrSubnetError::Exists),
);
assert_eq!(check_contains_addr(&mut ctx), true);
let wrong_addr_subnet = AddrSubnet::new(ip, prefix - 1).unwrap();
assert_eq!(
ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, wrong_addr_subnet),
Err(AddIpAddrSubnetError::Exists),
);
assert_eq!(check_contains_addr(&mut ctx), true);
let ip = SpecifiedAddr::new(ip).unwrap();
let () = ctx.core_api().device_ip::<I>().del_ip_addr(&device, ip).unwrap();
assert_eq!(check_contains_addr(&mut ctx), false);
assert_eq!(
ctx.core_api().device_ip::<I>().del_ip_addr(&device, ip),
Err(error::NotFoundError),
);
assert_eq!(check_contains_addr(&mut ctx), false);
}
#[test_case(None; "with no AddressConfig specified")]
#[test_case(Some(Ipv4AddrConfig {
valid_until: Lifetime::Finite(FakeInstant::from(Duration::from_secs(1)))
}); "with AddressConfig specified")]
fn test_add_remove_ipv4_addresses(addr_config: Option<Ipv4AddrConfig<FakeInstant>>) {
test_add_remove_ip_addresses::<Ipv4>(addr_config);
}
#[test_case(None; "with no AddressConfig specified")]
#[test_case(Some(Ipv6AddrManualConfig {
valid_until: Lifetime::Finite(FakeInstant::from(Duration::from_secs(1)))
}); "with AddressConfig specified")]
fn test_add_remove_ipv6_addresses(addr_config: Option<Ipv6AddrManualConfig<FakeInstant>>) {
test_add_remove_ip_addresses::<Ipv6>(addr_config);
}
}