blob: 14c941ffadab70f2c04e40c2e04857be91fd1e78 [file] [log] [blame]
// Copyright 2019 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.
use std::{
collections::hash_map::{self, HashMap},
fmt::{self, Debug, Display},
num::NonZeroU64,
ops::{Deref as _, DerefMut as _},
};
use assert_matches::assert_matches;
use derivative::Derivative;
use fidl_fuchsia_hardware_network as fhardware_network;
use fidl_fuchsia_net_interfaces as fnet_interfaces;
use fuchsia_zircon as zx;
use net_types::{
ethernet::Mac,
ip::{IpAddr, Mtu},
SpecifiedAddr, UnicastAddr,
};
use netstack3_core::{
device::{
DeviceClassMatcher, DeviceId, DeviceIdAndNameMatcher, DeviceProvider, DeviceSendFrameError,
LoopbackDeviceId,
},
sync::{Mutex as CoreMutex, RwLock as CoreRwLock},
types::WorkQueueReport,
};
use tracing::warn;
use crate::bindings::{
interfaces_admin, neighbor_worker, util::NeedsDataNotifier, BindingsCtx, Ctx,
};
pub(crate) const LOOPBACK_MAC: Mac = Mac::new([0, 0, 0, 0, 0, 0]);
pub(crate) type BindingId = NonZeroU64;
/// Keeps tabs on devices.
///
/// `Devices` keeps a list of devices that are installed in the netstack with
/// an associated netstack core ID `C` used to reference the device.
///
/// The type parameter `C` is for the extra information associated with the
/// device. The type parameters are there to allow testing without dependencies
/// on `core`.
pub(crate) struct Devices<C> {
id_map: CoreRwLock<HashMap<BindingId, C>>,
last_id: CoreMutex<BindingId>,
}
impl<C> Default for Devices<C> {
fn default() -> Self {
Self { id_map: Default::default(), last_id: CoreMutex::new(BindingId::MIN) }
}
}
impl<C> Devices<C>
where
C: Clone + std::fmt::Debug + PartialEq,
{
/// Allocates a new [`BindingId`].
#[must_use]
pub(crate) fn alloc_new_id(&self) -> BindingId {
let Self { id_map: _, last_id } = self;
let mut last_id = last_id.lock();
let id = *last_id;
*last_id = last_id.checked_add(1).expect("exhausted binding device IDs");
id
}
/// Adds a new device.
///
/// Adds a new device if the informed `core_id` is valid (i.e., not
/// currently tracked by [`Devices`]). A new [`BindingId`] will be allocated
/// and a [`DeviceInfo`] struct will be created with the provided `info` and
/// IDs.
pub(crate) fn add_device(&self, id: BindingId, core_id: C) {
let Self { id_map, last_id: _ } = self;
assert_matches!(id_map.write().insert(id, core_id), None);
}
/// Removes a device from the internal list.
///
/// Removes a device from the internal [`Devices`] list and returns the
/// associated [`DeviceInfo`] if `id` is found or `None` otherwise.
pub(crate) fn remove_device(&self, id: BindingId) -> Option<C> {
let Self { id_map, last_id: _ } = self;
id_map.write().remove(&id)
}
/// Retrieve associated `core_id` for [`BindingId`].
pub(crate) fn get_core_id(&self, id: BindingId) -> Option<C> {
self.id_map.read().get(&id).cloned()
}
/// Call the provided callback with an iterator over the devices.
pub(crate) fn with_devices<R>(
&self,
f: impl FnOnce(hash_map::Values<'_, BindingId, C>) -> R,
) -> R {
let Self { id_map, last_id: _ } = self;
f(id_map.read().values())
}
}
impl Devices<DeviceId<BindingsCtx>> {
/// Retrieves the device with the given name.
pub(crate) fn get_device_by_name(&self, name: &str) -> Option<DeviceId<BindingsCtx>> {
self.id_map
.read()
.iter()
.find_map(|(_binding_id, c)| (c.bindings_id().name == name).then_some(c))
.cloned()
}
}
/// Device specific iformation.
#[derive(Debug)]
pub(crate) enum DeviceSpecificInfo<'a> {
Loopback(&'a LoopbackInfo),
Ethernet(&'a EthernetInfo),
PureIp(&'a PureIpDeviceInfo),
}
impl DeviceSpecificInfo<'_> {
pub(crate) fn static_common_info(&self) -> &StaticCommonInfo {
match self {
Self::Loopback(i) => &i.static_common_info,
Self::Ethernet(i) => &i.common_info,
Self::PureIp(i) => &i.common_info,
}
}
pub(crate) fn with_common_info<O, F: FnOnce(&DynamicCommonInfo) -> O>(&self, cb: F) -> O {
match self {
Self::Loopback(i) => i.with_dynamic_info(cb),
Self::Ethernet(i) => i.with_dynamic_info(|dynamic| cb(&dynamic.netdevice.common_info)),
Self::PureIp(i) => i.with_dynamic_info(|dynamic| cb(&dynamic.common_info)),
}
}
pub(crate) fn with_common_info_mut<O, F: FnOnce(&mut DynamicCommonInfo) -> O>(
&self,
cb: F,
) -> O {
match self {
Self::Loopback(i) => i.with_dynamic_info_mut(cb),
Self::Ethernet(i) => {
i.with_dynamic_info_mut(|dynamic| cb(&mut dynamic.netdevice.common_info))
}
Self::PureIp(i) => i.with_dynamic_info_mut(|dynamic| cb(&mut dynamic.common_info)),
}
}
}
pub(crate) fn spawn_rx_task(
notifier: &NeedsDataNotifier,
mut ctx: Ctx,
device_id: &LoopbackDeviceId<BindingsCtx>,
) -> fuchsia_async::Task<()> {
let watcher = notifier.watcher();
let device_id = device_id.downgrade();
fuchsia_async::Task::spawn(crate::bindings::util::yielding_data_notifier_loop(
watcher,
move || {
device_id
.upgrade()
.map(|device_id| ctx.api().receive_queue().handle_queued_frames(&device_id))
},
))
}
pub(crate) fn spawn_tx_task(
notifier: &NeedsDataNotifier,
mut ctx: Ctx,
device_id: DeviceId<BindingsCtx>,
) -> fuchsia_async::Task<()> {
let watcher = notifier.watcher();
let device_id = device_id.downgrade();
fuchsia_async::Task::spawn(crate::bindings::util::yielding_data_notifier_loop(
watcher,
move || {
// NB: We could write this function generically in terms of `D:
// Device`, which is the type parameter given to instantiate the
// transmit queue API. To do that, core would need to expose a
// marker for CoreContext that is generic on `D`. That is doable but
// not worth the extra code at this moment since bindings doesn't
// have meaningful amounts of code that is generic over the device
// type.
device_id.upgrade().map(|device_id| {
netstack3_core::for_any_device_id!(DeviceId, DeviceProvider, D, &device_id,
id => ctx.api().transmit_queue::<D>().transmit_queued_frames(id)
)
.unwrap_or_else(|DeviceSendFrameError::DeviceNotReady(())| {
warn!(
"TODO(https://fxbug.dev/42057204): Support waiting for TX buffers to be \
available, dropping packet for now on device={device_id:?}",
);
WorkQueueReport::AllDone
})
})
},
))
}
/// Static information common to all devices.
#[derive(Derivative, Debug)]
pub(crate) struct StaticCommonInfo {
#[derivative(Debug = "ignore")]
pub(crate) tx_notifier: NeedsDataNotifier,
pub(crate) authorization_token: zx::Event,
}
impl Default for StaticCommonInfo {
fn default() -> StaticCommonInfo {
StaticCommonInfo {
tx_notifier: Default::default(),
authorization_token: zx::Event::create(),
}
}
}
/// Information common to all devices.
#[derive(Derivative)]
#[derivative(Debug)]
pub(crate) struct DynamicCommonInfo {
pub(crate) mtu: Mtu,
pub(crate) admin_enabled: bool,
pub(crate) events: super::InterfaceEventProducer,
// An attach point to send `fuchsia.net.interfaces.admin/Control` handles to the Interfaces
// Admin worker.
#[derivative(Debug = "ignore")]
pub(crate) control_hook: futures::channel::mpsc::Sender<interfaces_admin::OwnedControlHandle>,
pub(crate) addresses: HashMap<SpecifiedAddr<IpAddr>, AddressInfo>,
}
#[derive(Debug)]
pub(crate) struct AddressInfo {
// The `AddressStateProvider` FIDL protocol worker.
pub(crate) address_state_provider:
FidlWorkerInfo<interfaces_admin::AddressStateProviderCancellationReason>,
// Sender for [`AddressAssignmentState`] change events published by Core;
// the receiver is held by the `AddressStateProvider` worker. Note that an
// [`UnboundedSender`] is used because it exposes a synchronous send API
// which is required since Core is no-async.
pub(crate) assignment_state_sender:
futures::channel::mpsc::UnboundedSender<fnet_interfaces::AddressAssignmentState>,
}
/// Information associated with FIDL Protocol workers.
#[derive(Debug)]
pub(crate) struct FidlWorkerInfo<R> {
// The worker `Task`, wrapped in a `Shared` future so that it can be awaited
// multiple times.
pub(crate) worker: futures::future::Shared<fuchsia_async::Task<()>>,
// Mechanism to cancel the worker with reason `R`. If `Some`, the worker is
// active (and holds the `Receiver`). Otherwise, the worker has been
// canceled.
pub(crate) cancelation_sender: Option<futures::channel::oneshot::Sender<R>>,
}
/// Loopback device information.
#[derive(Derivative)]
#[derivative(Debug)]
pub(crate) struct LoopbackInfo {
pub(crate) static_common_info: StaticCommonInfo,
pub(crate) dynamic_common_info: CoreRwLock<DynamicCommonInfo>,
#[derivative(Debug = "ignore")]
pub(crate) rx_notifier: NeedsDataNotifier,
}
impl LoopbackInfo {
pub(crate) fn with_dynamic_info<O, F: FnOnce(&DynamicCommonInfo) -> O>(&self, cb: F) -> O {
cb(self.dynamic_common_info.read().deref())
}
pub(crate) fn with_dynamic_info_mut<O, F: FnOnce(&mut DynamicCommonInfo) -> O>(
&self,
cb: F,
) -> O {
cb(self.dynamic_common_info.write().deref_mut())
}
}
impl DeviceClassMatcher<fidl_fuchsia_net_filter::DeviceClass> for LoopbackInfo {
fn device_class_matches(&self, device_class: &fidl_fuchsia_net_filter::DeviceClass) -> bool {
match device_class {
fidl_fuchsia_net_filter::DeviceClass::Loopback(fidl_fuchsia_net_filter::Empty {}) => {
true
}
fidl_fuchsia_net_filter::DeviceClass::Device(_) => false,
fidl_fuchsia_net_filter::DeviceClass::__SourceBreaking { unknown_ordinal } => {
panic!("unknown device class ordinal {unknown_ordinal:?}")
}
}
}
}
/// Dynamic information common to all Netdevice backed devices.
#[derive(Debug)]
pub(crate) struct DynamicNetdeviceInfo {
pub(crate) phy_up: bool,
pub(crate) common_info: DynamicCommonInfo,
}
/// Static information common to all Netdevice backed devices.
#[derive(Debug)]
pub(crate) struct StaticNetdeviceInfo {
pub(crate) handler: super::netdevice_worker::PortHandler,
}
impl StaticNetdeviceInfo {
fn device_class_matches(&self, device_class: &fidl_fuchsia_net_filter::DeviceClass) -> bool {
match device_class {
fidl_fuchsia_net_filter::DeviceClass::Loopback(fidl_fuchsia_net_filter::Empty {}) => {
false
}
fidl_fuchsia_net_filter::DeviceClass::Device(class) => {
*class == self.handler.device_class()
}
fidl_fuchsia_net_filter::DeviceClass::__SourceBreaking { unknown_ordinal } => {
panic!("unknown device class ordinal {unknown_ordinal:?}")
}
}
}
}
/// Dynamic information for Ethernet devices
#[derive(Derivative)]
#[derivative(Debug)]
pub(crate) struct DynamicEthernetInfo {
pub(crate) netdevice: DynamicNetdeviceInfo,
#[derivative(Debug = "ignore")]
pub(crate) neighbor_event_sink: futures::channel::mpsc::UnboundedSender<neighbor_worker::Event>,
}
/// Ethernet device information.
#[derive(Debug)]
pub(crate) struct EthernetInfo {
pub(crate) dynamic_info: CoreRwLock<DynamicEthernetInfo>,
pub(crate) common_info: StaticCommonInfo,
pub(crate) netdevice: StaticNetdeviceInfo,
pub(crate) mac: UnicastAddr<Mac>,
// We must keep the mac proxy alive to maintain our multicast filtering mode
// selection set.
pub(crate) _mac_proxy: fhardware_network::MacAddressingProxy,
}
impl EthernetInfo {
pub(crate) fn with_dynamic_info<O, F: FnOnce(&DynamicEthernetInfo) -> O>(&self, cb: F) -> O {
let dynamic = self.dynamic_info.read();
cb(dynamic.deref())
}
pub(crate) fn with_dynamic_info_mut<O, F: FnOnce(&mut DynamicEthernetInfo) -> O>(
&self,
cb: F,
) -> O {
let mut dynamic = self.dynamic_info.write();
cb(dynamic.deref_mut())
}
}
impl DeviceClassMatcher<fidl_fuchsia_net_filter::DeviceClass> for EthernetInfo {
fn device_class_matches(&self, device_class: &fidl_fuchsia_net_filter::DeviceClass) -> bool {
self.netdevice.device_class_matches(device_class)
}
}
/// Pure IP device information.
#[derive(Debug)]
pub(crate) struct PureIpDeviceInfo {
pub(crate) common_info: StaticCommonInfo,
pub(crate) netdevice: StaticNetdeviceInfo,
pub(crate) dynamic_info: CoreRwLock<DynamicNetdeviceInfo>,
}
impl PureIpDeviceInfo {
pub(crate) fn with_dynamic_info<O, F: FnOnce(&DynamicNetdeviceInfo) -> O>(&self, cb: F) -> O {
let dynamic = self.dynamic_info.read();
cb(dynamic.deref())
}
pub(crate) fn with_dynamic_info_mut<O, F: FnOnce(&mut DynamicNetdeviceInfo) -> O>(
&self,
cb: F,
) -> O {
let mut dynamic = self.dynamic_info.write();
cb(dynamic.deref_mut())
}
}
impl DeviceClassMatcher<fidl_fuchsia_net_filter::DeviceClass> for PureIpDeviceInfo {
fn device_class_matches(&self, device_class: &fidl_fuchsia_net_filter::DeviceClass) -> bool {
self.netdevice.device_class_matches(device_class)
}
}
pub(crate) struct DeviceIdAndName {
pub(crate) id: BindingId,
pub(crate) name: String,
}
impl Debug for DeviceIdAndName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { id, name } = self;
write!(f, "{id}=>{name}")
}
}
impl Display for DeviceIdAndName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
}
}
impl DeviceIdAndNameMatcher for DeviceIdAndName {
fn id_matches(&self, id: &NonZeroU64) -> bool {
self.id == *id
}
fn name_matches(&self, name: &str) -> bool {
self.name == name
}
}