| // Copyright 2018 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. |
| |
| //! Testing-related utilities. |
| |
| #![cfg(any(test, feature = "testutils"))] |
| |
| use alloc::borrow::ToOwned; |
| use alloc::collections::HashMap; |
| use alloc::sync::Arc; |
| use alloc::vec; |
| use alloc::vec::Vec; |
| use assert_matches::assert_matches; |
| |
| use core::borrow::Borrow; |
| use core::convert::Infallible as Never; |
| use core::ffi::CStr; |
| use core::fmt::Debug; |
| use core::hash::Hash; |
| use core::ops::{Deref, DerefMut}; |
| use core::time::Duration; |
| |
| use derivative::Derivative; |
| use net_types::ethernet::Mac; |
| use net_types::ip::{ |
| AddrSubnet, AddrSubnetEither, GenericOverIp, Ip, IpAddr, IpAddress, IpInvariant, IpVersion, |
| Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Mtu, Subnet, SubnetEither, |
| }; |
| use net_types::{MulticastAddr, SpecifiedAddr, UnicastAddr, Witness as _}; |
| use netstack3_base::sync::{DynDebugReferences, Mutex}; |
| use netstack3_base::testutil::{ |
| FakeCryptoRng, FakeFrameCtx, FakeInstant, FakeNetwork, FakeNetworkLinks, FakeNetworkSpec, |
| FakeTimerCtx, FakeTimerCtxExt, MonotonicIdentifier, TestAddrs, WithFakeFrameContext, |
| WithFakeTimerContext, |
| }; |
| use netstack3_base::{ |
| AddressResolutionFailed, CtxPair, DeferredResourceRemovalContext, EventContext, |
| FrameDestination, InstantBindingsTypes, InstantContext, LinkDevice, NotFoundError, |
| ReferenceNotifiers, RemoveResourceResult, RngContext, TimerBindingsTypes, TimerContext, |
| TimerHandler, TracingContext, WorkQueueReport, |
| }; |
| use netstack3_device::ethernet::{ |
| EthernetCreationProperties, EthernetDeviceId, EthernetLinkDevice, EthernetWeakDeviceId, |
| RecvEthernetFrameMeta, |
| }; |
| use netstack3_device::loopback::{LoopbackCreationProperties, LoopbackDevice, LoopbackDeviceId}; |
| use netstack3_device::pure_ip::{PureIpDeviceId, PureIpWeakDeviceId}; |
| use netstack3_device::queue::{ReceiveQueueBindingsContext, TransmitQueueBindingsContext}; |
| use netstack3_device::socket::{DeviceSocketBindingsContext, DeviceSocketTypes}; |
| use netstack3_device::testutil::IPV6_MIN_IMPLIED_MAX_FRAME_SIZE; |
| use netstack3_device::{ |
| self as device, DeviceId, DeviceLayerEventDispatcher, DeviceLayerStateTypes, DeviceLayerTypes, |
| DeviceSendFrameError, WeakDeviceId, |
| }; |
| use netstack3_filter::{FilterBindingsTypes, FilterTimerId}; |
| use netstack3_icmp_echo::{IcmpEchoBindingsContext, IcmpEchoBindingsTypes, IcmpSocketId}; |
| use netstack3_ip::device::{ |
| IpDeviceConfiguration, IpDeviceConfigurationUpdate, IpDeviceEvent, |
| Ipv4DeviceConfigurationUpdate, Ipv6DeviceConfigurationUpdate, |
| }; |
| use netstack3_ip::nud::{self, LinkResolutionContext, LinkResolutionNotifier}; |
| use netstack3_ip::raw::{RawIpSocketId, RawIpSocketsBindingsContext, RawIpSocketsBindingsTypes}; |
| use netstack3_ip::{ |
| self as ip, AddRouteError, AddableEntryEither, AddableMetric, IpLayerEvent, IpLayerTimerId, |
| RawMetric, |
| }; |
| use netstack3_tcp::testutil::{ClientBuffers, ProvidedBuffers, TestSendBuffer}; |
| use netstack3_tcp::{BufferSizes, RingBuffer, TcpBindingsTypes}; |
| use netstack3_udp::{UdpBindingsTypes, UdpReceiveBindingsContext, UdpSocketId}; |
| use packet::{Buf, BufferMut}; |
| use zerocopy::ByteSlice; |
| |
| use crate::api::CoreApi; |
| use crate::context::prelude::*; |
| use crate::context::UnlockedCoreCtx; |
| use crate::state::{StackState, StackStateBuilder}; |
| use crate::time::{TimerId, TimerIdInner}; |
| use crate::{BindingsContext, BindingsTypes, IpExt}; |
| |
| /// The default interface routing metric for test interfaces. |
| pub const DEFAULT_INTERFACE_METRIC: RawMetric = RawMetric(100); |
| |
| /// Context available during the execution of the netstack. |
| pub type Ctx<BT> = CtxPair<StackState<BT>, BT>; |
| |
| /// Extensions to [`CtxPair`] when it holds a full stack state. |
| pub trait CtxPairExt<BC: BindingsContext> { |
| /// Retrieves the core and bindings context, respectively. |
| /// |
| /// This function can be used to call into non-api core functions that want |
| /// a core context. |
| fn contexts(&mut self) -> (UnlockedCoreCtx<'_, BC>, &mut BC); |
| |
| /// Retrieves a [`CoreApi`] from this context pair. |
| fn core_api(&mut self) -> CoreApi<'_, &mut BC> { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| CoreApi::new(CtxPair { core_ctx, bindings_ctx }) |
| } |
| |
| /// Like [`CtxPairExt::contexts`], but retrieves only the core context. |
| fn core_ctx(&self) -> UnlockedCoreCtx<'_, BC>; |
| |
| /// Retrieves a [`TestApi`] from this context pair. |
| fn test_api(&mut self) -> TestApi<'_, BC> { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| TestApi(core_ctx, bindings_ctx) |
| } |
| |
| /// Shortcut for [`FakeTimerCtxExt::trigger_next_timer`]. |
| fn trigger_next_timer<Id>(&mut self) -> Option<Id> |
| where |
| BC: FakeTimerCtxExt<Id>, |
| for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>, |
| { |
| let (mut core_ctx, bindings_ctx) = self.contexts(); |
| bindings_ctx.trigger_next_timer(&mut core_ctx) |
| } |
| |
| /// Shortcut for [`FakeTimerCtxExt::trigger_timers_for`]. |
| fn trigger_timers_for<Id>(&mut self, duration: Duration) -> Vec<Id> |
| where |
| BC: FakeTimerCtxExt<Id>, |
| for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>, |
| { |
| let (mut core_ctx, bindings_ctx) = self.contexts(); |
| bindings_ctx.trigger_timers_for(duration, &mut core_ctx) |
| } |
| |
| /// Shortcut for [`FaketimerCtx::trigger_timers_until_instant`]. |
| fn trigger_timers_until_instant<Id>(&mut self, instant: FakeInstant) -> Vec<Id> |
| where |
| BC: FakeTimerCtxExt<Id>, |
| for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>, |
| { |
| let (mut core_ctx, bindings_ctx) = self.contexts(); |
| bindings_ctx.trigger_timers_until_instant(instant, &mut core_ctx) |
| } |
| |
| /// Shortcut for [`FakeTimerCtxExt::trigger_timers_until_and_expect_unordered`]. |
| fn trigger_timers_until_and_expect_unordered<Id, I: IntoIterator<Item = Id>>( |
| &mut self, |
| instant: FakeInstant, |
| timers: I, |
| ) where |
| Id: Debug + Hash + Eq, |
| BC: FakeTimerCtxExt<Id>, |
| for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>, |
| { |
| let (mut core_ctx, bindings_ctx) = self.contexts(); |
| bindings_ctx.trigger_timers_until_and_expect_unordered(instant, timers, &mut core_ctx) |
| } |
| } |
| |
| impl<CC, BC> CtxPairExt<BC> for CtxPair<CC, BC> |
| where |
| CC: Borrow<StackState<BC>>, |
| BC: BindingsContext, |
| { |
| fn contexts(&mut self) -> (UnlockedCoreCtx<'_, BC>, &mut BC) { |
| let Self { core_ctx, bindings_ctx } = self; |
| (UnlockedCoreCtx::new(CC::borrow(core_ctx)), bindings_ctx) |
| } |
| |
| fn core_ctx(&self) -> UnlockedCoreCtx<'_, BC> { |
| UnlockedCoreCtx::new(CC::borrow(&self.core_ctx)) |
| } |
| } |
| |
| /// An API struct for test utilities. |
| pub struct TestApi<'a, BT: BindingsTypes>(UnlockedCoreCtx<'a, BT>, &'a mut BT); |
| |
| impl<'l, BC> TestApi<'l, BC> |
| where |
| BC: BindingsContext, |
| { |
| fn contexts(&mut self) -> (&mut UnlockedCoreCtx<'l, BC>, &mut BC) { |
| let Self(core_ctx, bindings_ctx) = self; |
| (core_ctx, bindings_ctx) |
| } |
| |
| fn core_api(&mut self) -> CoreApi<'_, &mut BC> { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| let core_ctx = core_ctx.as_owned(); |
| CoreApi::new(CtxPair { core_ctx, bindings_ctx }) |
| } |
| |
| /// Joins the multicast group `multicast_addr` for `device`. |
| #[netstack3_macros::context_ip_bounds(A::Version, BC, crate)] |
| pub fn join_ip_multicast<A: IpAddress>( |
| &mut self, |
| device: &DeviceId<BC>, |
| multicast_addr: MulticastAddr<A>, |
| ) where |
| A::Version: IpExt, |
| { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| ip::device::join_ip_multicast::<A::Version, _, _>( |
| core_ctx, |
| bindings_ctx, |
| device, |
| multicast_addr, |
| ); |
| } |
| |
| /// Leaves the multicast group `multicast_addr` for `device`. |
| #[netstack3_macros::context_ip_bounds(A::Version, BC, crate)] |
| pub fn leave_ip_multicast<A: IpAddress>( |
| &mut self, |
| device: &DeviceId<BC>, |
| multicast_addr: MulticastAddr<A>, |
| ) where |
| A::Version: IpExt, |
| { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| ip::device::leave_ip_multicast::<A::Version, _, _>( |
| core_ctx, |
| bindings_ctx, |
| device, |
| multicast_addr, |
| ); |
| } |
| |
| /// Returns whether `device` is in the multicast group `addr`. |
| #[netstack3_macros::context_ip_bounds(A::Version, BC, crate)] |
| pub fn is_in_ip_multicast<A: IpAddress>( |
| &mut self, |
| device: &DeviceId<BC>, |
| addr: MulticastAddr<A>, |
| ) -> bool |
| where |
| A::Version: IpExt, |
| { |
| use ip::{ |
| AddressStatus, IpDeviceStateContext, IpLayerIpExt, Ipv4PresentAddressStatus, |
| Ipv6PresentAddressStatus, |
| }; |
| |
| let (core_ctx, _) = self.contexts(); |
| let addr_status = IpDeviceStateContext::<A::Version, _>::address_status_for_device( |
| core_ctx, |
| addr.into_specified(), |
| device, |
| ); |
| let status = match addr_status { |
| AddressStatus::Present(p) => p, |
| AddressStatus::Unassigned => return false, |
| }; |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<I: IpLayerIpExt>(I::AddressStatus); |
| A::Version::map_ip( |
| Wrap(status), |
| |Wrap(v4)| match v4 { |
| Ipv4PresentAddressStatus::Multicast => true, |
| Ipv4PresentAddressStatus::LimitedBroadcast |
| | Ipv4PresentAddressStatus::SubnetBroadcast |
| | Ipv4PresentAddressStatus::LoopbackSubnet |
| | Ipv4PresentAddressStatus::Unicast => false, |
| }, |
| |Wrap(v6)| match v6 { |
| Ipv6PresentAddressStatus::Multicast => true, |
| Ipv6PresentAddressStatus::UnicastAssigned |
| | Ipv6PresentAddressStatus::UnicastTentative => false, |
| }, |
| ) |
| } |
| |
| /// Receive an IP packet from a device. |
| /// |
| /// `receive_ip_packet` injects a packet directly at the IP layer for this |
| /// context. |
| pub fn receive_ip_packet<I: Ip, B: BufferMut>( |
| &mut self, |
| device: &DeviceId<BC>, |
| frame_dst: Option<FrameDestination>, |
| buffer: B, |
| ) { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| match I::VERSION { |
| IpVersion::V4 => { |
| ip::receive_ipv4_packet(core_ctx, bindings_ctx, device, frame_dst, buffer) |
| } |
| IpVersion::V6 => { |
| ip::receive_ipv6_packet(core_ctx, bindings_ctx, device, frame_dst, buffer) |
| } |
| } |
| } |
| |
| /// Add a route directly to the forwarding table. |
| pub fn add_route( |
| &mut self, |
| entry: AddableEntryEither<DeviceId<BC>>, |
| ) -> Result<(), AddRouteError> { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| match entry { |
| AddableEntryEither::V4(entry) => { |
| ip::testutil::add_route::<Ipv4, _, _>(core_ctx, bindings_ctx, entry) |
| } |
| AddableEntryEither::V6(entry) => { |
| ip::testutil::add_route::<Ipv6, _, _>(core_ctx, bindings_ctx, entry) |
| } |
| } |
| } |
| |
| /// Delete a route from the forwarding table, returning `Err` if no route |
| /// was found to be deleted. |
| pub fn del_routes_to_subnet( |
| &mut self, |
| subnet: net_types::ip::SubnetEither, |
| ) -> Result<(), NotFoundError> { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| match subnet { |
| SubnetEither::V4(subnet) => { |
| ip::testutil::del_routes_to_subnet::<Ipv4, _, _>(core_ctx, bindings_ctx, subnet) |
| } |
| SubnetEither::V6(subnet) => { |
| ip::testutil::del_routes_to_subnet::<Ipv6, _, _>(core_ctx, bindings_ctx, subnet) |
| } |
| } |
| } |
| |
| /// Deletes all routes targeting `device`. |
| pub fn del_device_routes(&mut self, device: &DeviceId<BC>) { |
| let (core_ctx, bindings_ctx) = self.contexts(); |
| ip::testutil::del_device_routes::<Ipv4, _, _>(core_ctx, bindings_ctx, device); |
| ip::testutil::del_device_routes::<Ipv6, _, _>(core_ctx, bindings_ctx, device); |
| } |
| |
| /// Removes all of the routes through the device, then removes the device. |
| pub fn clear_routes_and_remove_ethernet_device( |
| &mut self, |
| ethernet_device: EthernetDeviceId<BC>, |
| ) { |
| let device_id = ethernet_device.into(); |
| self.del_device_routes(&device_id); |
| let ethernet_device = assert_matches!(device_id, DeviceId::Ethernet(id) => id); |
| match self.core_api().device().remove_device(ethernet_device) { |
| RemoveResourceResult::Removed(_external_state) => {} |
| RemoveResourceResult::Deferred(_reference_receiver) => { |
| panic!("failed to remove ethernet device") |
| } |
| } |
| } |
| |
| /// Enables or disables the device for IP version `I` and returns whether it |
| /// was enabled before. |
| #[netstack3_macros::context_ip_bounds(I, BC, crate)] |
| pub fn set_ip_device_enabled<I: IpExt>( |
| &mut self, |
| device: &DeviceId<BC>, |
| enabled: bool, |
| ) -> bool { |
| let update = |
| IpDeviceConfigurationUpdate { ip_enabled: Some(enabled), ..Default::default() }; |
| let prev = |
| self.core_api().device_ip::<I>().update_configuration(device, update.into()).unwrap(); |
| prev.as_ref().ip_enabled.unwrap() |
| } |
| |
| /// Enables `device`. |
| pub fn enable_device(&mut self, device: &DeviceId<BC>) { |
| let _was_enabled: bool = self.set_ip_device_enabled::<Ipv4>(device, true); |
| let _was_enabled: bool = self.set_ip_device_enabled::<Ipv6>(device, true); |
| } |
| |
| /// Enables or disables IP packet routing on `device`. |
| #[netstack3_macros::context_ip_bounds(I, BC, crate)] |
| pub fn set_forwarding_enabled<I: IpExt>(&mut self, device: &DeviceId<BC>, enabled: bool) { |
| let _config = self |
| .core_api() |
| .device_ip::<I>() |
| .update_configuration( |
| device, |
| IpDeviceConfigurationUpdate { |
| forwarding_enabled: Some(enabled), |
| ..Default::default() |
| } |
| .into(), |
| ) |
| .unwrap(); |
| } |
| |
| /// Returns whether IP packet routing is enabled on `device`. |
| #[netstack3_macros::context_ip_bounds(I, BC, crate)] |
| pub fn is_forwarding_enabled<I: IpExt>(&mut self, device: &DeviceId<BC>) -> bool { |
| let configuration = self.core_api().device_ip::<I>().get_configuration(device); |
| let IpDeviceConfiguration { forwarding_enabled, .. } = configuration.as_ref(); |
| *forwarding_enabled |
| } |
| |
| /// Adds a loopback device with the IPv4/IPv6 loopback addresses assigned. |
| pub fn add_loopback(&mut self) -> LoopbackDeviceId<BC> |
| where |
| <BC as DeviceLayerStateTypes>::DeviceIdentifier: Default, |
| <BC as DeviceLayerStateTypes>::LoopbackDeviceState: Default, |
| { |
| let loopback_id = self.core_api().device::<LoopbackDevice>().add_device_with_default_state( |
| LoopbackCreationProperties { mtu: Mtu::new(u32::MAX) }, |
| DEFAULT_INTERFACE_METRIC, |
| ); |
| let device_id: DeviceId<_> = loopback_id.clone().into(); |
| self.enable_device(&device_id); |
| |
| self.core_api() |
| .device_ip::<Ipv4>() |
| .add_ip_addr_subnet( |
| &device_id, |
| AddrSubnet::from_witness(Ipv4::LOOPBACK_ADDRESS, Ipv4::LOOPBACK_SUBNET.prefix()) |
| .unwrap(), |
| ) |
| .unwrap(); |
| |
| self.core_api() |
| .device_ip::<Ipv6>() |
| .add_ip_addr_subnet( |
| &device_id, |
| AddrSubnet::from_witness(Ipv6::LOOPBACK_ADDRESS, Ipv6::LOOPBACK_SUBNET.prefix()) |
| .unwrap(), |
| ) |
| .unwrap(); |
| loopback_id |
| } |
| } |
| |
| impl<'a> TestApi<'a, FakeBindingsCtx> { |
| /// Handles any pending frames and returns true if any frames that were in |
| /// the RX queue were processed. |
| pub fn handle_queued_rx_packets(&mut self) -> bool { |
| let mut handled = false; |
| loop { |
| let (_, bindings_ctx) = self.contexts(); |
| let rx_available = core::mem::take(&mut bindings_ctx.state_mut().rx_available); |
| if rx_available.len() == 0 { |
| break handled; |
| } |
| handled = true; |
| for id in rx_available.into_iter() { |
| loop { |
| match self.core_api().receive_queue().handle_queued_frames(&id) { |
| WorkQueueReport::AllDone => break, |
| WorkQueueReport::Pending => (), |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| #[derive(Default)] |
| /// Bindings context state held by [`FakeBindingsCtx`]. |
| pub struct FakeBindingsCtxState { |
| icmpv4_replies: |
| HashMap<IcmpSocketId<Ipv4, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>, |
| icmpv6_replies: |
| HashMap<IcmpSocketId<Ipv6, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>, |
| udpv4_received: |
| HashMap<UdpSocketId<Ipv4, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>, |
| udpv6_received: |
| HashMap<UdpSocketId<Ipv6, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>, |
| /// IDs with rx queue signaled available. |
| pub rx_available: Vec<LoopbackDeviceId<FakeBindingsCtx>>, |
| /// IDs with tx queue signaled available. |
| pub tx_available: Vec<DeviceId<FakeBindingsCtx>>, |
| } |
| |
| impl FakeBindingsCtxState { |
| pub(crate) fn udp_state_mut<I: IpExt>( |
| &mut self, |
| ) -> &mut HashMap<UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>> |
| { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrapper<'a, I: IpExt>( |
| &'a mut HashMap< |
| UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, |
| Vec<Vec<u8>>, |
| >, |
| ); |
| let Wrapper(map) = I::map_ip_out::<_, Wrapper<'_, I>>( |
| self, |
| |this| Wrapper(&mut this.udpv4_received), |
| |this| Wrapper(&mut this.udpv6_received), |
| ); |
| map |
| } |
| } |
| |
| /// Shorthand for [`Ctx`] with a [`FakeBindingsCtx`]. |
| pub type FakeCtx = Ctx<FakeBindingsCtx>; |
| /// Shorthand for [`StackState`] that uses a [`FakeBindingsCtx`]. |
| pub type FakeCoreCtx = StackState<FakeBindingsCtx>; |
| |
| type InnerFakeBindingsCtx = netstack3_base::testutil::FakeBindingsCtx< |
| TimerId<FakeBindingsCtx>, |
| DispatchedEvent, |
| FakeBindingsCtxState, |
| DispatchedFrame, |
| >; |
| |
| /// Test-only implementation of [`BindingsContext`]. |
| #[derive(Default, Clone)] |
| pub struct FakeBindingsCtx(Arc<Mutex<InnerFakeBindingsCtx>>); |
| |
| /// A wrapper type that makes it easier to implement `Deref` (and optionally |
| /// `DerefMut`) for a value that is protected by a lock. |
| /// |
| /// The first field is the type that provides access to the inner value, |
| /// probably a lock guard. The second and third fields are functions that, given |
| /// the first field, provide shared and mutable access (respectively) to the |
| /// inner value. |
| // TODO(https://github.com/rust-lang/rust/issues/117108): Replace this with |
| // mapped mutex guards once stable. |
| struct Wrapper<S, Callback, CallbackMut>(S, Callback, CallbackMut); |
| |
| impl<T: ?Sized, S: Deref, Callback: for<'a> Fn(&'a <S as Deref>::Target) -> &'a T, CallbackMut> |
| Deref for Wrapper<S, Callback, CallbackMut> |
| { |
| type Target = T; |
| |
| fn deref(&self) -> &T { |
| let Self(guard, f, _) = self; |
| let target = guard.deref(); |
| f(target) |
| } |
| } |
| |
| impl< |
| T: ?Sized, |
| S: DerefMut, |
| Callback: for<'a> Fn(&'a <S as Deref>::Target) -> &'a T, |
| CallbackMut: for<'a> Fn(&'a mut <S as Deref>::Target) -> &'a mut T, |
| > DerefMut for Wrapper<S, Callback, CallbackMut> |
| { |
| fn deref_mut(&mut self) -> &mut T { |
| let Self(guard, _, f) = self; |
| let target = guard.deref_mut(); |
| f(target) |
| } |
| } |
| |
| impl FakeBindingsCtx { |
| fn with_inner<F: FnOnce(&InnerFakeBindingsCtx) -> O, O>(&self, f: F) -> O { |
| let Self(this) = self; |
| let locked = this.lock(); |
| f(&*locked) |
| } |
| |
| fn with_inner_mut<F: FnOnce(&mut InnerFakeBindingsCtx) -> O, O>(&self, f: F) -> O { |
| let Self(this) = self; |
| let mut locked = this.lock(); |
| f(&mut *locked) |
| } |
| |
| /// Gets the fake timer context. |
| pub fn timer_ctx(&self) -> impl Deref<Target = FakeTimerCtx<TimerId<Self>>> + '_ { |
| // NB: Helper function is required to satisfy lifetime requirements of |
| // borrow. |
| fn get_timers<'a>( |
| i: &'a InnerFakeBindingsCtx, |
| ) -> &'a FakeTimerCtx<TimerId<FakeBindingsCtx>> { |
| &i.timers |
| } |
| Wrapper(self.0.lock(), get_timers, ()) |
| } |
| |
| /// Returns a mutable reference guard to [`FakeBindingsCtxState`]. |
| pub fn state_mut(&mut self) -> impl DerefMut<Target = FakeBindingsCtxState> + '_ { |
| // NB: Helper functions are required to satisfy lifetime requirements of |
| // borrow. |
| fn get_state<'a>(i: &'a InnerFakeBindingsCtx) -> &'a FakeBindingsCtxState { |
| &i.state |
| } |
| fn get_state_mut<'a>(i: &'a mut InnerFakeBindingsCtx) -> &'a mut FakeBindingsCtxState { |
| &mut i.state |
| } |
| Wrapper(self.0.lock(), get_state, get_state_mut) |
| } |
| |
| /// Copy all ethernet frames sent so far. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the there are non-Ethernet frames stored. |
| pub fn copy_ethernet_frames( |
| &mut self, |
| ) -> Vec<(EthernetWeakDeviceId<FakeBindingsCtx>, Vec<u8>)> { |
| self.with_inner_mut(|ctx| { |
| ctx.frames |
| .frames() |
| .into_iter() |
| .map(|(meta, frame)| match meta { |
| DispatchedFrame::Ethernet(eth) => (eth.clone(), frame.clone()), |
| DispatchedFrame::PureIp(ip) => panic!("unexpected IP packet {ip:?}: {frame:?}"), |
| }) |
| .collect() |
| }) |
| } |
| |
| /// Take all ethernet frames sent so far. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the there are non-Ethernet frames stored. |
| pub fn take_ethernet_frames( |
| &mut self, |
| ) -> Vec<(EthernetWeakDeviceId<FakeBindingsCtx>, Vec<u8>)> { |
| self.with_inner_mut(|ctx| { |
| ctx.frames |
| .take_frames() |
| .into_iter() |
| .map(|(meta, frame)| match meta { |
| DispatchedFrame::Ethernet(eth) => (eth, frame), |
| DispatchedFrame::PureIp(ip) => panic!("unexpected IP packet {ip:?}: {frame:?}"), |
| }) |
| .collect() |
| }) |
| } |
| |
| /// Take all IP frames sent so far. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the there are non-IP frames stored. |
| pub fn take_ip_frames(&mut self) -> Vec<(PureIpDeviceAndIpVersion<FakeBindingsCtx>, Vec<u8>)> { |
| self.with_inner_mut(|ctx| { |
| ctx.frames |
| .take_frames() |
| .into_iter() |
| .map(|(meta, frame)| match meta { |
| DispatchedFrame::Ethernet(eth) => { |
| panic!("unexpected Ethernet frame {eth:?}: {frame:?}") |
| } |
| DispatchedFrame::PureIp(ip) => (ip, frame), |
| }) |
| .collect() |
| }) |
| } |
| |
| /// Takes all the events stored in the fake context. |
| pub fn take_events(&mut self) -> Vec<DispatchedEvent> { |
| self.with_inner_mut(|ctx| ctx.events.take()) |
| } |
| |
| /// Takes all the received ICMP replies for a given `conn`. |
| pub fn take_icmp_replies<I: IpExt>( |
| &mut self, |
| conn: &IcmpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, |
| ) -> Vec<Vec<u8>> { |
| I::map_ip_in( |
| (IpInvariant(self), conn), |
| |(IpInvariant(this), conn)| this.state_mut().icmpv4_replies.remove(conn), |
| |(IpInvariant(this), conn)| this.state_mut().icmpv6_replies.remove(conn), |
| ) |
| .unwrap_or_else(Vec::default) |
| } |
| |
| /// Takes all received UDP frames from the fake bindings context. |
| pub fn take_udp_received<I: IpExt>( |
| &mut self, |
| conn: &UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, |
| ) -> Vec<Vec<u8>> { |
| self.state_mut().udp_state_mut::<I>().remove(conn).unwrap_or_else(Vec::default) |
| } |
| } |
| |
| impl FilterBindingsTypes for FakeBindingsCtx { |
| type DeviceClass = (); |
| } |
| |
| impl WithFakeTimerContext<TimerId<FakeBindingsCtx>> for FakeBindingsCtx { |
| fn with_fake_timer_ctx<O, F: FnOnce(&FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>( |
| &self, |
| f: F, |
| ) -> O { |
| self.with_inner(|ctx| f(&ctx.timers)) |
| } |
| |
| fn with_fake_timer_ctx_mut<O, F: FnOnce(&mut FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>( |
| &mut self, |
| f: F, |
| ) -> O { |
| self.with_inner_mut(|ctx| f(&mut ctx.timers)) |
| } |
| } |
| |
| impl WithFakeFrameContext<DispatchedFrame> for FakeBindingsCtx { |
| fn with_fake_frame_ctx_mut<O, F: FnOnce(&mut FakeFrameCtx<DispatchedFrame>) -> O>( |
| &mut self, |
| f: F, |
| ) -> O { |
| self.with_inner_mut(|ctx| f(&mut ctx.frames)) |
| } |
| } |
| |
| impl InstantBindingsTypes for FakeBindingsCtx { |
| type Instant = FakeInstant; |
| } |
| |
| impl InstantContext for FakeBindingsCtx { |
| fn now(&self) -> FakeInstant { |
| self.with_inner(|ctx| ctx.now()) |
| } |
| } |
| |
| impl TimerBindingsTypes for FakeBindingsCtx { |
| type Timer = <FakeTimerCtx<TimerId<Self>> as TimerBindingsTypes>::Timer; |
| type DispatchId = TimerId<Self>; |
| } |
| |
| impl TimerContext for FakeBindingsCtx { |
| fn new_timer(&mut self, id: Self::DispatchId) -> Self::Timer { |
| self.with_inner_mut(|ctx| ctx.new_timer(id)) |
| } |
| |
| fn schedule_timer_instant( |
| &mut self, |
| time: Self::Instant, |
| timer: &mut Self::Timer, |
| ) -> Option<Self::Instant> { |
| // Filter out conntrack GC timers. We don't need conntrack GC in most |
| // tests, and this causes issues with tests that are expecting the |
| // netstack to quiesce. |
| match timer.dispatch_id.0 { |
| TimerIdInner::IpLayer(IpLayerTimerId::FilterTimerv4(FilterTimerId::ConntrackGc(_))) |
| | TimerIdInner::IpLayer(IpLayerTimerId::FilterTimerv6(FilterTimerId::ConntrackGc(_))) => { |
| return None |
| } |
| _ => {} |
| } |
| self.with_inner_mut(|ctx| ctx.schedule_timer_instant(time, timer)) |
| } |
| |
| fn cancel_timer(&mut self, timer: &mut Self::Timer) -> Option<Self::Instant> { |
| self.with_inner_mut(|ctx| ctx.cancel_timer(timer)) |
| } |
| |
| fn scheduled_instant(&self, timer: &mut Self::Timer) -> Option<Self::Instant> { |
| self.with_inner_mut(|ctx| ctx.scheduled_instant(timer)) |
| } |
| } |
| |
| impl RngContext for FakeBindingsCtx { |
| type Rng<'a> = FakeCryptoRng; |
| |
| fn rng(&mut self) -> Self::Rng<'_> { |
| let Self(this) = self; |
| this.lock().rng() |
| } |
| } |
| |
| impl<T: Into<DispatchedEvent>> EventContext<T> for FakeBindingsCtx { |
| fn on_event(&mut self, event: T) { |
| self.with_inner_mut(|ctx| ctx.events.on_event(event.into())) |
| } |
| } |
| |
| impl TracingContext for FakeBindingsCtx { |
| type DurationScope = (); |
| |
| fn duration(&self, _: &'static CStr) {} |
| } |
| |
| impl TcpBindingsTypes for FakeBindingsCtx { |
| type ReceiveBuffer = Arc<Mutex<RingBuffer>>; |
| |
| type SendBuffer = TestSendBuffer; |
| |
| type ReturnedBuffers = ClientBuffers; |
| |
| type ListenerNotifierOrProvidedBuffers = ProvidedBuffers; |
| |
| fn new_passive_open_buffers( |
| buffer_sizes: BufferSizes, |
| ) -> (Self::ReceiveBuffer, Self::SendBuffer, Self::ReturnedBuffers) { |
| let client = ClientBuffers::new(buffer_sizes); |
| ( |
| Arc::clone(&client.receive), |
| TestSendBuffer::new(Arc::clone(&client.send), RingBuffer::default()), |
| client, |
| ) |
| } |
| |
| fn default_buffer_sizes() -> BufferSizes { |
| // Use the test-only default impl. |
| BufferSizes::default() |
| } |
| } |
| |
| impl ReferenceNotifiers for FakeBindingsCtx { |
| type ReferenceReceiver<T: 'static> = Never; |
| |
| type ReferenceNotifier<T: Send + 'static> = Never; |
| |
| fn new_reference_notifier<T: Send + 'static>( |
| debug_references: DynDebugReferences, |
| ) -> (Self::ReferenceNotifier<T>, Self::ReferenceReceiver<T>) { |
| // NB: We don't want deferred destruction in core tests. These are |
| // always single-threaded and single-task, and we want to encourage |
| // explicit cleanup. |
| panic!( |
| "FakeBindingsCtx can't create deferred reference notifiers for type {}: \ |
| debug_references={debug_references:?}", |
| core::any::type_name::<T>() |
| ); |
| } |
| } |
| |
| impl DeferredResourceRemovalContext for FakeBindingsCtx { |
| fn defer_removal<T: Send + 'static>(&mut self, receiver: Self::ReferenceReceiver<T>) { |
| match receiver {} |
| } |
| } |
| |
| /// A link resolution notifier that ignores all notifications. |
| #[derive(Debug)] |
| pub struct NoOpLinkResolutionNotifier; |
| |
| impl<D: LinkDevice> LinkResolutionContext<D> for FakeBindingsCtx { |
| type Notifier = NoOpLinkResolutionNotifier; |
| } |
| |
| impl<D: LinkDevice> LinkResolutionNotifier<D> for NoOpLinkResolutionNotifier { |
| type Observer = (); |
| |
| fn new() -> (Self, Self::Observer) { |
| (NoOpLinkResolutionNotifier, ()) |
| } |
| |
| fn notify(self, _result: Result<D::Address, AddressResolutionFailed>) {} |
| } |
| |
| #[derive(Clone)] |
| struct DeviceConfig { |
| mac: UnicastAddr<Mac>, |
| addr_subnet: Option<AddrSubnetEither>, |
| ipv4_config: Option<Ipv4DeviceConfigurationUpdate>, |
| ipv6_config: Option<Ipv6DeviceConfigurationUpdate>, |
| } |
| |
| /// A builder for `FakeCtx`s. |
| /// |
| /// A `FakeCtxBuilder` is capable of storing the configuration of a network |
| /// stack including forwarding table entries, devices and their assigned |
| /// addresses and configurations, ARP table entries, etc. It can be built using |
| /// `build`, producing a `FakeCtx` with all of the appropriate state configured. |
| #[derive(Clone, Default)] |
| pub struct FakeCtxBuilder { |
| devices: Vec<DeviceConfig>, |
| // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available. |
| arp_table_entries: Vec<(usize, SpecifiedAddr<Ipv4Addr>, UnicastAddr<Mac>)>, |
| ndp_table_entries: Vec<(usize, UnicastAddr<Ipv6Addr>, UnicastAddr<Mac>)>, |
| // usize refers to index into devices Vec. |
| device_routes: Vec<(SubnetEither, usize)>, |
| } |
| |
| impl FakeCtxBuilder { |
| /// Construct a `FakeCtxBuilder` from a `TestAddrs`. |
| pub fn with_addrs<A: IpAddress>(addrs: TestAddrs<A>) -> FakeCtxBuilder { |
| assert!(addrs.subnet.contains(&addrs.local_ip)); |
| assert!(addrs.subnet.contains(&addrs.remote_ip)); |
| |
| let mut builder = FakeCtxBuilder::default(); |
| builder.devices.push(DeviceConfig { |
| mac: addrs.local_mac, |
| addr_subnet: Some( |
| AddrSubnetEither::new(addrs.local_ip.get().into(), addrs.subnet.prefix()).unwrap(), |
| ), |
| ipv4_config: None, |
| ipv6_config: None, |
| }); |
| |
| match addrs.remote_ip.into() { |
| IpAddr::V4(ip) => builder.arp_table_entries.push((0, ip, addrs.remote_mac)), |
| IpAddr::V6(ip) => builder.ndp_table_entries.push(( |
| 0, |
| UnicastAddr::new(ip.get()).unwrap(), |
| addrs.remote_mac, |
| )), |
| }; |
| |
| // Even with fixed ipv4 address we can have IPv6 link local addresses |
| // pre-cached. |
| builder.ndp_table_entries.push(( |
| 0, |
| addrs.remote_mac.to_ipv6_link_local().addr().get(), |
| addrs.remote_mac, |
| )); |
| |
| builder.device_routes.push((addrs.subnet.into(), 0)); |
| builder |
| } |
| |
| /// Add a device. |
| /// |
| /// `add_device` returns a key which can be used to refer to the device in |
| /// future calls to `add_arp_table_entry` and `add_device_route`. |
| pub fn add_device(&mut self, mac: UnicastAddr<Mac>) -> usize { |
| let idx = self.devices.len(); |
| self.devices.push(DeviceConfig { |
| mac, |
| addr_subnet: None, |
| ipv4_config: None, |
| ipv6_config: None, |
| }); |
| idx |
| } |
| |
| /// Add a device with an IPv4 and IPv6 configuration. |
| /// |
| /// `add_device_with_config` is like `add_device`, except that it takes an |
| /// IPv4 and IPv6 configuration to apply to the device when it is enabled. |
| pub fn add_device_with_config( |
| &mut self, |
| mac: UnicastAddr<Mac>, |
| ipv4_config: Ipv4DeviceConfigurationUpdate, |
| ipv6_config: Ipv6DeviceConfigurationUpdate, |
| ) -> usize { |
| let idx = self.devices.len(); |
| self.devices.push(DeviceConfig { |
| mac, |
| addr_subnet: None, |
| ipv4_config: Some(ipv4_config), |
| ipv6_config: Some(ipv6_config), |
| }); |
| idx |
| } |
| |
| /// Add a device with an associated IP address. |
| /// |
| /// `add_device_with_ip` is like `add_device`, except that it takes an |
| /// associated IP address and subnet to assign to the device. |
| pub fn add_device_with_ip<A: IpAddress>( |
| &mut self, |
| mac: UnicastAddr<Mac>, |
| ip: A, |
| subnet: Subnet<A>, |
| ) -> usize { |
| assert!(subnet.contains(&ip)); |
| let idx = self.devices.len(); |
| self.devices.push(DeviceConfig { |
| mac, |
| addr_subnet: Some(AddrSubnetEither::new(ip.into(), subnet.prefix()).unwrap()), |
| ipv4_config: None, |
| ipv6_config: None, |
| }); |
| self.device_routes.push((subnet.into(), idx)); |
| idx |
| } |
| |
| /// Add a device with an associated IP address and a particular IPv4 and |
| /// IPv6 configuration. |
| /// |
| /// `add_device_with_ip_and_config` is like `add_device`, except that it |
| /// takes an associated IP address and subnet to assign to the device, as |
| /// well as IPv4 and IPv6 configurations to apply to the device when it is |
| /// enabled. |
| pub fn add_device_with_ip_and_config<A: IpAddress>( |
| &mut self, |
| mac: UnicastAddr<Mac>, |
| ip: A, |
| subnet: Subnet<A>, |
| ipv4_config: Ipv4DeviceConfigurationUpdate, |
| ipv6_config: Ipv6DeviceConfigurationUpdate, |
| ) -> usize { |
| assert!(subnet.contains(&ip)); |
| let idx = self.devices.len(); |
| self.devices.push(DeviceConfig { |
| mac, |
| addr_subnet: Some(AddrSubnetEither::new(ip.into(), subnet.prefix()).unwrap()), |
| ipv4_config: Some(ipv4_config), |
| ipv6_config: Some(ipv6_config), |
| }); |
| self.device_routes.push((subnet.into(), idx)); |
| idx |
| } |
| |
| /// Add an ARP table entry for a device's ARP table. |
| pub fn add_arp_table_entry( |
| &mut self, |
| device: usize, |
| // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available. |
| ip: SpecifiedAddr<Ipv4Addr>, |
| mac: UnicastAddr<Mac>, |
| ) { |
| self.arp_table_entries.push((device, ip, mac)); |
| } |
| |
| /// Add an NDP table entry for a device's NDP table. |
| pub fn add_ndp_table_entry( |
| &mut self, |
| device: usize, |
| // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available. |
| ip: UnicastAddr<Ipv6Addr>, |
| mac: UnicastAddr<Mac>, |
| ) { |
| self.ndp_table_entries.push((device, ip, mac)); |
| } |
| |
| /// Add either an NDP entry (if IPv6) or ARP entry (if IPv4) to a |
| /// `FakeCtxBuilder`. |
| pub fn add_arp_or_ndp_table_entry<A: IpAddress>( |
| &mut self, |
| device: usize, |
| // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available. |
| ip: SpecifiedAddr<A>, |
| mac: UnicastAddr<Mac>, |
| ) { |
| match ip.into() { |
| IpAddr::V4(ip) => self.add_arp_table_entry(device, ip, mac), |
| IpAddr::V6(ip) => { |
| self.add_ndp_table_entry(device, UnicastAddr::new(ip.get()).unwrap(), mac) |
| } |
| } |
| } |
| |
| /// Builds a `Ctx` from the present configuration with a default dispatcher. |
| pub fn build(self) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) { |
| self.build_with_modifications(|_| {}) |
| } |
| |
| /// `build_with_modifications` is equivalent to `build`, except that after |
| /// the `StackStateBuilder` is initialized, it is passed to `f` for further |
| /// modification before the `Ctx` is constructed. |
| pub fn build_with_modifications<F: FnOnce(&mut StackStateBuilder)>( |
| self, |
| f: F, |
| ) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) { |
| let mut stack_builder = StackStateBuilder::default(); |
| f(&mut stack_builder); |
| self.build_with(stack_builder) |
| } |
| |
| /// Build a `Ctx` from the present configuration with a caller-provided |
| /// dispatcher and `StackStateBuilder`. |
| pub fn build_with( |
| self, |
| state_builder: StackStateBuilder, |
| ) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) { |
| let mut ctx = Ctx::new_with_builder(state_builder); |
| |
| let FakeCtxBuilder { devices, arp_table_entries, ndp_table_entries, device_routes } = self; |
| let idx_to_device_id: Vec<_> = devices |
| .into_iter() |
| .map(|DeviceConfig { mac, addr_subnet: ip_and_subnet, ipv4_config, ipv6_config }| { |
| let eth_id = |
| ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ); |
| let id = eth_id.clone().into(); |
| if let Some(ipv4_config) = ipv4_config { |
| let _previous = ctx |
| .core_api() |
| .device_ip::<Ipv4>() |
| .update_configuration(&id, ipv4_config) |
| .unwrap(); |
| } |
| if let Some(ipv6_config) = ipv6_config { |
| let _previous = ctx |
| .core_api() |
| .device_ip::<Ipv6>() |
| .update_configuration(&id, ipv6_config) |
| .unwrap(); |
| } |
| ctx.test_api().enable_device(&id); |
| match ip_and_subnet { |
| Some(addr_sub) => { |
| ctx.core_api().device_ip_any().add_ip_addr_subnet(&id, addr_sub).unwrap(); |
| } |
| None => {} |
| } |
| eth_id |
| }) |
| .collect(); |
| for (idx, ip, mac) in arp_table_entries { |
| let device = &idx_to_device_id[idx]; |
| ctx.core_api() |
| .neighbor::<Ipv4, EthernetLinkDevice>() |
| .insert_static_entry(&device, ip.get(), mac.get()) |
| .expect("error inserting static ARP entry"); |
| } |
| for (idx, ip, mac) in ndp_table_entries { |
| let device = &idx_to_device_id[idx]; |
| ctx.core_api() |
| .neighbor::<Ipv6, EthernetLinkDevice>() |
| .insert_static_entry(&device, ip.get(), mac.get()) |
| .expect("error inserting static NDP entry"); |
| } |
| |
| for (subnet, idx) in device_routes { |
| let device = &idx_to_device_id[idx]; |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| subnet, |
| device.clone().into(), |
| AddableMetric::ExplicitMetric(RawMetric(0)), |
| )) |
| .expect("add device route"); |
| } |
| |
| (ctx, idx_to_device_id) |
| } |
| } |
| |
| /// The fake network spec to use in integration tests. |
| /// |
| /// It creates an Ethernet network. |
| pub enum FakeCtxNetworkSpec {} |
| |
| impl FakeNetworkSpec for FakeCtxNetworkSpec { |
| type Context = FakeCtx; |
| type TimerId = TimerId<FakeBindingsCtx>; |
| type SendMeta = DispatchedFrame; |
| type RecvMeta = EthernetDeviceId<FakeBindingsCtx>; |
| fn handle_frame(ctx: &mut FakeCtx, device_id: Self::RecvMeta, data: Buf<Vec<u8>>) { |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id }, data) |
| } |
| fn handle_timer(ctx: &mut FakeCtx, timer: Self::TimerId) { |
| ctx.core_api().handle_timer(timer) |
| } |
| fn process_queues(ctx: &mut FakeCtx) -> bool { |
| ctx.test_api().handle_queued_rx_packets() |
| } |
| fn fake_frames(ctx: &mut FakeCtx) -> &mut impl WithFakeFrameContext<Self::SendMeta> { |
| &mut ctx.bindings_ctx |
| } |
| } |
| |
| impl<I: IpExt> UdpReceiveBindingsContext<I, DeviceId<Self>> for FakeBindingsCtx { |
| fn receive_udp<B: BufferMut>( |
| &mut self, |
| id: &UdpSocketId<I, WeakDeviceId<Self>, FakeBindingsCtx>, |
| _device: &DeviceId<Self>, |
| _dst_addr: (<I>::Addr, core::num::NonZeroU16), |
| _src_addr: (<I>::Addr, Option<core::num::NonZeroU16>), |
| body: &B, |
| ) { |
| let mut state = self.state_mut(); |
| let received = |
| (&mut *state).udp_state_mut::<I>().entry(id.clone()).or_insert_with(Vec::default); |
| received.push(body.as_ref().to_owned()); |
| } |
| } |
| |
| impl UdpBindingsTypes for FakeBindingsCtx { |
| type ExternalData<I: Ip> = (); |
| } |
| |
| impl<I: IpExt> IcmpEchoBindingsContext<I, DeviceId<Self>> for FakeBindingsCtx { |
| fn receive_icmp_echo_reply<B: BufferMut>( |
| &mut self, |
| conn: &IcmpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, |
| _device: &DeviceId<Self>, |
| _src_ip: I::Addr, |
| _dst_ip: I::Addr, |
| _id: u16, |
| data: B, |
| ) { |
| I::map_ip( |
| (IpInvariant(self.state_mut()), conn.clone()), |
| |(IpInvariant(mut state), conn)| { |
| let replies = state.icmpv4_replies.entry(conn).or_insert_with(Vec::default); |
| replies.push(data.as_ref().to_owned()); |
| }, |
| |(IpInvariant(mut state), conn)| { |
| let replies = state.icmpv6_replies.entry(conn).or_insert_with(Vec::default); |
| replies.push(data.as_ref().to_owned()); |
| }, |
| ) |
| } |
| } |
| |
| impl IcmpEchoBindingsTypes for FakeBindingsCtx { |
| type ExternalData<I: Ip> = (); |
| } |
| |
| impl DeviceSocketTypes for FakeBindingsCtx { |
| type SocketState = Mutex<Vec<(WeakDeviceId<FakeBindingsCtx>, Vec<u8>)>>; |
| } |
| |
| impl RawIpSocketsBindingsTypes for FakeBindingsCtx { |
| type RawIpSocketState<I: Ip> = (); |
| } |
| |
| impl DeviceSocketBindingsContext<DeviceId<Self>> for FakeBindingsCtx { |
| fn receive_frame( |
| &self, |
| state: &Self::SocketState, |
| device: &DeviceId<Self>, |
| _frame: device::socket::Frame<&[u8]>, |
| raw_frame: &[u8], |
| ) { |
| state.lock().push((device.downgrade(), raw_frame.into())); |
| } |
| } |
| |
| impl<I: IpExt> RawIpSocketsBindingsContext<I, DeviceId<Self>> for FakeBindingsCtx { |
| fn receive_packet<B: ByteSlice>( |
| &self, |
| _socket: &RawIpSocketId<I, WeakDeviceId<Self>, Self>, |
| _packet: &I::Packet<B>, |
| _device: &DeviceId<Self>, |
| ) { |
| unimplemented!() |
| } |
| } |
| |
| impl DeviceLayerStateTypes for FakeBindingsCtx { |
| type LoopbackDeviceState = (); |
| type EthernetDeviceState = (); |
| type PureIpDeviceState = (); |
| type DeviceIdentifier = MonotonicIdentifier; |
| } |
| |
| impl ReceiveQueueBindingsContext<LoopbackDeviceId<Self>> for FakeBindingsCtx { |
| fn wake_rx_task(&mut self, device: &LoopbackDeviceId<FakeBindingsCtx>) { |
| self.state_mut().rx_available.push(device.clone()); |
| } |
| } |
| |
| impl<D: Clone + Into<DeviceId<Self>>> TransmitQueueBindingsContext<D> for FakeBindingsCtx { |
| fn wake_tx_task(&mut self, device: &D) { |
| self.state_mut().tx_available.push(device.clone().into()); |
| } |
| } |
| |
| impl DeviceLayerEventDispatcher for FakeBindingsCtx { |
| fn send_ethernet_frame( |
| &mut self, |
| device: &EthernetDeviceId<FakeBindingsCtx>, |
| frame: Buf<Vec<u8>>, |
| ) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>> { |
| let frame_meta = DispatchedFrame::Ethernet(device.downgrade()); |
| self.with_inner_mut(|ctx| ctx.frames.push(frame_meta, frame.into_inner())); |
| Ok(()) |
| } |
| |
| fn send_ip_packet( |
| &mut self, |
| device: &PureIpDeviceId<FakeBindingsCtx>, |
| packet: Buf<Vec<u8>>, |
| ip_version: IpVersion, |
| ) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>> { |
| let frame_meta = DispatchedFrame::PureIp(PureIpDeviceAndIpVersion { |
| device: device.downgrade(), |
| version: ip_version, |
| }); |
| self.with_inner_mut(|ctx| ctx.frames.push(frame_meta, packet.into_inner())); |
| Ok(()) |
| } |
| } |
| |
| /// Wraps all events emitted by Core into a single enum type. |
| #[derive(Debug, Eq, PartialEq, Hash, GenericOverIp)] |
| #[generic_over_ip()] |
| #[allow(missing_docs)] |
| pub enum DispatchedEvent { |
| IpDeviceIpv4(IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, Ipv4, FakeInstant>), |
| IpDeviceIpv6(IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, Ipv6, FakeInstant>), |
| IpLayerIpv4(IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, Ipv4>), |
| IpLayerIpv6(IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, Ipv6>), |
| NeighborIpv4(nud::Event<Mac, EthernetWeakDeviceId<FakeBindingsCtx>, Ipv4, FakeInstant>), |
| NeighborIpv6(nud::Event<Mac, EthernetWeakDeviceId<FakeBindingsCtx>, Ipv6, FakeInstant>), |
| } |
| |
| /// A tuple of device ID and IP version. |
| #[derive(Derivative)] |
| #[derivative(Debug(bound = ""))] |
| #[allow(missing_docs)] |
| pub struct PureIpDeviceAndIpVersion<BT: DeviceLayerTypes> { |
| pub device: PureIpWeakDeviceId<BT>, |
| pub version: IpVersion, |
| } |
| |
| /// A frame that's been dispatched to Bindings to be sent out the device driver. |
| pub enum DispatchedFrame { |
| /// A frame that's been dispatched to an Ethernet device. |
| Ethernet(EthernetWeakDeviceId<FakeBindingsCtx>), |
| /// A frame that's been dispatched to a PureIp device. |
| PureIp(PureIpDeviceAndIpVersion<FakeBindingsCtx>), |
| } |
| |
| impl<I: Ip> From<IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>> for DispatchedEvent { |
| fn from(e: IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>) -> DispatchedEvent { |
| let e = e.map_device(|d| d.downgrade()); |
| I::map_ip(e, |e| DispatchedEvent::IpDeviceIpv4(e), |e| DispatchedEvent::IpDeviceIpv6(e)) |
| } |
| } |
| |
| impl<I: Ip> From<IpLayerEvent<DeviceId<FakeBindingsCtx>, I>> for DispatchedEvent { |
| fn from(e: IpLayerEvent<DeviceId<FakeBindingsCtx>, I>) -> DispatchedEvent { |
| let e = e.map_device(|d| d.downgrade()); |
| I::map_ip(e, |e| DispatchedEvent::IpLayerIpv4(e), |e| DispatchedEvent::IpLayerIpv6(e)) |
| } |
| } |
| |
| impl<I: Ip> From<nud::Event<Mac, EthernetDeviceId<FakeBindingsCtx>, I, FakeInstant>> |
| for DispatchedEvent |
| { |
| fn from( |
| e: nud::Event<Mac, EthernetDeviceId<FakeBindingsCtx>, I, FakeInstant>, |
| ) -> DispatchedEvent { |
| let e = e.map_device(|d| d.downgrade()); |
| I::map_ip(e, |e| DispatchedEvent::NeighborIpv4(e), |e| DispatchedEvent::NeighborIpv6(e)) |
| } |
| } |
| |
| /// Creates a new [`FakeNetwork`] of [`Ctx`]s in a simple two-host |
| /// configuration. |
| /// |
| /// Two hosts are created with the given names. Packets emitted by one |
| /// arrive at the other and vice-versa. |
| pub fn new_simple_fake_network<CtxId: Copy + Debug + Hash + Eq>( |
| a_id: CtxId, |
| a: FakeCtx, |
| a_device_id: EthernetWeakDeviceId<FakeBindingsCtx>, |
| b_id: CtxId, |
| b: FakeCtx, |
| b_device_id: EthernetWeakDeviceId<FakeBindingsCtx>, |
| ) -> FakeNetwork< |
| FakeCtxNetworkSpec, |
| CtxId, |
| impl FakeNetworkLinks<DispatchedFrame, EthernetDeviceId<FakeBindingsCtx>, CtxId>, |
| > { |
| let contexts = vec![(a_id, a), (b_id, b)].into_iter(); |
| FakeNetwork::new(contexts, move |net, _frame: DispatchedFrame| { |
| if net == a_id { |
| b_device_id |
| .upgrade() |
| .map(|device_id| (b_id, device_id, None)) |
| .into_iter() |
| .collect::<Vec<_>>() |
| } else { |
| a_device_id |
| .upgrade() |
| .map(|device_id| (a_id, device_id, None)) |
| .into_iter() |
| .collect::<Vec<_>>() |
| } |
| }) |
| } |