| // 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. |
| |
| //! Device layer api. |
| |
| use alloc::fmt::Debug; |
| use core::marker::PhantomData; |
| |
| use net_types::ip::{Ipv4, Ipv6}; |
| use packet::BufferMut; |
| use tracing::debug; |
| |
| use crate::{ |
| context::{ |
| ContextPair, CoreTimerContext, RecvFrameContext, ReferenceNotifiers, |
| ResourceCounterContext, TimerContext2, |
| }, |
| device::{ |
| config::{ |
| ArpConfiguration, ArpConfigurationUpdate, DeviceConfiguration, |
| DeviceConfigurationContext, DeviceConfigurationUpdate, DeviceConfigurationUpdateError, |
| NdpConfiguration, NdpConfigurationUpdate, |
| }, |
| ethernet::EthernetLinkDevice, |
| loopback::LoopbackDevice, |
| pure_ip::PureIpDevice, |
| state::{BaseDeviceState, DeviceStateSpec, IpLinkDeviceStateInner}, |
| AnyDevice, BaseDeviceId, BasePrimaryDeviceId, BaseWeakDeviceId, Device, |
| DeviceCollectionContext, DeviceCounters, DeviceId, DeviceIdContext, DeviceLayerStateTypes, |
| DeviceLayerTypes, DeviceProvider, DeviceReceiveFrameSpec, OriginTrackerContext, |
| }, |
| for_any_device_id, |
| ip::{ |
| device::{ |
| IpDeviceBindingsContext, IpDeviceConfigurationContext, Ipv6DeviceConfigurationContext, |
| }, |
| types::RawMetric, |
| }, |
| sync::{ |
| types::{RemoveResourceResult, RemoveResourceResultWithContext}, |
| PrimaryRc, |
| }, |
| Inspector, |
| }; |
| |
| /// Pending device configuration update. |
| /// |
| /// This type is a witness for a valid [`DeviceConfigurationUpdate`] for some |
| /// device ID `D` and is obtained through |
| /// [`DeviceApi::new_configuration_update`]. |
| /// |
| /// The configuration is only applied when [`DeviceApi::apply_configuration`] is |
| /// called. |
| pub struct PendingDeviceConfigurationUpdate<'a, D>(DeviceConfigurationUpdate, &'a D); |
| |
| /// The device API. |
| pub struct DeviceApi<D, C>(C, PhantomData<D>); |
| |
| impl<D, C> DeviceApi<D, C> { |
| pub(crate) fn new(ctx: C) -> Self { |
| Self(ctx, PhantomData) |
| } |
| } |
| |
| impl<D, C> DeviceApi<D, C> |
| where |
| D: Device + DeviceStateSpec + DeviceReceiveFrameSpec, |
| C: ContextPair, |
| C::CoreContext: DeviceApiCoreContext<D, C::BindingsContext>, |
| C::BindingsContext: DeviceApiBindingsContext, |
| { |
| pub(crate) fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) { |
| let Self(pair, PhantomData) = self; |
| pair.contexts() |
| } |
| |
| pub(crate) fn core_ctx(&mut self) -> &mut C::CoreContext { |
| let Self(pair, PhantomData) = self; |
| pair.core_ctx() |
| } |
| |
| /// Adds a new device to the stack and returns its identifier. |
| /// |
| /// # Panics |
| /// |
| /// Panics if more than 1 loopback device is added to the stack. |
| pub fn add_device( |
| &mut self, |
| bindings_id: <C::BindingsContext as DeviceLayerStateTypes>::DeviceIdentifier, |
| properties: D::CreationProperties, |
| metric: RawMetric, |
| external_state: D::External<C::BindingsContext>, |
| ) -> <C::CoreContext as DeviceIdContext<D>>::DeviceId { |
| debug!("adding {} device with {:?} metric:{metric}", D::DEBUG_TYPE, properties); |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| let origin = core_ctx.origin_tracker(); |
| let primary = BasePrimaryDeviceId::new( |
| |weak_ref| { |
| IpLinkDeviceStateInner::new( |
| D::new_link_state::<C::CoreContext, _>(bindings_ctx, weak_ref, properties), |
| metric, |
| origin, |
| ) |
| }, |
| external_state, |
| bindings_id, |
| ); |
| let id = primary.clone_strong(); |
| core_ctx.insert(primary); |
| id |
| } |
| |
| /// Like [`DeviceApi::add_device`] but using default values for |
| /// `bindings_id` and `external_state`. |
| /// |
| /// This is provided as a convenience method for tests with faked bindings |
| /// contexts that have simple implementations for bindings state. |
| #[cfg(any(test, feature = "testutils"))] |
| pub(crate) fn add_device_with_default_state( |
| &mut self, |
| properties: D::CreationProperties, |
| metric: RawMetric, |
| ) -> <C::CoreContext as DeviceIdContext<D>>::DeviceId |
| where |
| <C::BindingsContext as DeviceLayerStateTypes>::DeviceIdentifier: Default, |
| D::External<C::BindingsContext>: Default, |
| { |
| self.add_device(Default::default(), properties, metric, Default::default()) |
| } |
| |
| /// Removes `device` from the stack. |
| /// |
| /// If the return value is `RemoveDeviceResult::Removed` the device is |
| /// immediately removed from the stack, otherwise |
| /// `RemoveDeviceResult::Deferred` indicates that the device was marked for |
| /// destruction but there are still references to it. It carries a |
| /// `ReferenceReceiver` from the bindings context that can be awaited on |
| /// until removal is complete. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the device is not currently in the stack. |
| pub fn remove_device( |
| &mut self, |
| device: BaseDeviceId<D, C::BindingsContext>, |
| ) -> RemoveResourceResultWithContext<D::External<C::BindingsContext>, C::BindingsContext> |
| where |
| // Required to call into IP layer for cleanup on removal: |
| BaseDeviceId<D, C::BindingsContext>: Into<DeviceId<C::BindingsContext>>, |
| C::CoreContext: IpDeviceConfigurationContext<Ipv4, C::BindingsContext> |
| + Ipv6DeviceConfigurationContext<C::BindingsContext> |
| + DeviceIdContext<AnyDevice, DeviceId = DeviceId<C::BindingsContext>>, |
| C::BindingsContext: IpDeviceBindingsContext<Ipv4, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId> |
| + IpDeviceBindingsContext<Ipv6, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>, |
| { |
| // Start cleaning up the device by disabling IP state. This removes timers |
| // for the device that would otherwise hold references to defunct device |
| // state. |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| let debug_references = { |
| let device = device.clone().into(); |
| crate::ip::device::clear_ipv4_device_state(core_ctx, bindings_ctx, &device); |
| crate::ip::device::clear_ipv6_device_state(core_ctx, bindings_ctx, &device); |
| device.downgrade().debug_references() |
| }; |
| |
| tracing::debug!("removing {device:?}"); |
| let primary = core_ctx.remove(&device).expect("tried to remove device not in stack"); |
| assert_eq!(device, primary); |
| core::mem::drop(device); |
| match PrimaryRc::unwrap_or_notify_with(primary.into_inner(), || { |
| let (notifier, receiver) = C::BindingsContext::new_reference_notifier::< |
| D::External<C::BindingsContext>, |
| _, |
| >(debug_references); |
| let notifier = |
| crate::sync::MapRcNotifier::new(notifier, |state: BaseDeviceState<_, _>| { |
| state.external_state |
| }); |
| (notifier, receiver) |
| }) { |
| Ok(s) => RemoveResourceResult::Removed(s.external_state), |
| Err(receiver) => RemoveResourceResult::Deferred(receiver), |
| } |
| } |
| |
| /// Receive a device layer frame from the network. |
| pub fn receive_frame<B: BufferMut + Debug>( |
| &mut self, |
| meta: D::FrameMetadata<BaseDeviceId<D, C::BindingsContext>>, |
| frame: B, |
| ) { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| core_ctx.receive_frame(bindings_ctx, meta, frame) |
| } |
| |
| /// Applies the configuration and returns a [`DeviceConfigurationUpdate`] |
| /// with the previous values for all configurations for all `Some` fields. |
| /// |
| /// Note that even if the previous value matched the requested value, it is |
| /// still populated in the returned `DeviceConfigurationUpdate`. |
| pub fn apply_configuration( |
| &mut self, |
| pending: PendingDeviceConfigurationUpdate<'_, BaseDeviceId<D, C::BindingsContext>>, |
| ) -> DeviceConfigurationUpdate { |
| let PendingDeviceConfigurationUpdate(DeviceConfigurationUpdate { arp, ndp }, device_id) = |
| pending; |
| let core_ctx = self.core_ctx(); |
| let arp = core_ctx.with_nud_config_mut::<Ipv4, _, _>(device_id, move |device_config| { |
| let device_config = match device_config { |
| Some(c) => c, |
| None => { |
| // Can't set ARP configuration if device doesn't support it, |
| // this is validated when creating the |
| // `PendingDeviceConfigurationUpdate`. |
| assert!(arp.is_none()); |
| return None; |
| } |
| }; |
| arp.map(|ArpConfigurationUpdate { nud }| { |
| let nud = nud.map(|config| config.apply_and_take_previous(device_config)); |
| ArpConfigurationUpdate { nud } |
| }) |
| }); |
| let ndp = core_ctx.with_nud_config_mut::<Ipv6, _, _>(device_id, move |device_config| { |
| let device_config = match device_config { |
| Some(c) => c, |
| None => { |
| // Can't set NDP configuration if device doesn't support it, |
| // this is validated when creating the |
| // `PendingDeviceConfigurationUpdate`. |
| assert!(ndp.is_none()); |
| return None; |
| } |
| }; |
| ndp.map(|NdpConfigurationUpdate { nud }| { |
| let nud = nud.map(|config| config.apply_and_take_previous(device_config)); |
| NdpConfigurationUpdate { nud } |
| }) |
| }); |
| DeviceConfigurationUpdate { arp, ndp } |
| } |
| |
| /// Creates a new device configuration update for the given device. |
| /// |
| /// This method only validates that `config` is valid for `device`. |
| /// [`DeviceApi::apply`] must be called to apply the configuration. |
| pub fn new_configuration_update<'a>( |
| &mut self, |
| device: &'a BaseDeviceId<D, C::BindingsContext>, |
| config: DeviceConfigurationUpdate, |
| ) -> Result< |
| PendingDeviceConfigurationUpdate<'a, BaseDeviceId<D, C::BindingsContext>>, |
| DeviceConfigurationUpdateError, |
| > { |
| let core_ctx = self.core_ctx(); |
| let DeviceConfigurationUpdate { arp, ndp } = &config; |
| if arp.is_some() && core_ctx.with_nud_config::<Ipv4, _, _>(device, |c| c.is_none()) { |
| return Err(DeviceConfigurationUpdateError::ArpNotSupported); |
| } |
| if ndp.is_some() && core_ctx.with_nud_config::<Ipv6, _, _>(device, |c| c.is_none()) { |
| return Err(DeviceConfigurationUpdateError::NdpNotSupported); |
| } |
| Ok(PendingDeviceConfigurationUpdate(config, device)) |
| } |
| |
| /// Returns a snapshot of the given device's configuration. |
| pub fn get_configuration( |
| &mut self, |
| device: &BaseDeviceId<D, C::BindingsContext>, |
| ) -> DeviceConfiguration { |
| let core_ctx = self.core_ctx(); |
| let arp = core_ctx |
| .with_nud_config::<Ipv4, _, _>(device, |config| config.cloned()) |
| .map(|nud| ArpConfiguration { nud }); |
| let ndp = core_ctx |
| .with_nud_config::<Ipv6, _, _>(device, |config| config.cloned()) |
| .map(|nud| NdpConfiguration { nud }); |
| DeviceConfiguration { arp, ndp } |
| } |
| |
| /// Exports state for `device` into `inspector`. |
| pub fn inspect<N: Inspector>( |
| &mut self, |
| device: &BaseDeviceId<D, C::BindingsContext>, |
| inspector: &mut N, |
| ) { |
| inspector.record_child("Counters", |inspector| { |
| self.core_ctx().with_per_resource_counters(device, |counters: &DeviceCounters| { |
| inspector.delegate_inspectable(counters) |
| }); |
| self.core_ctx().with_per_resource_counters(device, |counters: &D::Counters| { |
| inspector.delegate_inspectable(counters) |
| }); |
| }); |
| } |
| } |
| |
| /// The device API interacting with any kind of supported device. |
| pub struct DeviceAnyApi<C>(C); |
| |
| impl<C> DeviceAnyApi<C> { |
| pub(crate) fn new(ctx: C) -> Self { |
| Self(ctx) |
| } |
| } |
| |
| impl<C> DeviceAnyApi<C> |
| where |
| C: ContextPair, |
| C::CoreContext: DeviceApiCoreContext<EthernetLinkDevice, C::BindingsContext> |
| + DeviceApiCoreContext<LoopbackDevice, C::BindingsContext> |
| + DeviceApiCoreContext<PureIpDevice, C::BindingsContext>, |
| C::BindingsContext: DeviceApiBindingsContext, |
| { |
| fn device<D>(&mut self) -> DeviceApi<D, &mut C> { |
| let Self(pair) = self; |
| DeviceApi::new(pair) |
| } |
| |
| /// Like [`DeviceApi::apply_configuration`] but for any device types. |
| pub fn apply_configuration( |
| &mut self, |
| pending: PendingDeviceConfigurationUpdate<'_, DeviceId<C::BindingsContext>>, |
| ) -> DeviceConfigurationUpdate { |
| let PendingDeviceConfigurationUpdate(config, device) = pending; |
| for_any_device_id!(DeviceId, device, |
| device => { |
| self.device().apply_configuration(PendingDeviceConfigurationUpdate(config, device)) |
| } |
| ) |
| } |
| |
| /// Like [`DeviceApi::new_configuration_update`] but for any device |
| /// types. |
| pub fn new_configuration_update<'a>( |
| &mut self, |
| device: &'a DeviceId<C::BindingsContext>, |
| config: DeviceConfigurationUpdate, |
| ) -> Result< |
| PendingDeviceConfigurationUpdate<'a, DeviceId<C::BindingsContext>>, |
| DeviceConfigurationUpdateError, |
| > { |
| for_any_device_id!(DeviceId, device, |
| inner => { |
| self.device() |
| .new_configuration_update(inner, config) |
| .map(|PendingDeviceConfigurationUpdate(config, _)| { |
| PendingDeviceConfigurationUpdate(config, device) |
| }) |
| } |
| ) |
| } |
| |
| /// Like [`DeviceApi::get_configuration`] but for any device types. |
| pub fn get_configuration( |
| &mut self, |
| device: &DeviceId<C::BindingsContext>, |
| ) -> DeviceConfiguration { |
| for_any_device_id!(DeviceId, device, |
| device => self.device().get_configuration(device)) |
| } |
| |
| /// Like [`DeviceApi::inspect`] but for any device type. |
| pub fn inspect<N: Inspector>( |
| &mut self, |
| device: &DeviceId<C::BindingsContext>, |
| inspector: &mut N, |
| ) { |
| for_any_device_id!(DeviceId, DeviceProvider, D, device, |
| device => self.device::<D>().inspect(device, inspector)) |
| } |
| } |
| |
| /// A marker trait for all the core context traits required to fulfill the |
| /// [`DeviceApi`]. |
| pub trait DeviceApiCoreContext< |
| D: Device + DeviceStateSpec + DeviceReceiveFrameSpec, |
| BC: DeviceApiBindingsContext, |
| >: |
| DeviceIdContext<D, DeviceId = BaseDeviceId<D, BC>, WeakDeviceId = BaseWeakDeviceId<D, BC>> |
| + OriginTrackerContext |
| + DeviceCollectionContext<D, BC> |
| + DeviceConfigurationContext<D> |
| + RecvFrameContext<BC, D::FrameMetadata<BaseDeviceId<D, BC>>> |
| + ResourceCounterContext<Self::DeviceId, DeviceCounters> |
| + ResourceCounterContext<Self::DeviceId, D::Counters> |
| + CoreTimerContext<D::TimerId<Self::WeakDeviceId>, BC> |
| { |
| } |
| |
| impl<O, D, BC> DeviceApiCoreContext<D, BC> for O |
| where |
| D: Device + DeviceStateSpec + DeviceReceiveFrameSpec, |
| BC: DeviceApiBindingsContext, |
| O: DeviceIdContext<D, DeviceId = BaseDeviceId<D, BC>, WeakDeviceId = BaseWeakDeviceId<D, BC>> |
| + OriginTrackerContext |
| + DeviceCollectionContext<D, BC> |
| + DeviceConfigurationContext<D> |
| + RecvFrameContext<BC, D::FrameMetadata<BaseDeviceId<D, BC>>> |
| + ResourceCounterContext<Self::DeviceId, DeviceCounters> |
| + ResourceCounterContext<Self::DeviceId, D::Counters> |
| + CoreTimerContext<D::TimerId<Self::WeakDeviceId>, BC>, |
| { |
| } |
| |
| /// A marker trait for all the bindings context traits required to fulfill the |
| /// [`DeviceApi`]. |
| pub trait DeviceApiBindingsContext: DeviceLayerTypes + ReferenceNotifiers + TimerContext2 {} |
| |
| impl<O> DeviceApiBindingsContext for O where O: DeviceLayerTypes + ReferenceNotifiers + TimerContext2 |
| {} |