blob: c36eca77ce150aaf6c6e3eade199f8ec6deaa398 [file] [log] [blame] [edit]
// Copyright 2020 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::fmt::Display;
use fidl::endpoints::Proxy;
use fidl_fuchsia_device as fdev;
use fidl_fuchsia_hardware_ethernet as feth;
use fidl_fuchsia_hardware_ethernet_ext as feth_ext;
use fidl_fuchsia_hardware_network as fhwnet;
use fidl_fuchsia_net_stack as fnet_stack;
use fidl_fuchsia_netstack as fnetstack;
use fuchsia_zircon as zx;
use anyhow::Context as _;
use async_trait::async_trait;
use crate::errors::{self, ContextExt as _};
/// An error when adding a device.
pub(super) enum AddDeviceError {
AlreadyExists,
Other(errors::Error),
}
impl From<errors::Error> for AddDeviceError {
fn from(e: errors::Error) -> AddDeviceError {
AddDeviceError::Other(e)
}
}
impl errors::ContextExt for AddDeviceError {
fn context<C>(self, context: C) -> AddDeviceError
where
C: Display + Send + Sync + 'static,
{
match self {
AddDeviceError::AlreadyExists => AddDeviceError::AlreadyExists,
AddDeviceError::Other(e) => AddDeviceError::Other(e.context(context)),
}
}
fn with_context<C, F>(self, f: F) -> AddDeviceError
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
AddDeviceError::AlreadyExists => AddDeviceError::AlreadyExists,
AddDeviceError::Other(e) => AddDeviceError::Other(e.with_context(f)),
}
}
}
/// An abstraction over a [`Device`]'s info.
pub(super) trait DeviceInfo {
/// Is the device a WLAN device?
fn is_wlan(&self) -> bool;
/// Is the device a physical device?
fn is_physical(&self) -> bool;
}
impl DeviceInfo for feth_ext::EthernetInfo {
fn is_wlan(&self) -> bool {
feth_ext::is_wlan(self.features)
}
fn is_physical(&self) -> bool {
feth_ext::is_physical(self.features)
}
}
impl DeviceInfo for fhwnet::Info {
fn is_wlan(&self) -> bool {
self.class == fhwnet::DeviceClass::Wlan
}
fn is_physical(&self) -> bool {
// We do not know if a network device is physical or virtual. For now, we consider
// all network devices as physical. Virtual network devices do not show up on devfs.
true
}
}
/// A type of device that may be discovered.
#[async_trait]
pub(super) trait Device {
/// The name of this device.
const NAME: &'static str;
/// The relative path from the root device directory to this `Device`'s class of
/// devices.
const PATH: &'static str;
/// The service this device implements.
type ServiceMarker: fidl::endpoints::ServiceMarker;
/// The type returned by [`device_info_and_mac`].
type DeviceInfo: DeviceInfo;
/// The type returned by [`get_topo_path_and_device`].
type DeviceInstance;
/// Returns the topological path for a device located at `filepath` and an instance
/// of the device.
///
/// It is expected that the node at `filepath` implements `fuchsia.device/Controller`
/// and `Self::ServiceMarker`.
async fn get_topo_path_and_device(
filepath: &std::path::PathBuf,
) -> Result<(String, Self::DeviceInstance), errors::Error>;
/// Get the device's information and MAC address.
async fn device_info_and_mac(
device_instance: &Self::DeviceInstance,
) -> Result<(Self::DeviceInfo, feth_ext::MacAddress), errors::Error>;
/// Returns an [`EthernetInfo`] representation of the device.
///
/// [`EthernetInfo`]: feth_ext::EthernetInfo
fn eth_device_info(
info: Self::DeviceInfo,
mac: feth_ext::MacAddress,
features: feth::Features,
) -> feth_ext::EthernetInfo;
/// Adds the device to the netstack.
async fn add_to_stack(
netcfg: &mut super::NetCfg<'_>,
topological_path: String,
config: &mut fnetstack::InterfaceConfig,
device_instance: Self::DeviceInstance,
) -> Result<u64, AddDeviceError>;
}
/// An implementation of [`Device`] for ethernet devices.
pub(super) enum EthernetDevice {}
#[async_trait]
impl Device for EthernetDevice {
const NAME: &'static str = "ethdev";
const PATH: &'static str = "class/ethernet";
type ServiceMarker = feth::DeviceMarker;
type DeviceInfo = feth_ext::EthernetInfo;
type DeviceInstance = feth::DeviceProxy;
async fn get_topo_path_and_device(
filepath: &std::path::PathBuf,
) -> Result<(String, feth::DeviceProxy), errors::Error> {
get_topo_path_and_device::<feth::DeviceMarker>(filepath).await
}
async fn device_info_and_mac(
device_instance: &feth::DeviceProxy,
) -> Result<(feth_ext::EthernetInfo, feth_ext::MacAddress), errors::Error> {
let info: feth_ext::EthernetInfo = device_instance
.get_info()
.await
.map(Into::into)
.context("error getting device info for ethdev")
.map_err(errors::Error::NonFatal)?;
let mac_addr = info.mac;
Ok((info, mac_addr))
}
fn eth_device_info(
info: feth_ext::EthernetInfo,
_mac: feth_ext::MacAddress,
_features: feth::Features,
) -> feth_ext::EthernetInfo {
info
}
async fn add_to_stack(
netcfg: &mut super::NetCfg<'_>,
topological_path: String,
config: &mut fnetstack::InterfaceConfig,
device_instance: feth::DeviceProxy,
) -> Result<u64, AddDeviceError> {
let client = device_instance
.into_channel()
.map_err(|_: feth::DeviceProxy| {
errors::Error::Fatal(anyhow::anyhow!("failed to convert device proxy into channel"))
})?
.into_zx_channel();
let res = netcfg
.netstack
.add_ethernet_device(
&topological_path,
config,
fidl::endpoints::ClientEnd::<feth::DeviceMarker>::new(client),
)
.await
.context("error sending add_ethernet_device request")
.map_err(errors::Error::Fatal)?
.map_err(zx::Status::from_raw)
.map(Into::into);
if res == Err(zx::Status::ALREADY_EXISTS) {
Err(AddDeviceError::AlreadyExists)
} else {
res.context("error adding ethernet device")
.map_err(errors::Error::NonFatal)
.map_err(AddDeviceError::Other)
}
}
}
/// An implementation of [`Device`] for network devices.
pub(super) enum NetworkDevice {}
/// An instance of a network device.
pub(super) struct NetworkDeviceInstance {
device: fhwnet::DeviceProxy,
mac_addressing: fhwnet::MacAddressingProxy,
}
#[async_trait]
impl Device for NetworkDevice {
const NAME: &'static str = "netdev";
const PATH: &'static str = "class/network";
type ServiceMarker = fhwnet::DeviceInstanceMarker;
type DeviceInfo = fhwnet::Info;
type DeviceInstance = NetworkDeviceInstance;
async fn get_topo_path_and_device(
filepath: &std::path::PathBuf,
) -> Result<(String, NetworkDeviceInstance), errors::Error> {
let (path, device_instance) =
get_topo_path_and_device::<fhwnet::DeviceInstanceMarker>(filepath)
.await
.context("error getting topological path and device instance")?;
let (device, req) = fidl::endpoints::create_proxy()
.context("error creating device proxy")
.map_err(errors::Error::Fatal)?;
let () = device_instance
.get_device(req)
.context("error geting device")
.map_err(errors::Error::NonFatal)?;
let (mac_addressing, req) = fidl::endpoints::create_proxy()
.context("error creating mac addressing proxy")
.map_err(errors::Error::Fatal)?;
let () = device_instance
.get_mac_addressing(req)
.context("error getting MAC addressing client")
.map_err(errors::Error::NonFatal)?;
Ok((path, NetworkDeviceInstance { device, mac_addressing }))
}
async fn device_info_and_mac(
device_instance: &NetworkDeviceInstance,
) -> Result<(fhwnet::Info, feth_ext::MacAddress), errors::Error> {
let NetworkDeviceInstance { device, mac_addressing } = device_instance;
let info = device
.get_info()
.await
.context("error getting netdev info")
.map_err(errors::Error::NonFatal)?;
let mac_addr = feth_ext::MacAddress {
octets: mac_addressing
.get_unicast_address()
.await
.context("error getting unicast MAC address")
.map_err(errors::Error::NonFatal)?
.octets,
};
Ok((info, mac_addr))
}
fn eth_device_info(
_info: fhwnet::Info,
mac: feth_ext::MacAddress,
features: feth::Features,
) -> feth_ext::EthernetInfo {
feth_ext::EthernetInfo { features, mtu: 0, mac }
}
async fn add_to_stack(
netcfg: &mut super::NetCfg<'_>,
topological_path: String,
config: &mut fnetstack::InterfaceConfig,
device_instance: NetworkDeviceInstance,
) -> Result<u64, AddDeviceError> {
let NetworkDeviceInstance { device, mac_addressing } = device_instance;
netcfg
.stack
.add_interface(
fnet_stack::InterfaceConfig {
name: Some(config.name.clone()),
topopath: Some(topological_path.clone()),
metric: Some(config.metric),
..fnet_stack::InterfaceConfig::EMPTY
},
&mut fnet_stack::DeviceDefinition::Ethernet(fnet_stack::EthernetDeviceDefinition {
network_device: fidl::endpoints::ClientEnd::new(
device
.into_channel()
.map_err(|_: fhwnet::DeviceProxy| {
errors::Error::Fatal(anyhow::anyhow!(
"failed to retrieve network device ClientEnd"
))
})?
.into(),
),
mac: fidl::endpoints::ClientEnd::new(
mac_addressing
.into_channel()
.map_err(|_: fhwnet::MacAddressingProxy| {
errors::Error::Fatal(anyhow::anyhow!(
"failed to retrieve mac addressing ClientEnd"
))
})?
.into(),
),
}),
)
.await
.context("error sending stack add interface request")
.map_err(errors::Error::Fatal)?
.map_err(|e: fnet_stack::Error| {
if e == fnet_stack::Error::AlreadyExists {
AddDeviceError::AlreadyExists
} else {
AddDeviceError::Other(errors::Error::NonFatal(anyhow::anyhow!(
"error adding netdev interface: {:?}",
e
)))
}
})
}
}
/// Returns the topological path for a device located at `filepath` and a proxy to `S`.
///
/// It is expected that the node at `filepath` implements `fuchsia.device/Controller`
/// and `S`.
async fn get_topo_path_and_device<S: fidl::endpoints::ServiceMarker>(
filepath: &std::path::PathBuf,
) -> Result<(String, S::Proxy), errors::Error> {
let filepath = filepath
.to_str()
.ok_or_else(|| anyhow::anyhow!("failed to convert {} to str", filepath.display()))
.map_err(errors::Error::NonFatal)?;
// Get the topological path using `fuchsia.device/Controller`.
let (controller, req) = fidl::endpoints::create_proxy::<fdev::ControllerMarker>()
.context("error creating fuchsia.device.Controller proxy")
.map_err(errors::Error::Fatal)?;
fdio::service_connect(filepath, req.into_channel().into())
.with_context(|| format!("error calling fdio::service_connect({})", filepath))
.map_err(errors::Error::NonFatal)?;
let path = controller
.get_topological_path()
.await
.context("error sending get topological path request")
.map_err(errors::Error::NonFatal)?
.map_err(zx::Status::from_raw)
.context("error getting topological path")
.map_err(errors::Error::NonFatal)?;
// The same channel is expeceted to implement `S`.
let ch = controller
.into_channel()
.map_err(|_: fdev::ControllerProxy| anyhow::anyhow!("failed to get controller's channel"))
.map_err(errors::Error::Fatal)?
.into_zx_channel();
let device = fidl::endpoints::ClientEnd::<S>::new(ch)
.into_proxy()
.context("error getting client end proxy")
.map_err(errors::Error::Fatal)?;
Ok((path, device))
}