blob: 3891ad0cefc45f05b2fa6c43525526c2f386532c [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 fidl_fuchsia_net as fidl_net;
use fidl_fuchsia_net_stack as fidl_net_stack;
use net_types::ip::{AddrSubnetEither, IpAddr, Ipv4Addr, Ipv6Addr, SubnetEither};
use net_types::{SpecifiedAddr, Witness};
use netstack3_core::{Context, DeviceId, EntryDest, EntryDestEither, EntryEither};
use never::Never;
use std::convert::TryFrom;
/// Error returned when trying create `core` subnets with invalid values.
#[derive(Debug)]
pub struct InvalidSubnetError;
/// Defines a type that can be converted to a FIDL type `F`.
///
/// This trait provides easy conversion to and from FIDL types. All implementers
/// of this trait for `F` automatically provide the inverse conversion for `F`
/// by implementing [`CoreCompatible`] for `F`.
///
/// This trait is meant only to be implemented for types in `netstack3_core`.
pub trait FidlCompatible<F>: Sized {
type FromError;
type IntoError;
fn try_from_fidl(fidl: F) -> Result<Self, Self::FromError>;
fn try_into_fidl(self) -> Result<F, Self::IntoError>;
}
/// Defines a type that can be converted to a Core type `C`.
///
/// This trait offers the convenient symmetrical for [`FidlCompatible`] and is
/// automatically implemented for all types for which a [`FidlCompatible`]
/// conversion is implemented.
pub trait CoreCompatible<C>: Sized {
type FromError;
type IntoError;
fn try_from_core(core: C) -> Result<Self, Self::FromError>;
fn try_into_core(self) -> Result<C, Self::IntoError>;
}
/// Utility trait for infallible FIDL conversion.
pub trait FromFidlExt<F>: FidlCompatible<F, FromError = Never> {
fn from_fidl(fidl: F) -> Self {
match Self::try_from_fidl(fidl) {
Ok(slf) => slf,
Err(err) => match err {},
}
}
}
/// Utility trait for infallible FIDL conversion.
pub trait IntoFidlExt<F>: FidlCompatible<F, IntoError = Never> {
fn into_fidl(self) -> F {
match self.try_into_fidl() {
Ok(fidl) => fidl,
Err(err) => match err {},
}
}
}
/// Utility trait for infallible Core conversion.
pub trait FromCoreExt<C>: CoreCompatible<C, FromError = Never> {
fn from_core(core: C) -> Self {
match Self::try_from_core(core) {
Ok(slf) => slf,
Err(err) => match err {},
}
}
}
/// Utility trait for infallible Core conversion.
pub trait IntoCoreExt<C>: CoreCompatible<C, IntoError = Never> {
fn into_core(self) -> C {
match self.try_into_core() {
Ok(core) => core,
Err(err) => match err {},
}
}
}
impl<F, C> CoreCompatible<C> for F
where
C: FidlCompatible<F>,
{
type FromError = C::IntoError;
type IntoError = C::FromError;
fn try_from_core(core: C) -> Result<Self, Self::FromError> {
core.try_into_fidl()
}
fn try_into_core(self) -> Result<C, Self::IntoError> {
C::try_from_fidl(self)
}
}
impl<C, F: CoreCompatible<C, IntoError = Never>> IntoCoreExt<C> for F {}
impl<C, F: CoreCompatible<C, FromError = Never>> FromCoreExt<C> for F {}
impl<F, C: FidlCompatible<F, IntoError = Never>> IntoFidlExt<F> for C {}
impl<F, C: FidlCompatible<F, FromError = Never>> FromFidlExt<F> for C {}
impl FidlCompatible<fidl_net::IpAddress> for IpAddr {
type FromError = Never;
type IntoError = Never;
fn try_from_fidl(fidl: fidl_net::IpAddress) -> Result<Self, Self::FromError> {
match fidl {
fidl_net::IpAddress::Ipv4(v4) => Ok(IpAddr::V4(v4.addr.into())),
fidl_net::IpAddress::Ipv6(v6) => Ok(IpAddr::V6(v6.addr.into())),
}
}
fn try_into_fidl(self) -> Result<fidl_net::IpAddress, Self::IntoError> {
match self {
IpAddr::V4(addr) => {
Ok(fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: addr.ipv4_bytes() }))
}
IpAddr::V6(addr) => {
Ok(fidl_net::IpAddress::Ipv6(fidl_net::Ipv6Address { addr: addr.ipv6_bytes() }))
}
}
}
}
/// An error indicating that an address was a member of the wrong class (for
/// example, a unicast address used where a multicast address is required).
pub struct AddrClassError;
// TODO(joshlf): Introduce a separate variant to `fidl_net_stack::ErrorType` for
// `AddrClassError`?
impl From<AddrClassError> for fidl_net_stack::Error {
fn from(_err: AddrClassError) -> Self {
fidl_net_stack::Error { type_: fidl_net_stack::ErrorType::InvalidArgs }
}
}
impl FidlCompatible<fidl_net::IpAddress> for SpecifiedAddr<IpAddr> {
type FromError = AddrClassError;
type IntoError = Never;
fn try_from_fidl(fidl: fidl_net::IpAddress) -> Result<Self, AddrClassError> {
SpecifiedAddr::new(fidl.into_core()).ok_or(AddrClassError)
}
fn try_into_fidl(self) -> Result<fidl_net::IpAddress, Never> {
Ok(self.into_addr().into_fidl())
}
}
impl FidlCompatible<fidl_net_stack::InterfaceAddress> for AddrSubnetEither {
type FromError = InvalidSubnetError;
type IntoError = Never;
fn try_from_fidl(fidl: fidl_net_stack::InterfaceAddress) -> Result<Self, Self::FromError> {
AddrSubnetEither::new(fidl.ip_address.into_core(), fidl.prefix_len)
.ok_or(InvalidSubnetError)
}
fn try_into_fidl(self) -> Result<fidl_net_stack::InterfaceAddress, Self::IntoError> {
let (addr, prefix) = self.into_addr_prefix();
Ok(fidl_net_stack::InterfaceAddress { ip_address: addr.into_fidl(), prefix_len: prefix })
}
}
impl FidlCompatible<fidl_net::Subnet> for SubnetEither {
type FromError = InvalidSubnetError;
type IntoError = Never;
fn try_from_fidl(fidl: fidl_net::Subnet) -> Result<Self, Self::FromError> {
SubnetEither::new(fidl.addr.into_core(), fidl.prefix_len).ok_or(InvalidSubnetError)
}
fn try_into_fidl(self) -> Result<fidl_net::Subnet, Self::IntoError> {
let (net, prefix) = self.into_net_prefix();
Ok(fidl_net::Subnet { addr: net.into_fidl(), prefix_len: prefix })
}
}
/// Provides a stateful context for operations that require state-keeping to be
/// completed.
///
/// `ConversionContext` is used by conversion functions in
/// [`ContextFidlCompatible`] and [`ContextCoreCompatible`].
pub trait ConversionContext {
/// Converts a binding identifier (exposed in FIDL as `u64`) to a core
/// identifier `DeviceId`.
///
/// Returns `None` if there is no core mapping equivalent for `binding_id`.
fn get_core_id(&self, binding_id: u64) -> Option<DeviceId>;
/// Converts a core identifier `DeviceId` to a FIDL-compatible `u64`
/// identifier.
///
/// Returns `None` if there is no FIDL mapping equivalent for `core_id`.
fn get_binding_id(&self, core_id: DeviceId) -> Option<u64>;
}
/// Defines a type that can be converted to a FIDL type `F`, given a context
/// that implements [`ConversionContext`].
///
/// This trait provides easy conversion to and from FIDL types. All implementers
/// of this trait for `F` automatically provide the inverse conversion for `F`
/// by implementing [`ContextCoreCompatible`] for `F`.
///
/// This trait is meant only to be implemented for types in `netstack3_core`.
pub trait ContextFidlCompatible<F>: Sized {
type FromError;
type IntoError;
fn try_from_fidl_with_ctx<C: ConversionContext>(
ctx: &C,
fidl: F,
) -> Result<Self, Self::FromError>;
fn try_into_fidl_with_ctx<C: ConversionContext>(self, ctx: &C) -> Result<F, Self::IntoError>;
}
/// Defines a type that can be converted to a Core type `C`, given a context
/// that implements [`ConversionContext`].
///
/// This trait offers the convenient symmetrical for [`ContextFidlCompatible`]
/// and is automatically implemented for all types for which a
/// [`ContextFidlCompatible`] conversion is implemented.
pub trait ContextCoreCompatible<T>: Sized {
type FromError;
type IntoError;
fn try_from_core_with_ctx<C: ConversionContext>(
ctx: &C,
core: T,
) -> Result<Self, Self::FromError>;
fn try_into_core_with_ctx<C: ConversionContext>(self, ctx: &C) -> Result<T, Self::IntoError>;
}
impl<F, C> ContextCoreCompatible<C> for F
where
C: ContextFidlCompatible<F>,
{
type FromError = C::IntoError;
type IntoError = C::FromError;
fn try_from_core_with_ctx<X: ConversionContext>(
ctx: &X,
core: C,
) -> Result<Self, Self::FromError> {
core.try_into_fidl_with_ctx(ctx)
}
fn try_into_core_with_ctx<X: ConversionContext>(self, ctx: &X) -> Result<C, Self::IntoError> {
C::try_from_fidl_with_ctx(ctx, self)
}
}
#[derive(Debug)]
pub struct DeviceNotFoundError;
impl ContextFidlCompatible<u64> for DeviceId {
type FromError = DeviceNotFoundError;
type IntoError = DeviceNotFoundError;
fn try_from_fidl_with_ctx<C: ConversionContext>(
ctx: &C,
fidl: u64,
) -> Result<Self, Self::FromError> {
ctx.get_core_id(fidl).ok_or(DeviceNotFoundError)
}
fn try_into_fidl_with_ctx<C: ConversionContext>(self, ctx: &C) -> Result<u64, Self::IntoError> {
ctx.get_binding_id(self).ok_or(DeviceNotFoundError)
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum ForwardingConversionError {
DeviceNotFound,
TypeMismatch,
InvalidSubnet,
AddrClassError,
}
impl From<DeviceNotFoundError> for ForwardingConversionError {
fn from(_: DeviceNotFoundError) -> Self {
ForwardingConversionError::DeviceNotFound
}
}
impl From<InvalidSubnetError> for ForwardingConversionError {
fn from(_: InvalidSubnetError) -> Self {
ForwardingConversionError::InvalidSubnet
}
}
impl From<AddrClassError> for ForwardingConversionError {
fn from(_: AddrClassError) -> Self {
ForwardingConversionError::AddrClassError
}
}
impl From<ForwardingConversionError> for fidl_net_stack::Error {
fn from(fwd_error: ForwardingConversionError) -> Self {
fidl_net_stack::Error {
type_: match fwd_error {
ForwardingConversionError::DeviceNotFound => fidl_net_stack::ErrorType::NotFound,
ForwardingConversionError::TypeMismatch
| ForwardingConversionError::InvalidSubnet
| ForwardingConversionError::AddrClassError => {
fidl_net_stack::ErrorType::InvalidArgs
}
},
}
}
}
impl ContextFidlCompatible<fidl_net_stack::ForwardingDestination> for EntryDestEither<DeviceId> {
type FromError = ForwardingConversionError;
type IntoError = DeviceNotFoundError;
fn try_from_fidl_with_ctx<C: ConversionContext>(
ctx: &C,
fidl: fidl_net_stack::ForwardingDestination,
) -> Result<Self, Self::FromError> {
Ok(match fidl {
fidl_net_stack::ForwardingDestination::DeviceId(binding_id) => {
EntryDest::Local { device: binding_id.try_into_core_with_ctx(ctx)? }
}
fidl_net_stack::ForwardingDestination::NextHop(addr) => {
EntryDest::Remote { next_hop: addr.try_into_core()? }
}
})
}
fn try_into_fidl_with_ctx<C: ConversionContext>(
self,
ctx: &C,
) -> Result<fidl_net_stack::ForwardingDestination, Self::IntoError> {
Ok(match self {
EntryDest::Local { device } => {
fidl_net_stack::ForwardingDestination::DeviceId(device.try_into_fidl_with_ctx(ctx)?)
}
EntryDest::Remote { next_hop } => {
let next_hop: IpAddr = next_hop.into_addr().into();
fidl_net_stack::ForwardingDestination::NextHop(next_hop.into_fidl())
}
})
}
}
impl ContextFidlCompatible<fidl_net_stack::ForwardingEntry> for EntryEither<DeviceId> {
type FromError = ForwardingConversionError;
type IntoError = DeviceNotFoundError;
fn try_from_fidl_with_ctx<C: ConversionContext>(
ctx: &C,
fidl: fidl_net_stack::ForwardingEntry,
) -> Result<Self, Self::FromError> {
EntryEither::new(
fidl.subnet.try_into_core()?,
fidl.destination.try_into_core_with_ctx(ctx)?,
)
.ok_or(ForwardingConversionError::TypeMismatch)
}
fn try_into_fidl_with_ctx<C: ConversionContext>(
self,
ctx: &C,
) -> Result<fidl_net_stack::ForwardingEntry, Self::IntoError> {
let (subnet, dest) = self.into_subnet_dest();
Ok(fidl_net_stack::ForwardingEntry {
subnet: subnet.into_fidl(),
destination: dest.try_into_fidl_with_ctx(ctx)?,
})
}
}
#[cfg(test)]
mod tests {
use fidl_fuchsia_net as fidl_net;
use fidl_fuchsia_net_stack as fidl_net_stack;
use net_types::ethernet::Mac;
use net_types::ip::Subnet;
use std::convert::TryFrom;
use super::*;
use crate::eventloop::EventLoop;
struct FakeConversionContext {
binding: u64,
core: DeviceId,
}
impl FakeConversionContext {
fn new() -> Self {
// we need a valid context to be able to create DeviceIds, so
// we just create it, get the device id and then destroy everything
let (snd, rcv) = futures::channel::mpsc::unbounded();
let mut evt_loop = EventLoop::new_with_channels(snd, rcv);
let core =
evt_loop.ctx.state_mut().add_ethernet_device(Mac::new([1, 2, 3, 4, 5, 6]), 1500);
Self { binding: 1, core }
}
}
impl ConversionContext for FakeConversionContext {
fn get_core_id(&self, binding_id: u64) -> Option<DeviceId> {
if binding_id == self.binding {
Some(self.core)
} else {
None
}
}
fn get_binding_id(&self, core_id: DeviceId) -> Option<u64> {
if self.core == core_id {
Some(self.binding)
} else {
None
}
}
}
struct EmptyFakeConversionContext;
impl ConversionContext for EmptyFakeConversionContext {
fn get_core_id(&self, binding_id: u64) -> Option<DeviceId> {
None
}
fn get_binding_id(&self, core_id: DeviceId) -> Option<u64> {
None
}
}
fn create_addr_v4(bytes: [u8; 4]) -> (IpAddr, fidl_net::IpAddress) {
let core = IpAddr::V4(Ipv4Addr::from(bytes));
let fidl = fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: bytes });
(core, fidl)
}
fn create_addr_v6(bytes: [u8; 16]) -> (IpAddr, fidl_net::IpAddress) {
let core = IpAddr::V6(Ipv6Addr::from(bytes));
let fidl = fidl_net::IpAddress::Ipv6(fidl_net::Ipv6Address { addr: bytes });
(core, fidl)
}
fn create_subnet(
subnet: (IpAddr, fidl_net::IpAddress),
prefix: u8,
) -> (SubnetEither, fidl_net::Subnet) {
let (core, fidl) = subnet;
(
SubnetEither::new(core, prefix).unwrap(),
fidl_net::Subnet { addr: fidl, prefix_len: prefix },
)
}
fn create_addr_subnet(
addr: (IpAddr, fidl_net::IpAddress),
prefix: u8,
) -> (AddrSubnetEither, fidl_net_stack::InterfaceAddress) {
let (core, fidl) = addr;
(
AddrSubnetEither::new(core, prefix).unwrap(),
fidl_net_stack::InterfaceAddress { ip_address: fidl, prefix_len: prefix },
)
}
fn create_local_dest_entry(
binding: u64,
core: DeviceId,
) -> (EntryDestEither<DeviceId>, fidl_net_stack::ForwardingDestination) {
let core = EntryDest::<IpAddr, _>::Local { device: core };
let fidl = fidl_net_stack::ForwardingDestination::DeviceId(binding);
(core, fidl)
}
fn create_remote_dest_entry(
addr: (IpAddr, fidl_net::IpAddress),
) -> (EntryDestEither<DeviceId>, fidl_net_stack::ForwardingDestination) {
let (core, fidl) = addr;
let core = EntryDest::<IpAddr, _>::Remote { next_hop: SpecifiedAddr::new(core).unwrap() };
let fidl = fidl_net_stack::ForwardingDestination::NextHop(fidl);
(core, fidl)
}
fn create_forwarding_entry(
subnet: (SubnetEither, fidl_net::Subnet),
dest: (EntryDestEither<DeviceId>, fidl_net_stack::ForwardingDestination),
) -> (EntryEither<DeviceId>, fidl_net_stack::ForwardingEntry) {
let (core_s, fidl_s) = subnet;
let (core_d, fidl_d) = dest;
(
EntryEither::new(core_s, core_d).unwrap(),
fidl_net_stack::ForwardingEntry { subnet: fidl_s, destination: fidl_d },
)
}
#[test]
fn test_addr_v4() {
let bytes = [192, 168, 0, 1];
let (core, fidl) = create_addr_v4(bytes);
assert_eq!(core, fidl.into_core());
assert_eq!(fidl, core.into_fidl());
}
#[test]
fn test_addr_v6() {
let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let (core, fidl) = create_addr_v6(bytes);
assert_eq!(core, fidl.into_core());
assert_eq!(fidl, core.into_fidl());
}
#[test]
fn test_addr_subnet_v4() {
let bytes = [192, 168, 0, 1];
let prefix = 24;
let (core, fidl) = create_addr_subnet(create_addr_v4(bytes), prefix);
assert_eq!(fidl, core.into_fidl());
assert_eq!(core, fidl.try_into_core().unwrap());
}
#[test]
fn test_addr_subnet_v6() {
let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let prefix = 64;
let (core, fidl) = create_addr_subnet(create_addr_v6(bytes), prefix);
assert_eq!(fidl, core.into_fidl());
assert_eq!(core, fidl.try_into_core().unwrap());
}
#[test]
fn test_subnet_v4() {
let bytes = [192, 168, 0, 0];
let prefix = 24;
let (core, fidl) = create_subnet(create_addr_v4(bytes), prefix);
assert_eq!(fidl, core.into_fidl());
assert_eq!(core, fidl.try_into_core().unwrap());
}
#[test]
fn test_subnet_v6() {
let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
let prefix = 64;
let (core, fidl) = create_subnet(create_addr_v6(bytes), prefix);
assert_eq!(fidl, core.into_fidl());
assert_eq!(core, fidl.try_into_core().unwrap());
}
#[test]
fn test_entry_dest_device() {
let ctx = FakeConversionContext::new();
let (core, fidl) = create_local_dest_entry(ctx.binding, ctx.core);
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
let (_, fidl) = create_local_dest_entry(ctx.binding, ctx.core);
assert!(EntryDestEither::try_from_fidl_with_ctx(&EmptyFakeConversionContext, fidl).is_err());
assert!(fidl_net_stack::ForwardingDestination::try_from_core_with_ctx(
&EmptyFakeConversionContext,
core,
)
.is_err());
}
#[test]
fn test_entry_dest_v4() {
let bytes = [192, 168, 0, 1];
let ctx = EmptyFakeConversionContext;
let (core, fidl) = create_remote_dest_entry(create_addr_v4(bytes));
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
}
#[test]
fn test_entry_dest_v6() {
let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let ctx = EmptyFakeConversionContext;
let (core, fidl) = create_remote_dest_entry(create_addr_v6(bytes));
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
}
#[test]
fn test_forwarding_entry_v4() {
let dst_bytes = [192, 168, 0, 1];
let subnet_bytes = [192, 168, 0, 0];
let prefix = 24;
let ctx = EmptyFakeConversionContext;
let (core, fidl) = create_forwarding_entry(
create_subnet(create_addr_v4(subnet_bytes), prefix),
create_remote_dest_entry(create_addr_v4(dst_bytes)),
);
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
}
#[test]
fn test_forwarding_entry_v6() {
let dst_bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let subnet_bytes = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
let prefix = 64;
let ctx = EmptyFakeConversionContext;
let (core, fidl) = create_forwarding_entry(
create_subnet(create_addr_v6(subnet_bytes), prefix),
create_remote_dest_entry(create_addr_v6(dst_bytes)),
);
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
}
#[test]
fn test_forwarding_entry_device() {
let ctx = FakeConversionContext::new();
let subnet_bytes = [192, 168, 0, 0];
let prefix = 24;
let (core, fidl) = create_forwarding_entry(
create_subnet(create_addr_v4(subnet_bytes), prefix),
create_local_dest_entry(ctx.binding, ctx.core),
);
assert_eq!(fidl, core.try_into_fidl_with_ctx(&ctx).unwrap());
assert_eq!(core, fidl.try_into_core_with_ctx(&ctx).unwrap());
}
#[test]
fn test_forwarding_entry_errors() {
let valid_ctx = FakeConversionContext::new();
let ctx = EmptyFakeConversionContext;
let subnet_bytes = [192, 168, 0, 0];
let prefix = 24;
let bad_addr_bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let (core, mut fidl) = create_forwarding_entry(
create_subnet(create_addr_v4(subnet_bytes), prefix),
create_local_dest_entry(valid_ctx.binding, valid_ctx.core),
);
// check device not found works:
assert!(fidl_net_stack::ForwardingEntry::try_from_core_with_ctx(&ctx, core).is_err());
assert_eq!(
EntryEither::try_from_fidl_with_ctx(&ctx, fidl).unwrap_err(),
ForwardingConversionError::DeviceNotFound
);
// try with an invalid subnet (fidl is not Clone, so we keep
// re-generating it from core):
fidl = core.try_into_fidl_with_ctx(&valid_ctx).unwrap();
fidl.subnet.prefix_len = 64;
assert_eq!(
EntryEither::try_from_fidl_with_ctx(&ctx, fidl).unwrap_err(),
ForwardingConversionError::InvalidSubnet
);
// Try with a mismatched address:
fidl = core.try_into_fidl_with_ctx(&valid_ctx).unwrap();
fidl.destination = create_remote_dest_entry(create_addr_v6(bad_addr_bytes)).1;
assert_eq!(
EntryEither::try_from_fidl_with_ctx(&ctx, fidl).unwrap_err(),
ForwardingConversionError::TypeMismatch
);
}
}