| // 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::{ |
| convert::Infallible as Never, |
| fmt::Debug, |
| num::{NonZeroU16, NonZeroU64}, |
| ops::Deref, |
| sync::{ |
| atomic::{AtomicBool, Ordering}, |
| Arc, Weak, |
| }, |
| }; |
| |
| use explicit::UnreachableExt as _; |
| use fidl_fuchsia_net as fidl_net; |
| use fidl_fuchsia_net_interfaces as fnet_interfaces; |
| use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin; |
| use fidl_fuchsia_net_routes as fnet_routes; |
| use fidl_fuchsia_net_routes_ext as fnet_routes_ext; |
| use fidl_fuchsia_net_stack as fidl_net_stack; |
| use fidl_fuchsia_posix as fposix; |
| use fidl_fuchsia_posix_socket as fposix_socket; |
| use futures::{task::AtomicWaker, Future, FutureExt as _, Stream, StreamExt as _}; |
| use net_types::{ |
| ethernet::Mac, |
| ip::{ |
| AddrSubnetEither, AddrSubnetError, GenericOverIp, Ip, IpAddr, IpAddress, |
| IpInvariant as IpInv, Ipv4Addr, Ipv6Addr, SubnetEither, SubnetError, |
| }, |
| AddrAndZone, MulticastAddr, SpecifiedAddr, Witness, ZonedAddr, |
| }; |
| use netstack3_core::{ |
| device::{ArpConfiguration, ArpConfigurationUpdate, DeviceId, WeakDeviceId}, |
| error::{ExistsError, NotFoundError}, |
| neighbor::{NudUserConfig, NudUserConfigUpdate}, |
| routes::{ |
| AddRouteError, AddableEntry, AddableEntryEither, AddableMetric, Entry, EntryEither, Metric, |
| RawMetric, |
| }, |
| socket::{ |
| self as core_socket, MulticastInterfaceSelector, MulticastMembershipInterfaceSelector, |
| }, |
| sync::RemoveResourceResult, |
| types::WorkQueueReport, |
| }; |
| use packet_formats::utils::NonZeroDuration; |
| use tracing::debug; |
| |
| use crate::bindings::{ |
| devices::BindingId, |
| socket::{IntoErrno, IpSockAddrExt, SockAddr}, |
| BindingsCtx, |
| }; |
| |
| /// The value used to specify that a `ForwardingEntry.metric` is unset, and the |
| /// entry's metric should track the interface's routing metric. |
| const UNSET_FORWARDING_ENTRY_METRIC: u32 = 0; |
| |
| /// A signal used between Core and Bindings, whenever Bindings receive a |
| /// notification by the protocol (Core), it should kick the associated task |
| /// to do work. |
| #[derive(Debug)] |
| struct NeedsData { |
| ready: AtomicBool, |
| waker: AtomicWaker, |
| } |
| |
| impl Default for NeedsData { |
| fn default() -> NeedsData { |
| NeedsData { ready: AtomicBool::new(false), waker: AtomicWaker::new() } |
| } |
| } |
| |
| impl NeedsData { |
| fn poll_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<()> { |
| self.waker.register(cx.waker()); |
| match self.ready.compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed) { |
| Ok(_) => std::task::Poll::Ready(()), |
| Err(_) => std::task::Poll::Pending, |
| } |
| } |
| |
| fn schedule(&self) { |
| self.ready.store(true, Ordering::Release); |
| self.waker.wake(); |
| } |
| } |
| |
| impl Drop for NeedsData { |
| fn drop(&mut self) { |
| self.schedule() |
| } |
| } |
| |
| /// The notifier side of the underlying signal struct, it is meant to be held |
| /// by the Core side and schedule signals to be received by the Bindings. |
| #[derive(Default, Debug, Clone)] |
| pub(crate) struct NeedsDataNotifier { |
| inner: Arc<NeedsData>, |
| } |
| |
| impl NeedsDataNotifier { |
| pub(crate) fn schedule(&self) { |
| self.inner.schedule() |
| } |
| |
| pub(crate) fn watcher(&self) -> NeedsDataWatcher { |
| NeedsDataWatcher { inner: Arc::downgrade(&self.inner) } |
| } |
| } |
| |
| /// The receiver side of the underlying signal struct, it is meant to be held |
| /// by the Bindings side. It is a [`Stream`] of wakeups scheduled by the Core |
| /// and upon receiving those wakeups, Bindings should perform any blocked |
| /// work. |
| #[derive(Debug, Clone)] |
| pub(crate) struct NeedsDataWatcher { |
| inner: Weak<NeedsData>, |
| } |
| |
| impl Stream for NeedsDataWatcher { |
| type Item = (); |
| |
| fn poll_next( |
| self: std::pin::Pin<&mut Self>, |
| cx: &mut std::task::Context<'_>, |
| ) -> std::task::Poll<Option<Self::Item>> { |
| let this = self.get_mut(); |
| match this.inner.upgrade() { |
| None => std::task::Poll::Ready(None), |
| Some(needs_data) => { |
| std::task::Poll::Ready(Some(std::task::ready!(needs_data.poll_ready(cx)))) |
| } |
| } |
| } |
| } |
| |
| struct TaskWaitGroupInner { |
| counter: std::sync::atomic::AtomicUsize, |
| waker: AtomicWaker, |
| } |
| |
| /// Provides a means to wait on the completion of tasks spawned in |
| /// [`TaskWaitGroupSpawner`]. |
| /// |
| /// `TaskWaitGroup` provides a [`futures::Future`] implementation that will |
| /// resolve once the associated [`TaskGroupSpawner`] and all its clones are |
| /// dropped *and* all the tasks spawned from those have finished. |
| /// |
| /// The [`TaskWaitGroupSpawner`] and `TaskWaitGroup` pair provide a way to |
| /// ensure [`fuchsia_async::Task`] spawning + detaching and then joining without |
| /// keeping track of each individual task. |
| #[must_use = "Future must be polled to completion"] |
| pub(crate) struct TaskWaitGroup { |
| inner: Arc<TaskWaitGroupInner>, |
| } |
| |
| impl futures::Future for TaskWaitGroup { |
| type Output = (); |
| |
| fn poll( |
| self: std::pin::Pin<&mut Self>, |
| cx: &mut std::task::Context<'_>, |
| ) -> std::task::Poll<Self::Output> { |
| let Self { inner } = self.deref(); |
| let TaskWaitGroupInner { counter, waker } = inner.deref(); |
| // Optimistically check the counter once. Use the strongest ordering |
| // guarantees since we're not too worried about performance here. |
| if counter.load(Ordering::SeqCst) == 0 { |
| return std::task::Poll::Ready(()); |
| } |
| // Register the waker and check again. |
| waker.register(cx.waker()); |
| if counter.load(Ordering::SeqCst) == 0 { |
| return std::task::Poll::Ready(()); |
| } else { |
| return std::task::Poll::Pending; |
| } |
| } |
| } |
| |
| impl TaskWaitGroup { |
| /// Creates a new [`TaskWaitGroup`] and [`TaskWaitGroupSpawner`] pair. |
| pub(crate) fn new() -> (Self, TaskWaitGroupSpawner) { |
| let inner = Arc::new(TaskWaitGroupInner { |
| // Start counter at 1 because we're creating a spawner. |
| counter: std::sync::atomic::AtomicUsize::new(1), |
| waker: AtomicWaker::new(), |
| }); |
| (Self { inner: inner.clone() }, TaskWaitGroupSpawner { inner }) |
| } |
| } |
| |
| /// Provides a way to spawn [`fuchsia_async::Task`]s. |
| /// |
| /// See [`TaskWaitGroup`]. |
| pub(crate) struct TaskWaitGroupSpawner { |
| inner: Arc<TaskWaitGroupInner>, |
| } |
| |
| impl Clone for TaskWaitGroupSpawner { |
| fn clone(&self) -> Self { |
| let Self { inner } = self; |
| let TaskWaitGroupInner { counter, waker: _ } = inner.deref(); |
| // Use the strongest ordering guarantee we have. Spawning and finishing |
| // tasks is not going to be happening very frequently so prefer the |
| // safest ordering we have available. |
| let prev = counter.fetch_add(1, Ordering::SeqCst); |
| // Because count can only be increased from spawners and spawners |
| // themselves increase the count, assert that the previous value could |
| // not be zero. |
| assert!(prev != 0); |
| |
| Self { inner: inner.clone() } |
| } |
| } |
| |
| impl Drop for TaskWaitGroupSpawner { |
| fn drop(&mut self) { |
| let Self { inner } = self; |
| let TaskWaitGroupInner { counter, waker } = (*inner).deref(); |
| // Use the strongest ordering guarantee we have. Spawning and finishing |
| // tasks is not going to be happening very frequently so prefer the |
| // safest ordering we have available. |
| let prev = counter.fetch_sub(1, Ordering::SeqCst); |
| if prev == 1 { |
| // Just finished the last task, the counter is now at zero and we |
| // can wake any parked tasks. |
| waker.wake(); |
| } |
| } |
| } |
| |
| impl TaskWaitGroupSpawner { |
| /// Spawns the future `fut` in this wait group. |
| // fuchsia_async::Task::spawn tracks the caller and adds trace-level logging |
| // when tasks start and finish, allow track_caller here in instrumented |
| // builds so we can see who the real caller is. |
| #[cfg_attr(feature = "instrumented", track_caller)] |
| pub(crate) fn spawn<F: futures::Future<Output = ()> + Send + 'static>(&self, fut: F) { |
| let spawner = self.clone(); |
| // Spawn the task and detach. The executor will take care of it but |
| // we'll get notified when it finishes by `future.map`. We give it a |
| // clone of the spawner (increasing the counter by 1) and drop it when |
| // the future is complete (decreasing the counter by 1). |
| fuchsia_async::Task::spawn(fut.map(move |()| { |
| std::mem::drop(spawner); |
| })) |
| .detach(); |
| } |
| } |
| |
| /// Extracts common bounded work operations performed on [`NeedsDataWatcher`]. |
| /// |
| /// Runs the watcher loop until `watcher` is finished or `f` returns `None`. |
| pub(crate) async fn yielding_data_notifier_loop<F: FnMut() -> Option<WorkQueueReport>>( |
| mut watcher: NeedsDataWatcher, |
| mut f: F, |
| ) { |
| let mut yield_fut = futures::future::OptionFuture::default(); |
| loop { |
| // Loop while we are woken up to handle enqueued RX packets. |
| let r = futures::select! { |
| w = watcher.next().fuse() => w, |
| y = yield_fut => Some(y.expect("OptionFuture is only selected when non-empty")), |
| }; |
| |
| match r.and_then(|()| f()) { |
| Some(WorkQueueReport::AllDone) => (), |
| Some(WorkQueueReport::Pending) => { |
| // Yield the task to the executor once. |
| yield_fut = Some(async_utils::futures::YieldToExecutorOnce::new()).into(); |
| } |
| None => break, |
| } |
| } |
| } |
| |
| pub(crate) trait RemoveResourceResultExt<T> { |
| fn into_future(self) -> impl Future<Output = T>; |
| } |
| |
| impl<T, F> RemoveResourceResultExt<T> for RemoveResourceResult<T, F> |
| where |
| F: Future<Output = T>, |
| { |
| fn into_future(self) -> impl Future<Output = T> { |
| match self { |
| Self::Removed(r) => futures::future::Either::Left(futures::future::ready(r)), |
| Self::Deferred(d) => futures::future::Either::Right(d), |
| } |
| } |
| } |
| |
| /// A core type which can be fallibly converted from the FIDL type `F`. |
| /// |
| /// For all `C: TryFromFidl<F>`, we provide a blanket impl of |
| /// [`F: TryIntoCore<C>`]. |
| /// |
| /// [`F: TryIntoCore<C>`]: TryIntoCore |
| pub(crate) trait TryFromFidl<F>: Sized { |
| /// The type of error returned from [`try_from_fidl`]. |
| /// |
| /// [`try_from_fidl`]: TryFromFidl::try_from_fidl |
| type Error; |
| |
| /// Attempt to convert from `fidl` into an instance of `Self`. |
| fn try_from_fidl(fidl: F) -> Result<Self, Self::Error>; |
| } |
| |
| /// A core type which can be fallibly converted to the FIDL type `F`. |
| pub(crate) trait TryIntoFidl<F>: Sized { |
| /// The type of error returned from [`try_into_fidl`]. |
| /// |
| /// [`try_into_fidl`]: TryIntoFidl::try_into_fidl |
| type Error; |
| |
| /// Attempt to convert `self` into an instance of `F`. |
| fn try_into_fidl(self) -> Result<F, Self::Error>; |
| } |
| |
| /// A core type which can be infallibly converted into the FIDL type `F`. |
| /// |
| /// `IntoFidl<F>` extends [`TryIntoFidl<F, Error = Never>`], and provides the |
| /// infallible conversion method [`into_fidl`]. |
| /// |
| /// [`TryIntoFidl<F, Error = Never>`]: TryIntoFidl |
| /// [`into_fidl`]: IntoFidl::into_fidl |
| pub(crate) trait IntoFidl<F> { |
| /// Infallibly convert `self` into an instance of `F`. |
| fn into_fidl(self) -> F; |
| } |
| |
| impl<C: TryIntoFidl<F, Error = Never>, F> IntoFidl<F> for C { |
| fn into_fidl(self) -> F { |
| match self.try_into_fidl() { |
| Ok(f) => f, |
| Err(never) => match never {}, |
| } |
| } |
| } |
| |
| /// A FIDL type which can be fallibly converted into the core type `C`. |
| /// |
| /// `TryIntoCore<C>` is automatically implemented for all `F` where |
| /// [`C: TryFromFidl<F>`]. |
| /// |
| /// [`C: TryFromFidl<F>`]: TryFromFidl |
| pub(crate) trait TryIntoCore<C>: Sized { |
| /// The error returned on conversion failure. |
| type Error; |
| |
| /// Attempt to convert from `self` into an instance of `C`. |
| /// |
| /// This is equivalent to [`C::try_from_fidl(self)`]. |
| /// |
| /// [`C::try_from_fidl(self)`]: TryFromFidl::try_from_fidl |
| fn try_into_core(self) -> Result<C, Self::Error>; |
| } |
| |
| impl<F, C: TryFromFidl<F>> TryIntoCore<C> for F { |
| type Error = C::Error; |
| fn try_into_core(self) -> Result<C, Self::Error> { |
| C::try_from_fidl(self) |
| } |
| } |
| |
| /// A FIDL type which can be infallibly converted into the core type `C`. |
| /// |
| /// `IntoCore<C>` extends [`TryIntoCore<C>`] where `<C as TryFromFidl<_>>::Error |
| /// = Never`, and provides the infallible conversion method [`into_core`]. |
| /// |
| /// [`TryIntoCore<C>`]: TryIntoCore |
| /// [`into_core`]: IntoCore::into_core |
| pub(crate) trait IntoCore<C> { |
| /// Infallibly convert `self` into an instance of `C`. |
| fn into_core(self) -> C; |
| } |
| |
| impl<F, C: TryFromFidl<F, Error = Never>> IntoCore<C> for F { |
| fn into_core(self) -> C { |
| match self.try_into_core() { |
| Ok(c) => c, |
| Err(never) => match never {}, |
| } |
| } |
| } |
| |
| impl<T> TryIntoFidl<T> for Never { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<T, Never> { |
| match self {} |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net_stack::Error> for SubnetError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| Ok(fidl_net_stack::Error::InvalidArgs) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net_stack::Error> for AddrSubnetError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| Ok(fidl_net_stack::Error::InvalidArgs) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net_stack::Error> for ExistsError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| Ok(fidl_net_stack::Error::AlreadyExists) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net_stack::Error> for NotFoundError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| Ok(fidl_net_stack::Error::NotFound) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net_stack::Error> for AddRouteError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| match self { |
| AddRouteError::AlreadyExists => Ok(fidl_net_stack::Error::AlreadyExists), |
| AddRouteError::GatewayNotNeighbor => Ok(fidl_net_stack::Error::BadState), |
| } |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::IpAddress> for IpAddr { |
| type Error = Never; |
| |
| fn try_from_fidl(addr: fidl_net::IpAddress) -> Result<IpAddr, Never> { |
| match addr { |
| fidl_net::IpAddress::Ipv4(v4) => Ok(IpAddr::V4(v4.into_core())), |
| fidl_net::IpAddress::Ipv6(v6) => Ok(IpAddr::V6(v6.into_core())), |
| } |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::IpAddress> for IpAddr { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::IpAddress, Never> { |
| match self { |
| IpAddr::V4(addr) => Ok(fidl_net::IpAddress::Ipv4(addr.into_fidl())), |
| IpAddr::V6(addr) => Ok(fidl_net::IpAddress::Ipv6(addr.into_fidl())), |
| } |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::Ipv4Address> for Ipv4Addr { |
| type Error = Never; |
| |
| fn try_from_fidl(addr: fidl_net::Ipv4Address) -> Result<Ipv4Addr, Never> { |
| Ok(addr.addr.into()) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::Ipv4Address> for Ipv4Addr { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::Ipv4Address, Never> { |
| Ok(fidl_net::Ipv4Address { addr: self.ipv4_bytes() }) |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::Ipv6Address> for Ipv6Addr { |
| type Error = Never; |
| |
| fn try_from_fidl(addr: fidl_net::Ipv6Address) -> Result<Ipv6Addr, Never> { |
| Ok(addr.addr.into()) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::Ipv6Address> for Ipv6Addr { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::Ipv6Address, Never> { |
| Ok(fidl_net::Ipv6Address { addr: self.ipv6_bytes() }) |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::MacAddress> for Mac { |
| type Error = Never; |
| |
| fn try_from_fidl(mac: fidl_net::MacAddress) -> Result<Mac, Never> { |
| Ok(Mac::new(mac.octets)) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::MacAddress> for Mac { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::MacAddress, Never> { |
| Ok(fidl_net::MacAddress { octets: self.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). |
| #[derive(Debug)] |
| pub(crate) struct AddrClassError; |
| |
| // TODO(joshlf): Introduce a separate variant to `fidl_net_stack::Error` for |
| // `AddrClassError`? |
| impl TryIntoFidl<fidl_net_stack::Error> for AddrClassError { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net_stack::Error, Never> { |
| Ok(fidl_net_stack::Error::InvalidArgs) |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::IpAddress> for SpecifiedAddr<IpAddr> { |
| type Error = AddrClassError; |
| |
| fn try_from_fidl(fidl: fidl_net::IpAddress) -> Result<SpecifiedAddr<IpAddr>, AddrClassError> { |
| SpecifiedAddr::new(fidl.into_core()).ok_or(AddrClassError) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::IpAddress> for SpecifiedAddr<IpAddr> { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::IpAddress, Never> { |
| Ok(self.get().into_fidl()) |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::Subnet> for AddrSubnetEither { |
| type Error = AddrSubnetError; |
| |
| fn try_from_fidl(fidl: fidl_net::Subnet) -> Result<AddrSubnetEither, AddrSubnetError> { |
| AddrSubnetEither::new(fidl.addr.into_core(), fidl.prefix_len) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::Subnet> for AddrSubnetEither { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::Subnet, Never> { |
| let (addr, prefix) = self.addr_prefix(); |
| Ok(fidl_net::Subnet { addr: addr.into_fidl(), prefix_len: prefix }) |
| } |
| } |
| |
| impl TryFromFidl<fidl_net::Subnet> for SubnetEither { |
| type Error = SubnetError; |
| |
| fn try_from_fidl(fidl: fidl_net::Subnet) -> Result<SubnetEither, SubnetError> { |
| SubnetEither::new(fidl.addr.into_core(), fidl.prefix_len) |
| } |
| } |
| |
| impl TryIntoFidl<fidl_net::Subnet> for SubnetEither { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fidl_net::Subnet, Never> { |
| let (net, prefix) = self.net_prefix(); |
| Ok(fidl_net::Subnet { addr: net.into_fidl(), prefix_len: prefix }) |
| } |
| } |
| |
| impl TryFromFidl<fposix_socket::OptionalUint8> for Option<u8> { |
| type Error = Never; |
| |
| fn try_from_fidl(fidl: fposix_socket::OptionalUint8) -> Result<Self, Self::Error> { |
| Ok(match fidl { |
| fposix_socket::OptionalUint8::Unset(fposix_socket::Empty) => None, |
| fposix_socket::OptionalUint8::Value(u) => Some(u), |
| }) |
| } |
| } |
| |
| impl TryIntoFidl<fposix_socket::OptionalUint8> for Option<u8> { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fposix_socket::OptionalUint8, Self::Error> { |
| Ok(self |
| .map(fposix_socket::OptionalUint8::Value) |
| .unwrap_or(fposix_socket::OptionalUint8::Unset(fposix_socket::Empty))) |
| } |
| } |
| |
| impl TryIntoFidl<fnet_interfaces::AddressAssignmentState> for netstack3_core::ip::IpAddressState { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fnet_interfaces::AddressAssignmentState, Never> { |
| match self { |
| netstack3_core::ip::IpAddressState::Unavailable => { |
| Ok(fnet_interfaces::AddressAssignmentState::Unavailable) |
| } |
| netstack3_core::ip::IpAddressState::Assigned => { |
| Ok(fnet_interfaces::AddressAssignmentState::Assigned) |
| } |
| netstack3_core::ip::IpAddressState::Tentative => { |
| Ok(fnet_interfaces::AddressAssignmentState::Tentative) |
| } |
| } |
| } |
| } |
| |
| impl<A: IpAddress> TryIntoFidl<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (Option<SpecifiedAddr<A>>, NonZeroU16) |
| where |
| A::Version: IpSockAddrExt, |
| { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<<A::Version as IpSockAddrExt>::SocketAddress, Self::Error> { |
| let (addr, port) = self; |
| Ok(SockAddr::new(addr.map(|a| ZonedAddr::Unzoned(a).into()), port.get())) |
| } |
| } |
| |
| impl TryIntoFidl<fposix_socket::OptionalUint32> for Option<u32> { |
| type Error = Never; |
| |
| fn try_into_fidl(self) -> Result<fposix_socket::OptionalUint32, Self::Error> { |
| Ok(match self { |
| Some(value) => fposix_socket::OptionalUint32::Value(value), |
| None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty), |
| }) |
| } |
| } |
| |
| impl TryFromFidl<fposix_socket::OptionalUint32> for Option<u32> { |
| type Error = Never; |
| |
| fn try_from_fidl(fidl: fposix_socket::OptionalUint32) -> Result<Self, Self::Error> { |
| Ok(match fidl { |
| fposix_socket::OptionalUint32::Value(value) => Some(value), |
| fposix_socket::OptionalUint32::Unset(fposix_socket::Empty) => None, |
| }) |
| } |
| } |
| |
| pub(crate) enum MulticastMembershipConversionError { |
| AddrNotMulticast, |
| WrongIpVersion, |
| } |
| |
| impl<I: Ip> GenericOverIp<I> for MulticastMembershipConversionError { |
| type Type = Self; |
| } |
| |
| impl IntoErrno for MulticastMembershipConversionError { |
| fn into_errno(self) -> fposix::Errno { |
| match self { |
| Self::AddrNotMulticast => fposix::Errno::Einval, |
| Self::WrongIpVersion => fposix::Errno::Enoprotoopt, |
| } |
| } |
| } |
| |
| impl<A: IpAddress> TryFromFidl<fposix_socket::IpMulticastMembership> |
| for (MulticastAddr<A>, Option<MulticastInterfaceSelector<A, BindingId>>) |
| { |
| type Error = MulticastMembershipConversionError; |
| |
| fn try_from_fidl(fidl: fposix_socket::IpMulticastMembership) -> Result<Self, Self::Error> { |
| <A::Version as Ip>::map_ip( |
| IpInv(fidl), |
| |IpInv(fidl)| { |
| let fposix_socket::IpMulticastMembership { iface, local_addr, mcast_addr } = fidl; |
| let mcast_addr = MulticastAddr::new(mcast_addr.into_core()) |
| .ok_or(Self::Error::AddrNotMulticast)?; |
| // Match Linux behavior by ignoring the address if an interface |
| // identifier is provided. |
| let selector = BindingId::new(iface) |
| .map(MulticastInterfaceSelector::Interface) |
| .or_else(|| { |
| SpecifiedAddr::new(local_addr.into_core()) |
| .map(MulticastInterfaceSelector::LocalAddress) |
| }); |
| Ok((mcast_addr, selector)) |
| }, |
| |IpInv(_fidl)| Err(Self::Error::WrongIpVersion), |
| ) |
| } |
| } |
| |
| impl<A: IpAddress> TryFromFidl<fposix_socket::Ipv6MulticastMembership> |
| for (MulticastAddr<A>, Option<MulticastInterfaceSelector<A, BindingId>>) |
| { |
| type Error = MulticastMembershipConversionError; |
| |
| fn try_from_fidl(fidl: fposix_socket::Ipv6MulticastMembership) -> Result<Self, Self::Error> { |
| <A::Version as Ip>::map_ip( |
| IpInv(fidl), |
| |IpInv(_fidl)| Err(Self::Error::WrongIpVersion), |
| |IpInv(fidl)| { |
| let fposix_socket::Ipv6MulticastMembership { iface, mcast_addr } = fidl; |
| let mcast_addr = MulticastAddr::new(mcast_addr.into_core()) |
| .ok_or(Self::Error::AddrNotMulticast)?; |
| let selector = BindingId::new(iface).map(MulticastInterfaceSelector::Interface); |
| Ok((mcast_addr, selector)) |
| }, |
| ) |
| } |
| } |
| |
| /// Provides a stateful context for operations that require state-keeping to be |
| /// completed. |
| /// |
| /// `ConversionContext` is used by conversion functions in |
| /// [`TryFromFidlWithContext`] and [`TryIntoFidlWithContext`]. |
| pub(crate) 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: BindingId) -> Option<DeviceId<BindingsCtx>>; |
| |
| /// Converts a core identifier `DeviceId` to a FIDL-compatible [`BindingId`]. |
| fn get_binding_id(&self, core_id: DeviceId<BindingsCtx>) -> BindingId; |
| } |
| |
| /// A core type which can be fallibly converted from the FIDL type `F` given a |
| /// context that implements [`ConversionContext`]. |
| /// |
| /// For all `C: TryFromFidlWithContext<F>`, we provide a blanket impl of |
| /// [`F: TryIntoCoreWithContext<C>`]. |
| /// |
| /// [`F: TryIntoCoreWithContext<C>`]: TryIntoCoreWithContext |
| pub(crate) trait TryFromFidlWithContext<F>: Sized { |
| /// The type of error returned from [`try_from_fidl_with_ctx`]. |
| /// |
| /// [`try_from_fidl_with_ctx`]: TryFromFidlWithContext::try_from_fidl_with_ctx |
| type Error; |
| |
| /// Attempt to convert from `fidl` into an instance of `Self`. |
| fn try_from_fidl_with_ctx<C: ConversionContext>(ctx: &C, fidl: F) -> Result<Self, Self::Error>; |
| } |
| |
| /// A core type which can be fallibly converted to the FIDL type `F` given a |
| /// context that implements [`ConversionContext`]. |
| pub(crate) trait TryIntoFidlWithContext<F>: Sized { |
| /// The type of error returned from [`try_into_fidl_with_ctx`]. |
| /// |
| /// [`try_into_fidl_with_ctx`]: TryIntoFidlWithContext::try_into_fidl_with_ctx |
| type Error; |
| |
| /// Attempt to convert from `self` into an instance of `F`. |
| fn try_into_fidl_with_ctx<C: ConversionContext>(self, ctx: &C) -> Result<F, Self::Error>; |
| } |
| |
| /// A FIDL type which can be fallibly converted into the core type `C` given a |
| /// context that implements [`ConversionContext`]. |
| /// |
| /// `TryIntoCoreWithContext<C>` is automatically implemented for all `F` where |
| /// [`C: TryFromFidlWithContext<F>`]. |
| /// |
| /// [`C: TryFromFidlWithContext<F>`]: TryFromFidlWithContext |
| pub(crate) trait TryIntoCoreWithContext<C>: Sized { |
| /// The type of error returned from [`try_into_core_with_ctx`]. |
| /// |
| /// [`try_into_core_with_ctx`]: TryIntoCoreWithContext::try_into_core_with_ctx |
| type Error; |
| |
| /// Attempt to convert from `self` into an instance of `C`. |
| fn try_into_core_with_ctx<X: ConversionContext>(self, ctx: &X) -> Result<C, Self::Error>; |
| } |
| |
| impl<F, C: TryFromFidlWithContext<F>> TryIntoCoreWithContext<C> for F { |
| type Error = C::Error; |
| |
| fn try_into_core_with_ctx<X: ConversionContext>(self, ctx: &X) -> Result<C, Self::Error> { |
| C::try_from_fidl_with_ctx(ctx, self) |
| } |
| } |
| |
| pub(crate) struct UninstantiableFuture<O>(Never, std::marker::PhantomData<O>); |
| |
| impl<O: std::marker::Unpin> futures::Future for UninstantiableFuture<O> { |
| type Output = O; |
| |
| fn poll( |
| self: std::pin::Pin<&mut Self>, |
| _cx: &mut futures::task::Context<'_>, |
| ) -> futures::task::Poll<Self::Output> { |
| self.get_mut().uninstantiable_unreachable() |
| } |
| } |
| |
| impl<O> AsRef<Never> for UninstantiableFuture<O> { |
| fn as_ref(&self) -> &Never { |
| let Self(never, _) = self; |
| never |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub(crate) struct DeviceNotFoundError; |
| |
| #[derive(Debug, PartialEq)] |
| pub(crate) enum SocketAddressError { |
| Device(DeviceNotFoundError), |
| } |
| |
| impl IntoErrno for SocketAddressError { |
| fn into_errno(self) -> fposix::Errno { |
| match self { |
| SocketAddressError::Device(d) => d.into_errno(), |
| } |
| } |
| } |
| |
| impl<A: IpAddress, D> TryFromFidlWithContext<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (Option<ZonedAddr<SpecifiedAddr<A>, D>>, u16) |
| where |
| A::Version: IpSockAddrExt, |
| D: TryFromFidlWithContext<NonZeroU64, Error = DeviceNotFoundError>, |
| { |
| type Error = SocketAddressError; |
| |
| fn try_from_fidl_with_ctx<C: ConversionContext>( |
| ctx: &C, |
| fidl: <A::Version as IpSockAddrExt>::SocketAddress, |
| ) -> Result<Self, Self::Error> { |
| let port = fidl.port(); |
| let specified = match fidl.get_specified_addr() { |
| Some(addr) => addr, |
| None => return Ok((None, port)), |
| }; |
| |
| let zoned = match fidl.zone() { |
| Some(zone) => { |
| match AddrAndZone::new(specified, zone) { |
| None => { |
| // For conformance with Linux, allow callers to provide |
| // a scope ID for addresses that don't allow zones. |
| debug!("ignoring zone ({zone:?}) provided for address ({specified})"); |
| ZonedAddr::Unzoned(specified).into() |
| } |
| Some(addr_and_zone) => addr_and_zone |
| .try_map_zone(|zone| { |
| TryFromFidlWithContext::try_from_fidl_with_ctx(ctx, zone) |
| .map_err(SocketAddressError::Device) |
| }) |
| .map(|a| ZonedAddr::Zoned(a).into())?, |
| } |
| } |
| None => ZonedAddr::Unzoned(specified).into(), |
| }; |
| Ok((Some(zoned), port)) |
| } |
| } |
| |
| impl<A: IpAddress, D> TryIntoFidlWithContext<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (Option<ZonedAddr<SpecifiedAddr<A>, D>>, u16) |
| where |
| A::Version: IpSockAddrExt, |
| D: TryIntoFidlWithContext<NonZeroU64>, |
| { |
| type Error = D::Error; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<<A::Version as IpSockAddrExt>::SocketAddress, Self::Error> { |
| let (addr, port) = self; |
| let addr = addr |
| .map(|addr| { |
| Ok(match addr { |
| ZonedAddr::Unzoned(addr) => ZonedAddr::Unzoned(addr), |
| ZonedAddr::Zoned(z) => z |
| .try_map_zone(|zone| { |
| TryIntoFidlWithContext::try_into_fidl_with_ctx(zone, ctx) |
| })? |
| .into(), |
| } |
| .into()) |
| }) |
| .transpose()?; |
| Ok(SockAddr::new(addr, port)) |
| } |
| } |
| |
| impl<A: IpAddress, D> TryIntoFidlWithContext<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (Option<core_socket::StrictlyZonedAddr<A, SpecifiedAddr<A>, D>>, u16) |
| where |
| A::Version: IpSockAddrExt, |
| D: TryIntoFidlWithContext<NonZeroU64>, |
| { |
| type Error = D::Error; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<<A::Version as IpSockAddrExt>::SocketAddress, Self::Error> { |
| let (addr, port) = self; |
| (addr.map(core_socket::StrictlyZonedAddr::into_inner), port).try_into_fidl_with_ctx(ctx) |
| } |
| } |
| |
| impl<A: IpAddress, D> TryIntoFidlWithContext<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (ZonedAddr<SpecifiedAddr<A>, D>, NonZeroU16) |
| where |
| A::Version: IpSockAddrExt, |
| D: TryIntoFidlWithContext<NonZeroU64>, |
| { |
| type Error = D::Error; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<<A::Version as IpSockAddrExt>::SocketAddress, Self::Error> { |
| let (addr, port) = self; |
| (Some(addr), port.get()).try_into_fidl_with_ctx(ctx) |
| } |
| } |
| |
| impl<A: IpAddress, D> TryIntoFidlWithContext<<A::Version as IpSockAddrExt>::SocketAddress> |
| for (Option<ZonedAddr<SpecifiedAddr<A>, D>>, NonZeroU16) |
| where |
| A::Version: IpSockAddrExt, |
| D: TryIntoFidlWithContext<NonZeroU64>, |
| { |
| type Error = D::Error; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<<A::Version as IpSockAddrExt>::SocketAddress, Self::Error> { |
| let (addr, port) = self; |
| (addr, port.get()).try_into_fidl_with_ctx(ctx) |
| } |
| } |
| |
| impl<A, D1, D2> TryFromFidlWithContext<MulticastMembershipInterfaceSelector<A, D1>> |
| for MulticastMembershipInterfaceSelector<A, D2> |
| where |
| A: IpAddress, |
| D2: TryFromFidlWithContext<D1>, |
| { |
| type Error = D2::Error; |
| |
| fn try_from_fidl_with_ctx<C: ConversionContext>( |
| ctx: &C, |
| selector: MulticastMembershipInterfaceSelector<A, D1>, |
| ) -> Result<Self, Self::Error> { |
| use MulticastMembershipInterfaceSelector::*; |
| Ok(match selector { |
| Specified(MulticastInterfaceSelector::Interface(id)) => { |
| Specified(MulticastInterfaceSelector::Interface(id.try_into_core_with_ctx(ctx)?)) |
| } |
| Specified(MulticastInterfaceSelector::LocalAddress(addr)) => { |
| Specified(MulticastInterfaceSelector::LocalAddress(addr)) |
| } |
| AnyInterfaceWithRoute => AnyInterfaceWithRoute, |
| }) |
| } |
| } |
| |
| impl TryFromFidlWithContext<BindingId> for DeviceId<BindingsCtx> { |
| type Error = DeviceNotFoundError; |
| |
| fn try_from_fidl_with_ctx<C: ConversionContext>( |
| ctx: &C, |
| fidl: BindingId, |
| ) -> Result<Self, Self::Error> { |
| ctx.get_core_id(fidl).ok_or(DeviceNotFoundError) |
| } |
| } |
| |
| impl TryIntoFidlWithContext<BindingId> for DeviceId<BindingsCtx> { |
| type Error = Never; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>(self, ctx: &C) -> Result<BindingId, Never> { |
| Ok(ctx.get_binding_id(self)) |
| } |
| } |
| |
| impl TryIntoFidlWithContext<BindingId> for WeakDeviceId<BindingsCtx> { |
| type Error = DeviceNotFoundError; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<BindingId, DeviceNotFoundError> { |
| self.upgrade().map(|d| ctx.get_binding_id(d)).ok_or(DeviceNotFoundError) |
| } |
| } |
| |
| /// A wrapper type that provides an infallible conversion from a |
| /// [`WeakDeviceId`] to [`BindingId`]. |
| /// |
| /// By default we don't want to provide this conversion infallibly and hidden |
| /// behind the conversion traits, so `AllowBindingIdFromWeak` acts as an |
| /// explicit opt-in for that conversion. |
| pub(crate) struct AllowBindingIdFromWeak(pub(crate) WeakDeviceId<BindingsCtx>); |
| |
| impl TryIntoFidlWithContext<BindingId> for AllowBindingIdFromWeak { |
| type Error = Never; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>(self, _ctx: &C) -> Result<BindingId, Never> { |
| let Self(weak) = self; |
| Ok(weak.bindings_id().id) |
| } |
| } |
| |
| impl IntoErrno for DeviceNotFoundError { |
| fn into_errno(self) -> fposix::Errno { |
| fposix::Errno::Enodev |
| } |
| } |
| |
| #[derive(Debug, Eq, PartialEq)] |
| pub(crate) enum ForwardingConversionError { |
| DeviceNotFound, |
| TypeMismatch, |
| Subnet(SubnetError), |
| AddrClassError, |
| } |
| |
| impl From<DeviceNotFoundError> for ForwardingConversionError { |
| fn from(_: DeviceNotFoundError) -> Self { |
| ForwardingConversionError::DeviceNotFound |
| } |
| } |
| |
| impl From<SubnetError> for ForwardingConversionError { |
| fn from(err: SubnetError) -> Self { |
| ForwardingConversionError::Subnet(err) |
| } |
| } |
| |
| 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 { |
| match fwd_error { |
| ForwardingConversionError::DeviceNotFound => fidl_net_stack::Error::NotFound, |
| ForwardingConversionError::TypeMismatch |
| | ForwardingConversionError::Subnet(_) |
| | ForwardingConversionError::AddrClassError => fidl_net_stack::Error::InvalidArgs, |
| } |
| } |
| } |
| |
| impl TryFromFidlWithContext<fidl_net_stack::ForwardingEntry> |
| for AddableEntryEither<Option<DeviceId<BindingsCtx>>> |
| { |
| type Error = ForwardingConversionError; |
| |
| fn try_from_fidl_with_ctx<C: ConversionContext>( |
| ctx: &C, |
| fidl: fidl_net_stack::ForwardingEntry, |
| ) -> Result<AddableEntryEither<Option<DeviceId<BindingsCtx>>>, ForwardingConversionError> { |
| let fidl_net_stack::ForwardingEntry { subnet, device_id, next_hop, metric } = fidl; |
| let subnet = subnet.try_into_core()?; |
| let device = |
| BindingId::new(device_id).map(|d| d.try_into_core_with_ctx(ctx)).transpose()?; |
| let next_hop: Option<SpecifiedAddr<IpAddr>> = |
| next_hop.map(|next_hop| (*next_hop).try_into_core()).transpose()?; |
| let metric = if metric == UNSET_FORWARDING_ENTRY_METRIC { |
| AddableMetric::MetricTracksInterface |
| } else { |
| AddableMetric::ExplicitMetric(RawMetric(metric)) |
| }; |
| |
| Ok(match (subnet, device, next_hop.map(Into::into)) { |
| (subnet, device, None) => Self::without_gateway(subnet, device, metric), |
| (SubnetEither::V4(subnet), device, Some(IpAddr::V4(gateway))) => { |
| AddableEntry::with_gateway(subnet, device, gateway, metric).into() |
| } |
| (SubnetEither::V6(subnet), device, Some(IpAddr::V6(gateway))) => { |
| AddableEntry::with_gateway(subnet, device, gateway, metric).into() |
| } |
| (SubnetEither::V4(_), _, Some(IpAddr::V6(_))) |
| | (SubnetEither::V6(_), _, Some(IpAddr::V4(_))) => { |
| return Err(ForwardingConversionError::TypeMismatch) |
| } |
| }) |
| } |
| } |
| |
| #[derive(Debug, Copy, Clone)] |
| pub(crate) enum AddableEntryFromRoutesExtError { |
| UnknownAction, |
| DeviceNotFound, |
| } |
| |
| impl<I: Ip> TryFromFidlWithContext<fnet_routes_ext::Route<I>> |
| for AddableEntry<I::Addr, DeviceId<BindingsCtx>> |
| { |
| type Error = AddableEntryFromRoutesExtError; |
| |
| fn try_from_fidl_with_ctx<C: ConversionContext>( |
| ctx: &C, |
| fidl: fnet_routes_ext::Route<I>, |
| ) -> Result<Self, Self::Error> { |
| let fnet_routes_ext::Route { |
| destination, |
| action, |
| properties: |
| fnet_routes_ext::RouteProperties { |
| specified_properties: fnet_routes_ext::SpecifiedRouteProperties { metric }, |
| }, |
| } = fidl; |
| let fnet_routes_ext::RouteTarget { outbound_interface, next_hop } = match action { |
| fnet_routes_ext::RouteAction::Unknown => { |
| return Err(AddableEntryFromRoutesExtError::UnknownAction) |
| } |
| fnet_routes_ext::RouteAction::Forward(target) => target, |
| }; |
| let device: DeviceId<BindingsCtx> = BindingId::new(outbound_interface) |
| .ok_or(AddableEntryFromRoutesExtError::DeviceNotFound)? |
| .try_into_core_with_ctx(ctx) |
| .map_err(|DeviceNotFoundError| AddableEntryFromRoutesExtError::DeviceNotFound)?; |
| |
| let metric = match metric { |
| fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => { |
| AddableMetric::MetricTracksInterface |
| } |
| fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => { |
| AddableMetric::ExplicitMetric(RawMetric(metric)) |
| } |
| }; |
| |
| Ok(AddableEntry { subnet: destination, device, gateway: next_hop, metric }) |
| } |
| } |
| |
| impl TryIntoFidlWithContext<fidl_net_stack::ForwardingEntry> |
| for EntryEither<DeviceId<BindingsCtx>> |
| { |
| type Error = Never; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<fidl_net_stack::ForwardingEntry, Never> { |
| let (subnet, device, gateway, metric): ( |
| SubnetEither, |
| _, |
| Option<IpAddr<SpecifiedAddr<Ipv4Addr>, SpecifiedAddr<Ipv6Addr>>>, |
| _, |
| ) = match self { |
| EntryEither::V4(Entry { subnet, device, gateway, metric }) => { |
| (subnet.into(), device, gateway.map(|gateway| gateway.into()), metric) |
| } |
| EntryEither::V6(Entry { subnet, device, gateway, metric }) => { |
| (subnet.into(), device, gateway.map(|gateway| gateway.into()), metric) |
| } |
| }; |
| let RawMetric(metric) = metric.value(); |
| let device_id: BindingId = device.try_into_fidl_with_ctx(ctx)?; |
| let next_hop = gateway.map(|next_hop| { |
| let next_hop: SpecifiedAddr<IpAddr> = next_hop.into(); |
| Box::new(next_hop.into_fidl()) |
| }); |
| Ok(fidl_net_stack::ForwardingEntry { |
| subnet: subnet.into_fidl(), |
| device_id: device_id.get(), |
| next_hop, |
| metric: metric, |
| }) |
| } |
| } |
| |
| impl<I: Ip> TryIntoFidlWithContext<fnet_routes_ext::InstalledRoute<I>> |
| for Entry<I::Addr, DeviceId<BindingsCtx>> |
| { |
| type Error = Never; |
| |
| fn try_into_fidl_with_ctx<C: ConversionContext>( |
| self, |
| ctx: &C, |
| ) -> Result<fnet_routes_ext::InstalledRoute<I>, Never> { |
| let Entry { subnet, device, gateway, metric } = self; |
| let device: BindingId = device.try_into_fidl_with_ctx(ctx)?; |
| let specified_metric = match metric { |
| Metric::ExplicitMetric(value) => { |
| fnet_routes::SpecifiedMetric::ExplicitMetric(value.into()) |
| } |
| Metric::MetricTracksInterface(_value) => { |
| fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) |
| } |
| }; |
| Ok(fnet_routes_ext::InstalledRoute { |
| route: fnet_routes_ext::Route { |
| destination: subnet, |
| action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget { |
| outbound_interface: device.get(), |
| next_hop: gateway, |
| }), |
| properties: fnet_routes_ext::RouteProperties { |
| specified_properties: fnet_routes_ext::SpecifiedRouteProperties { |
| metric: specified_metric, |
| }, |
| }, |
| }, |
| effective_properties: fnet_routes_ext::EffectiveRouteProperties { |
| metric: metric.value().into(), |
| }, |
| }) |
| } |
| } |
| |
| #[derive(Debug)] |
| pub(crate) struct IllegalZeroValueError; |
| |
| #[derive(Debug)] |
| pub(crate) enum IllegalNonPositiveValueError { |
| Zero, |
| Negative, |
| } |
| |
| impl From<IllegalZeroValueError> for IllegalNonPositiveValueError { |
| fn from(_: IllegalZeroValueError) -> Self { |
| Self::Zero |
| } |
| } |
| |
| impl TryFromFidl<u16> for NonZeroU16 { |
| type Error = IllegalZeroValueError; |
| |
| fn try_from_fidl(fidl: u16) -> Result<Self, Self::Error> { |
| NonZeroU16::new(fidl).ok_or(IllegalZeroValueError) |
| } |
| } |
| |
| impl TryFromFidl<i64> for NonZeroDuration { |
| type Error = IllegalNonPositiveValueError; |
| |
| fn try_from_fidl(fidl: i64) -> Result<Self, Self::Error> { |
| NonZeroDuration::from_nanos( |
| u64::try_from(fidl).map_err(|_| IllegalNonPositiveValueError::Negative)?, |
| ) |
| .ok_or(IllegalNonPositiveValueError::Zero) |
| } |
| } |
| |
| impl TryFromFidl<fnet_interfaces_admin::NudConfiguration> for NudUserConfigUpdate { |
| type Error = IllegalNonPositiveValueError; |
| |
| fn try_from_fidl(fidl: fnet_interfaces_admin::NudConfiguration) -> Result<Self, Self::Error> { |
| let fnet_interfaces_admin::NudConfiguration { |
| max_multicast_solicitations, |
| max_unicast_solicitations, |
| base_reachable_time, |
| __source_breaking, |
| } = fidl; |
| Ok(NudUserConfigUpdate { |
| max_multicast_solicitations: max_multicast_solicitations |
| .map(TryIntoCore::try_into_core) |
| .transpose()?, |
| max_unicast_solicitations: max_unicast_solicitations |
| .map(TryIntoCore::try_into_core) |
| .transpose()?, |
| base_reachable_time: base_reachable_time.map(TryIntoCore::try_into_core).transpose()?, |
| ..Default::default() |
| }) |
| } |
| } |
| |
| impl IntoFidl<fnet_interfaces_admin::NudConfiguration> for NudUserConfigUpdate { |
| fn into_fidl(self) -> fnet_interfaces_admin::NudConfiguration { |
| let NudUserConfigUpdate { |
| max_unicast_solicitations, |
| max_multicast_solicitations, |
| base_reachable_time, |
| } = self; |
| fnet_interfaces_admin::NudConfiguration { |
| max_multicast_solicitations: max_multicast_solicitations.map(|c| c.get()), |
| max_unicast_solicitations: max_unicast_solicitations.map(|c| c.get()), |
| base_reachable_time: base_reachable_time.map(|c| { |
| // Even though `as_nanos` returns a `u128`, the value will |
| // always fit in an `i64` because it is either set via FIDL |
| // (stored as a `zx_duration_t`, i.e. `i64`) or learnt via |
| // the Reachable Time field in RA messages which is a 32-bit |
| // value in milliseconds. |
| c.get().as_nanos().try_into().unwrap() |
| }), |
| __source_breaking: fidl::marker::SourceBreaking, |
| } |
| } |
| } |
| |
| /// A helper function to transform a NudUserConfig to a NudUserConfigUpdate with |
| /// all the fields set so we can maximize reusing FIDL conversion functions. |
| fn nud_user_config_to_update(c: NudUserConfig) -> NudUserConfigUpdate { |
| let NudUserConfig { |
| max_multicast_solicitations, |
| max_unicast_solicitations, |
| base_reachable_time, |
| } = c; |
| NudUserConfigUpdate { |
| max_unicast_solicitations: Some(max_unicast_solicitations), |
| max_multicast_solicitations: Some(max_multicast_solicitations), |
| base_reachable_time: Some(base_reachable_time), |
| } |
| } |
| |
| impl IntoFidl<fnet_interfaces_admin::NudConfiguration> for NudUserConfig { |
| fn into_fidl(self) -> fnet_interfaces_admin::NudConfiguration { |
| nud_user_config_to_update(self).into_fidl() |
| } |
| } |
| |
| impl TryFromFidl<fnet_interfaces_admin::ArpConfiguration> for ArpConfigurationUpdate { |
| type Error = IllegalNonPositiveValueError; |
| |
| fn try_from_fidl(fidl: fnet_interfaces_admin::ArpConfiguration) -> Result<Self, Self::Error> { |
| let fnet_interfaces_admin::ArpConfiguration { nud, __source_breaking } = fidl; |
| Ok(ArpConfigurationUpdate { nud: nud.map(TryFromFidl::try_from_fidl).transpose()? }) |
| } |
| } |
| |
| impl IntoFidl<fnet_interfaces_admin::ArpConfiguration> for ArpConfigurationUpdate { |
| fn into_fidl(self) -> fnet_interfaces_admin::ArpConfiguration { |
| let ArpConfigurationUpdate { nud } = self; |
| fnet_interfaces_admin::ArpConfiguration { |
| nud: nud.map(IntoFidl::into_fidl), |
| __source_breaking: fidl::marker::SourceBreaking, |
| } |
| } |
| } |
| |
| impl IntoFidl<fnet_interfaces_admin::ArpConfiguration> for ArpConfiguration { |
| fn into_fidl(self) -> fnet_interfaces_admin::ArpConfiguration { |
| let ArpConfiguration { nud } = self; |
| ArpConfigurationUpdate { nud: Some(nud_user_config_to_update(nud)) }.into_fidl() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use fidl_fuchsia_net as fidl_net; |
| use fidl_fuchsia_net_ext::IntoExt; |
| |
| use net_declare::{net_ip_v4, net_ip_v6}; |
| use test_case::test_case; |
| |
| use crate::bindings::integration_tests::TestStack; |
| |
| use super::*; |
| |
| struct FakeConversionContext { |
| binding: BindingId, |
| core: DeviceId<BindingsCtx>, |
| test_stack: TestStack, |
| } |
| |
| impl FakeConversionContext { |
| async fn shutdown(self) { |
| let Self { binding, core, test_stack } = self; |
| std::mem::drop((binding, core)); |
| test_stack.shutdown().await |
| } |
| |
| async fn new() -> Self { |
| // Create a test stack to get a valid device ID. |
| let mut test_stack = TestStack::new(None); |
| let binding = BindingId::MIN; |
| test_stack.wait_for_interface_online(binding).await; |
| let ctx = test_stack.ctx(); |
| let core = ctx.bindings_ctx().get_core_id(binding).expect("should get core ID"); |
| Self { binding, core, test_stack } |
| } |
| } |
| |
| impl ConversionContext for FakeConversionContext { |
| fn get_core_id(&self, binding_id: BindingId) -> Option<DeviceId<BindingsCtx>> { |
| if binding_id == self.binding { |
| Some(self.core.clone()) |
| } else { |
| None |
| } |
| } |
| |
| fn get_binding_id(&self, core_id: DeviceId<BindingsCtx>) -> BindingId { |
| core_id.bindings_id().id |
| } |
| } |
| |
| struct EmptyFakeConversionContext; |
| impl ConversionContext for EmptyFakeConversionContext { |
| fn get_core_id(&self, _binding_id: BindingId) -> Option<DeviceId<BindingsCtx>> { |
| None |
| } |
| |
| fn get_binding_id(&self, core_id: DeviceId<BindingsCtx>) -> BindingId { |
| core_id.bindings_id().id |
| } |
| } |
| |
| 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::Subnet) { |
| let (core, fidl) = addr; |
| ( |
| AddrSubnetEither::new(core, prefix).unwrap(), |
| fidl_net::Subnet { addr: fidl, prefix_len: prefix }, |
| ) |
| } |
| |
| #[test] |
| fn 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 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 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 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 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 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 ip_address_state() { |
| use fnet_interfaces::AddressAssignmentState; |
| use netstack3_core::ip::IpAddressState; |
| assert_eq!(IpAddressState::Unavailable.into_fidl(), AddressAssignmentState::Unavailable); |
| assert_eq!(IpAddressState::Tentative.into_fidl(), AddressAssignmentState::Tentative); |
| assert_eq!(IpAddressState::Assigned.into_fidl(), AddressAssignmentState::Assigned); |
| } |
| |
| #[fixture::teardown(FakeConversionContext::shutdown)] |
| #[test_case( |
| fidl_net::Ipv6SocketAddress { |
| address: net_ip_v6!("fe80::1").into_ext(), |
| port: 8080, |
| zone_index: 2 |
| }, |
| SocketAddressError::Device(DeviceNotFoundError); |
| "IPv6 specified invalid zone")] |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn sock_addr_into_core_err<A: SockAddr>(addr: A, expected: SocketAddressError) |
| where |
| (Option<ZonedAddr<SpecifiedAddr<A::AddrType>, DeviceId<BindingsCtx>>>, u16): |
| TryFromFidlWithContext<A, Error = SocketAddressError>, |
| <A::AddrType as IpAddress>::Version: IpSockAddrExt<SocketAddress = A>, |
| DeviceId<BindingsCtx>: TryFromFidlWithContext<NonZeroU64, Error = DeviceNotFoundError>, |
| { |
| let ctx = FakeConversionContext::new().await; |
| let result: Result<(Option<_>, _), _> = addr.try_into_core_with_ctx(&ctx); |
| assert_eq!(result.expect_err("should fail"), expected); |
| ctx |
| } |
| |
| /// Placeholder for an ID that should be replaced with the real `DeviceId` |
| /// from the `FakeConversionContext`. |
| struct ReplaceWithCoreId; |
| |
| #[fixture::teardown(FakeConversionContext::shutdown)] |
| #[test_case( |
| fidl_net::Ipv4SocketAddress {address: net_ip_v4!("192.168.0.0").into_ext(), port: 8080}, |
| (Some(ZonedAddr::Unzoned(SpecifiedAddr::new(net_ip_v4!("192.168.0.0")).unwrap())), 8080); |
| "IPv4 specified")] |
| #[test_case( |
| fidl_net::Ipv4SocketAddress {address: net_ip_v4!("0.0.0.0").into_ext(), port: 8000}, |
| (None, 8000); |
| "IPv4 unspecified")] |
| #[test_case( |
| fidl_net::Ipv6SocketAddress { |
| address: net_ip_v6!("1:2:3:4::").into_ext(), |
| port: 8080, |
| zone_index: 0 |
| }, |
| (Some(ZonedAddr::Unzoned(SpecifiedAddr::new(net_ip_v6!("1:2:3:4::")).unwrap())), 8080); |
| "IPv6 specified no zone")] |
| #[test_case( |
| fidl_net::Ipv6SocketAddress { |
| address: net_ip_v6!("::").into_ext(), |
| port: 8080, |
| zone_index: 0, |
| }, |
| (None, 8080); |
| "IPv6 unspecified")] |
| #[test_case( |
| fidl_net::Ipv6SocketAddress { |
| address: net_ip_v6!("fe80::1").into_ext(), |
| port: 8080, |
| zone_index: 1 |
| }, |
| (Some( |
| ZonedAddr::Zoned(AddrAndZone::new(SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap(), ReplaceWithCoreId).unwrap()) |
| ), 8080); |
| "IPv6 specified valid zone")] |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn sock_addr_conversion_reversible<A: SockAddr + Eq + Clone>( |
| addr: A, |
| (zoned, port): (Option<ZonedAddr<SpecifiedAddr<A::AddrType>, ReplaceWithCoreId>>, u16), |
| ) where |
| (Option<ZonedAddr<SpecifiedAddr<A::AddrType>, DeviceId<BindingsCtx>>>, u16): |
| TryFromFidlWithContext<A, Error = SocketAddressError> + TryIntoFidlWithContext<A>, |
| <(Option<ZonedAddr<SpecifiedAddr<A::AddrType>, DeviceId<BindingsCtx>>>, u16) as |
| TryIntoFidlWithContext<A>>::Error: Debug, |
| <A::AddrType as IpAddress>::Version: IpSockAddrExt<SocketAddress = A>, |
| DeviceId<BindingsCtx>: |
| TryFromFidlWithContext<NonZeroU64, Error = DeviceNotFoundError>, |
| { |
| let ctx = FakeConversionContext::new().await; |
| let zoned = zoned.map(|z| match z { |
| ZonedAddr::Unzoned(z) => ZonedAddr::Unzoned(z).into(), |
| ZonedAddr::Zoned(z) => { |
| ZonedAddr::Zoned(z.map_zone(|ReplaceWithCoreId| ctx.core.clone())).into() |
| } |
| }); |
| |
| let result: (Option<ZonedAddr<_, _>>, _) = |
| addr.clone().try_into_core_with_ctx(&ctx).expect("into core should succeed"); |
| assert_eq!(result, (zoned, port)); |
| |
| let result = result.try_into_fidl_with_ctx(&ctx).expect("reverse should succeed"); |
| assert_eq!(result, addr); |
| ctx |
| } |
| |
| // Verify that the unnecessary zone IDs are ignored and result in |
| // `Unzoned` addresses. This is a regression test for |
| // https://fxbug.dev/329694011. |
| #[fixture::teardown(FakeConversionContext::shutdown)] |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn sock_addr_conversion_with_unnecessary_zone_id() { |
| let ctx = FakeConversionContext::new().await; |
| const GLOBAL_IPV6_ADDR: Ipv6Addr = net_ip_v6!("a:b:c:d::"); |
| const PORT: u16 = 8080; |
| let fidl_addr = fidl_net::Ipv6SocketAddress { |
| address: GLOBAL_IPV6_ADDR.into_ext(), |
| port: PORT, |
| zone_index: 1, |
| }; |
| let expected_addr: ZonedAddr<_, DeviceId<BindingsCtx>> = |
| ZonedAddr::Unzoned(SpecifiedAddr::new(GLOBAL_IPV6_ADDR).unwrap()); |
| let addr: (Option<ZonedAddr<_, _>>, _) = |
| fidl_addr.try_into_core_with_ctx(&ctx).expect("into core should succeed"); |
| assert_eq!(addr, (Some(expected_addr), PORT)); |
| ctx |
| } |
| |
| #[test] |
| fn test_unzoned_ip_port_into_fidl() { |
| let ip = net_ip_v4!("1.7.2.4"); |
| let port = 3893; |
| assert_eq!( |
| (SpecifiedAddr::new(ip), NonZeroU16::new(port).unwrap()).into_fidl(), |
| fidl_net::Ipv4SocketAddress { address: ip.into_ext(), port } |
| ); |
| |
| let ip = net_ip_v6!("1:2:3:4:5::"); |
| assert_eq!( |
| (SpecifiedAddr::new(ip), NonZeroU16::new(port).unwrap()).into_fidl(), |
| fidl_net::Ipv6SocketAddress { address: ip.into_ext(), port, zone_index: 0 } |
| ); |
| } |
| |
| #[fixture::teardown(FakeConversionContext::shutdown)] |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn device_id_from_bindings_id() { |
| let ctx = FakeConversionContext::new().await; |
| let id = ctx.binding; |
| let device_id: DeviceId<_> = id.try_into_core_with_ctx(&ctx).unwrap(); |
| assert_eq!(device_id, ctx.core); |
| |
| let bad_id = id.checked_add(1).unwrap(); |
| assert_eq!(bad_id.try_into_core_with_ctx(&ctx), Err::<DeviceId<_>, _>(DeviceNotFoundError)); |
| ctx |
| } |
| |
| #[test] |
| fn optional_u8_conversion() { |
| let empty = fposix_socket::OptionalUint8::Unset(fposix_socket::Empty); |
| let empty_core: Option<u8> = empty.into_core(); |
| assert_eq!(empty_core, None); |
| assert_eq!(empty_core.into_fidl(), empty); |
| |
| let value = fposix_socket::OptionalUint8::Value(46); |
| let value_core: Option<u8> = value.into_core(); |
| assert_eq!(value_core, Some(46)); |
| assert_eq!(value_core.into_fidl(), value); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn wait_group_waits_spawner_drop() { |
| let (mut wait_group, spawner) = TaskWaitGroup::new(); |
| assert_eq!(futures::poll!(&mut wait_group), futures::task::Poll::Pending); |
| std::mem::drop(spawner); |
| assert_eq!(futures::poll!(&mut wait_group), futures::task::Poll::Ready(())); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn wait_group_waits_futures() { |
| let (mut wait_group, spawner) = TaskWaitGroup::new(); |
| assert_eq!(futures::poll!(&mut wait_group), futures::task::Poll::Pending); |
| let (sender, receiver) = futures::channel::oneshot::channel(); |
| spawner.spawn(async move { |
| receiver.await.unwrap(); |
| }); |
| std::mem::drop(spawner); |
| |
| // Yield this for a while to the executor while ensuring the wait group |
| // hasn't finished. |
| for _ in 0..50 { |
| async_utils::futures::YieldToExecutorOnce::new().await; |
| assert_eq!(futures::poll!(&mut wait_group), futures::task::Poll::Pending); |
| } |
| |
| sender.send(()).unwrap(); |
| // Now wait_group should finish. |
| wait_group.await; |
| } |
| } |