| // 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. |
| |
| //! The Ethernet protocol. |
| |
| use alloc::vec::Vec; |
| use core::{fmt::Debug, num::NonZeroU32}; |
| use lock_order::{ |
| lock::{LockFor, RwLockFor, UnlockedAccess}, |
| relation::LockBefore, |
| wrap::prelude::*, |
| }; |
| |
| use const_unwrap::const_unwrap_option; |
| use net_types::{ |
| ethernet::Mac, |
| ip::{GenericOverIp, Ip, IpAddress, IpInvariant, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Mtu}, |
| BroadcastAddress, MulticastAddr, SpecifiedAddr, UnicastAddr, Witness, |
| }; |
| use packet::{Buf, BufferMut, InnerPacketBuilder as _, Nested, Serializer}; |
| use packet_formats::{ |
| arp::{peek_arp_types, ArpHardwareType, ArpNetworkType}, |
| ethernet::{ |
| EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt, |
| ETHERNET_HDR_LEN_NO_TAG, |
| }, |
| icmp::{ |
| ndp::{options::NdpOptionBuilder, NeighborSolicitation, OptionSequenceBuilder}, |
| IcmpUnusedCode, |
| }, |
| ipv4::Ipv4FragmentType, |
| utils::NonZeroDuration, |
| }; |
| use tracing::trace; |
| |
| use crate::{ |
| context::{ |
| CoreTimerContext, CounterContext, RecvFrameContext, ResourceCounterContext, RngContext, |
| SendFrameContext, TimerContext2, TimerHandler, |
| }, |
| data_structures::ref_counted_hash_map::{InsertResult, RefCountedHashSet, RemoveResult}, |
| device::{ |
| self, |
| arp::{ |
| ArpConfigContext, ArpContext, ArpFrameMetadata, ArpPacketHandler, ArpSenderContext, |
| ArpState, ArpTimerId, |
| }, |
| link::LinkDevice, |
| queue::{ |
| tx::{ |
| BufVecU8Allocator, TransmitDequeueContext, TransmitQueue, |
| TransmitQueueBindingsContext, TransmitQueueCommon, TransmitQueueContext, |
| TransmitQueueHandler, TransmitQueueState, |
| }, |
| DequeueState, TransmitQueueFrameError, |
| }, |
| socket::{ |
| DatagramHeader, DeviceSocketBindingsContext, DeviceSocketHandler, DeviceSocketMetadata, |
| HeldDeviceSockets, ParseSentFrameError, ReceivedFrame, SentFrame, |
| }, |
| state::{DeviceStateSpec, IpLinkDeviceState}, |
| Device, DeviceCounters, DeviceIdContext, DeviceLayerEventDispatcher, DeviceLayerTimerId, |
| DeviceLayerTypes, DeviceReceiveFrameSpec, DeviceSendFrameError, EthernetDeviceCounters, |
| EthernetDeviceId, EthernetWeakDeviceId, FrameDestination, RecvIpFrameMeta, |
| }, |
| ip::{ |
| device::nud::{ |
| LinkResolutionContext, NudBindingsTypes, NudConfigContext, NudContext, NudHandler, |
| NudIcmpContext, NudSenderContext, NudState, NudTimerId, NudUserConfig, |
| }, |
| icmp::NdpCounters, |
| types::IpTypesIpExt, |
| }, |
| routes::WrapBroadcastMarker, |
| socket::address::SocketIpAddr, |
| sync::{Mutex, RwLock}, |
| trace_duration, BindingsContext, BindingsTypes, CoreCtx, TracingContext, |
| }; |
| |
| const ETHERNET_HDR_LEN_NO_TAG_U32: u32 = ETHERNET_HDR_LEN_NO_TAG as u32; |
| |
| /// The execution context for an Ethernet device provided by bindings. |
| pub(crate) trait EthernetIpLinkDeviceBindingsContext<DeviceId>: |
| RngContext + TimerContext2 |
| { |
| } |
| impl<DeviceId, BC: RngContext + TimerContext2> EthernetIpLinkDeviceBindingsContext<DeviceId> |
| for BC |
| { |
| } |
| |
| /// Provides access to an ethernet device's static state. |
| pub(crate) trait EthernetIpLinkDeviceStaticStateContext: |
| DeviceIdContext<EthernetLinkDevice> |
| { |
| /// Calls the function with an immutable reference to the ethernet device's |
| /// static state. |
| fn with_static_ethernet_device_state<O, F: FnOnce(&StaticEthernetDeviceState) -> O>( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O; |
| } |
| |
| /// Provides access to an ethernet device's dynamic state. |
| pub(crate) trait EthernetIpLinkDeviceDynamicStateContext< |
| BC: EthernetIpLinkDeviceBindingsContext<Self::DeviceId>, |
| >: EthernetIpLinkDeviceStaticStateContext |
| { |
| /// Calls the function with the ethernet device's static state and immutable |
| /// reference to the dynamic state. |
| fn with_ethernet_state< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with the ethernet device's static state and mutable |
| /// reference to the dynamic state. |
| fn with_ethernet_state_mut< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &mut DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O; |
| } |
| |
| impl<BC: BindingsContext, L> EthernetIpLinkDeviceStaticStateContext for CoreCtx<'_, BC, L> { |
| fn with_static_ethernet_device_state<O, F: FnOnce(&StaticEthernetDeviceState) -> O>( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state(self, device_id, |state| { |
| cb(state.unlocked_access::<crate::lock_ordering::EthernetDeviceStaticState>()) |
| }) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::EthernetDeviceDynamicState>> |
| EthernetIpLinkDeviceDynamicStateContext<BC> for CoreCtx<'_, BC, L> |
| { |
| fn with_ethernet_state< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state(self, device_id, |mut state| { |
| let (dynamic_state, locked) = |
| state.read_lock_and::<crate::lock_ordering::EthernetDeviceDynamicState>(); |
| cb( |
| &locked.unlocked_access::<crate::lock_ordering::EthernetDeviceStaticState>(), |
| &dynamic_state, |
| ) |
| }) |
| } |
| |
| fn with_ethernet_state_mut< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &mut DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state(self, device_id, |mut state| { |
| let (mut dynamic_state, locked) = |
| state.write_lock_and::<crate::lock_ordering::EthernetDeviceDynamicState>(); |
| cb( |
| &locked.unlocked_access::<crate::lock_ordering::EthernetDeviceStaticState>(), |
| &mut dynamic_state, |
| ) |
| }) |
| } |
| } |
| |
| pub struct CoreCtxWithDeviceId< |
| 'a, |
| CC: DeviceIdContext<EthernetLinkDevice> + CounterContext<DeviceCounters>, |
| > { |
| pub(crate) core_ctx: &'a mut CC, |
| pub(crate) device_id: &'a CC::DeviceId, |
| } |
| |
| impl<BT: BindingsTypes, L> CoreTimerContext<EthernetTimerId<EthernetWeakDeviceId<BT>>, BT> |
| for CoreCtx<'_, BT, L> |
| { |
| fn convert_timer(dispatch_id: EthernetTimerId<EthernetWeakDeviceId<BT>>) -> BT::DispatchId { |
| DeviceLayerTimerId::from(dispatch_id).into() |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::FilterState<Ipv6>>> |
| NudContext<Ipv6, EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| type ConfigCtx<'a> = |
| CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, crate::lock_ordering::EthernetIpv6Nud>>; |
| |
| type SenderCtx<'a> = |
| CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, crate::lock_ordering::EthernetIpv6Nud>>; |
| |
| fn with_nud_state_mut_and_sender_ctx< |
| O, |
| F: FnOnce(&mut NudState<Ipv6, EthernetLinkDevice, BC>, &mut Self::SenderCtx<'_>) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let (mut nud, mut locked) = |
| core_ctx_and_resource |
| .lock_with_and::<crate::lock_ordering::EthernetIpv6Nud, _>(|c| c.right()); |
| let mut locked = |
| CoreCtxWithDeviceId { device_id, core_ctx: &mut locked.cast_core_ctx() }; |
| cb(&mut nud, &mut locked) |
| }, |
| ) |
| } |
| |
| fn with_nud_state_mut< |
| O, |
| F: FnOnce(&mut NudState<Ipv6, EthernetLinkDevice, BC>, &mut Self::ConfigCtx<'_>) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let (mut nud, mut locked) = |
| core_ctx_and_resource |
| .lock_with_and::<crate::lock_ordering::EthernetIpv6Nud, _>(|c| c.right()); |
| let mut locked = |
| CoreCtxWithDeviceId { device_id, core_ctx: &mut locked.cast_core_ctx() }; |
| cb(&mut nud, &mut locked) |
| }, |
| ) |
| } |
| |
| fn with_nud_state<O, F: FnOnce(&NudState<Ipv6, EthernetLinkDevice, BC>) -> O>( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let nud = core_ctx_and_resource |
| .lock_with::<crate::lock_ordering::EthernetIpv6Nud, _>(|c| c.right()); |
| cb(&nud) |
| }, |
| ) |
| } |
| |
| fn send_neighbor_solicitation( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device_id: &EthernetDeviceId<BC>, |
| lookup_addr: SpecifiedAddr<Ipv6Addr>, |
| remote_link_addr: Option<Mac>, |
| ) { |
| let dst_ip = match remote_link_addr { |
| // TODO(https://fxbug.dev/42081683): once `send_ndp_packet` does not go through |
| // the normal IP egress flow, using the NUD table to resolve the link address, |
| // use the specified link address to determine where to unicast the |
| // solicitation. |
| Some(_) => lookup_addr, |
| None => lookup_addr.to_solicited_node_address().into_specified(), |
| }; |
| let src_ip = crate::ip::IpDeviceStateContext::<Ipv6, _>::get_local_addr_for_remote( |
| self, |
| &device_id.clone().into(), |
| Some(dst_ip), |
| ); |
| let src_ip = match src_ip { |
| Some(s) => s, |
| None => return, |
| }; |
| |
| let mac = get_mac(self, device_id); |
| |
| <Self as CounterContext<NdpCounters>>::increment(self, |counters| { |
| &counters.tx.neighbor_solicitation |
| }); |
| tracing::debug!("sending NDP solicitation for {lookup_addr} to {dst_ip}"); |
| // TODO(https://fxbug.dev/42165912): Either panic or guarantee that this error |
| // can't happen statically. |
| let _: Result<(), _> = crate::ip::icmp::send_ndp_packet( |
| self, |
| bindings_ctx, |
| &device_id.clone().into(), |
| Some(src_ip.into()), |
| dst_ip, |
| OptionSequenceBuilder::<_>::new( |
| [NdpOptionBuilder::SourceLinkLayerAddress(mac.bytes().as_ref())].iter(), |
| ) |
| .into_serializer(), |
| IcmpUnusedCode, |
| NeighborSolicitation::new(lookup_addr.get()), |
| ); |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::IcmpAllSocketsSet<Ipv6>>> |
| NudIcmpContext<Ipv6, EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| fn send_icmp_dest_unreachable( |
| &mut self, |
| bindings_ctx: &mut BC, |
| frame: Buf<Vec<u8>>, |
| device_id: Option<&Self::DeviceId>, |
| original_src_ip: SocketIpAddr<Ipv6Addr>, |
| original_dst_ip: SocketIpAddr<Ipv6Addr>, |
| _: (), |
| ) { |
| crate::ip::icmp::send_icmpv6_address_unreachable( |
| self, |
| bindings_ctx, |
| device_id.map(|device_id| device_id.clone().into()).as_ref(), |
| // NB: link layer address resolution only happens for packets destined for |
| // a unicast address, so passing `None` as `FrameDestination` here is always |
| // correct since there's never a need to not send the ICMP error due to |
| // a multicast/broadcast destination. |
| None, |
| original_src_ip, |
| original_dst_ip, |
| frame, |
| ); |
| } |
| } |
| |
| impl<'a, BC: BindingsContext, L: LockBefore<crate::lock_ordering::Ipv6DeviceLearnedParams>> |
| NudConfigContext<Ipv6> for CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, L>> |
| { |
| fn retransmit_timeout(&mut self) -> NonZeroDuration { |
| let Self { device_id, core_ctx } = self; |
| device::integration::with_device_state(core_ctx, device_id, |mut state| { |
| let mut state = state.cast(); |
| let x = state |
| .read_lock::<crate::lock_ordering::Ipv6DeviceLearnedParams>() |
| .retrans_timer_or_default(); |
| x |
| }) |
| } |
| |
| fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O { |
| let Self { device_id, core_ctx } = self; |
| device::integration::with_device_state(core_ctx, device_id, |mut state| { |
| let x = state.read_lock::<crate::lock_ordering::NudConfig<Ipv6>>(); |
| cb(&*x) |
| }) |
| } |
| } |
| |
| fn send_as_ethernet_frame_to_dst<S, BC, CC>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device_id: &CC::DeviceId, |
| dst_mac: Mac, |
| body: S, |
| ether_type: EtherType, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + TransmitQueueHandler<EthernetLinkDevice, BC, Meta = ()> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters>, |
| { |
| /// The minimum body length for the Ethernet frame. |
| /// |
| /// Using a frame length of 0 improves efficiency by avoiding unnecessary |
| /// padding at this layer. The expectation is that the implementation of |
| /// [`DeviceLayerEventDispatcher::send_frame`] will add any padding required |
| /// by the implementation. |
| const MIN_BODY_LEN: usize = 0; |
| |
| let local_mac = get_mac(core_ctx, device_id); |
| let frame = body.encapsulate(EthernetFrameBuilder::new( |
| local_mac.get(), |
| dst_mac, |
| ether_type, |
| MIN_BODY_LEN, |
| )); |
| send_ethernet_frame(core_ctx, bindings_ctx, device_id, frame) |
| .map_err(|frame| frame.into_inner()) |
| } |
| |
| fn send_ethernet_frame<S, BC, CC>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device_id: &CC::DeviceId, |
| frame: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + TransmitQueueHandler<EthernetLinkDevice, BC, Meta = ()> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters>, |
| { |
| core_ctx.increment(device_id, |counters| &counters.send_total_frames); |
| match TransmitQueueHandler::<EthernetLinkDevice, _>::queue_tx_frame( |
| core_ctx, |
| bindings_ctx, |
| device_id, |
| (), |
| frame, |
| ) { |
| Ok(()) => { |
| core_ctx.increment(device_id, |counters| &counters.send_frame); |
| Ok(()) |
| } |
| Err(TransmitQueueFrameError::NoQueue(e)) => { |
| core_ctx.increment(device_id, |counters| &counters.send_dropped_no_queue); |
| tracing::error!("device {device_id:?} not ready to send frame: {e:?}"); |
| Ok(()) |
| } |
| Err(TransmitQueueFrameError::QueueFull(s)) => { |
| core_ctx.increment(device_id, |counters| &counters.send_queue_full); |
| Err(s) |
| } |
| Err(TransmitQueueFrameError::SerializeError(s)) => { |
| core_ctx.increment(device_id, |counters| &counters.send_serialize_error); |
| Err(s) |
| } |
| } |
| } |
| |
| impl<'a, BC: BindingsContext, L: LockBefore<crate::lock_ordering::AllDeviceSockets>> |
| NudSenderContext<Ipv6, EthernetLinkDevice, BC> for CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, L>> |
| { |
| fn send_ip_packet_to_neighbor_link_addr<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| dst_mac: Mac, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| { |
| let Self { device_id, core_ctx } = self; |
| send_as_ethernet_frame_to_dst( |
| *core_ctx, |
| bindings_ctx, |
| device_id, |
| dst_mac, |
| body, |
| EtherType::Ipv6, |
| ) |
| } |
| } |
| |
| /// The maximum frame size one ethernet device can send. |
| /// |
| /// The frame size includes the ethernet header, the data payload, but excludes |
| /// the 4 bytes from FCS (frame check sequence) as we don't calculate CRC and it |
| /// is normally handled by the device. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub struct MaxEthernetFrameSize(NonZeroU32); |
| |
| impl MaxEthernetFrameSize { |
| /// The minimum ethernet frame size. |
| /// |
| /// We don't care about FCS, so the minimum frame size for us is 64 - 4. |
| pub(crate) const MIN: MaxEthernetFrameSize = |
| MaxEthernetFrameSize(const_unwrap_option(NonZeroU32::new(60))); |
| |
| /// Creates from the maximum size of ethernet header and ethernet payload, |
| /// checks that it is valid, i.e., larger than the minimum frame size. |
| pub const fn new(frame_size: u32) -> Option<Self> { |
| if frame_size < Self::MIN.get().get() { |
| return None; |
| } |
| Some(Self(const_unwrap_option(NonZeroU32::new(frame_size)))) |
| } |
| |
| const fn get(&self) -> NonZeroU32 { |
| let Self(frame_size) = *self; |
| frame_size |
| } |
| |
| /// Converts the maximum frame size to its corresponding MTU. |
| pub const fn as_mtu(&self) -> Mtu { |
| // MTU must be positive because of the limit on minimum ethernet frame size |
| Mtu::new(self.get().get().saturating_sub(ETHERNET_HDR_LEN_NO_TAG_U32)) |
| } |
| |
| /// Creates the maximum ethernet frame size from MTU. |
| pub const fn from_mtu(mtu: Mtu) -> Option<MaxEthernetFrameSize> { |
| let frame_size = mtu.get().saturating_add(ETHERNET_HDR_LEN_NO_TAG_U32); |
| Self::new(frame_size) |
| } |
| } |
| |
| /// Base properties to create a new Ethernet device. |
| #[derive(Debug)] |
| pub struct EthernetCreationProperties { |
| /// The device's MAC address. |
| pub mac: UnicastAddr<Mac>, |
| /// The maximum frame size this device supports. |
| // TODO(https://fxbug.dev/42072516): Add a minimum frame size for all |
| // Ethernet devices such that you can't create an `EthernetDeviceState` |
| // with a `MaxEthernetFrameSize` smaller than the minimum. The absolute minimum |
| // needs to be at least the minimum body size of an Ethernet frame. For |
| // IPv6-capable devices, the minimum needs to be higher - the frame size |
| // implied by the IPv6 minimum MTU. The easy path is to simply use that |
| // frame size as the minimum in all cases, although we may at some point |
| // want to figure out how to configure devices which don't support IPv6, |
| // and allow smaller frame sizes for those devices. |
| pub max_frame_size: MaxEthernetFrameSize, |
| } |
| |
| pub(crate) struct DynamicEthernetDeviceState { |
| /// The value this netstack assumes as the device's maximum frame size. |
| max_frame_size: MaxEthernetFrameSize, |
| |
| /// A flag indicating whether the device will accept all ethernet frames |
| /// that it receives, regardless of the ethernet frame's destination MAC |
| /// address. |
| promiscuous_mode: bool, |
| |
| /// Link multicast groups this device has joined. |
| link_multicast_groups: RefCountedHashSet<MulticastAddr<Mac>>, |
| } |
| |
| impl DynamicEthernetDeviceState { |
| fn new(max_frame_size: MaxEthernetFrameSize) -> Self { |
| Self { max_frame_size, promiscuous_mode: false, link_multicast_groups: Default::default() } |
| } |
| } |
| |
| pub(crate) struct StaticEthernetDeviceState { |
| /// Mac address of the device this state is for. |
| mac: UnicastAddr<Mac>, |
| |
| /// The maximum frame size allowed by the hardware. |
| max_frame_size: MaxEthernetFrameSize, |
| } |
| |
| /// The state associated with an Ethernet device. |
| pub struct EthernetDeviceState<BT: NudBindingsTypes<EthernetLinkDevice>> { |
| /// Ethernet device counters. |
| counters: EthernetDeviceCounters, |
| |
| /// IPv4 ARP state. |
| ipv4_arp: Mutex<ArpState<EthernetLinkDevice, BT>>, |
| |
| /// IPv6 NUD state. |
| ipv6_nud: Mutex<NudState<Ipv6, EthernetLinkDevice, BT>>, |
| |
| ipv4_nud_config: RwLock<NudUserConfig>, |
| |
| ipv6_nud_config: RwLock<NudUserConfig>, |
| |
| static_state: StaticEthernetDeviceState, |
| |
| dynamic_state: RwLock<DynamicEthernetDeviceState>, |
| |
| tx_queue: TransmitQueue<(), Buf<Vec<u8>>, BufVecU8Allocator>, |
| } |
| |
| impl<BT: NudBindingsTypes<EthernetLinkDevice>> EthernetDeviceState<BT> { |
| fn nud_config<I: Ip>(&self) -> &RwLock<NudUserConfig> { |
| let IpInvariant(nudconfig) = I::map_ip( |
| (), |
| |()| IpInvariant(&self.ipv4_nud_config), |
| |()| IpInvariant(&self.ipv6_nud_config), |
| ); |
| nudconfig |
| } |
| } |
| |
| impl<BT: BindingsTypes, I: Ip> RwLockFor<crate::lock_ordering::NudConfig<I>> |
| for IpLinkDeviceState<EthernetLinkDevice, BT> |
| { |
| type Data = NudUserConfig; |
| type ReadGuard<'l> = crate::sync::RwLockReadGuard<'l, NudUserConfig> |
| where |
| Self: 'l; |
| type WriteGuard<'l> = crate::sync::RwLockWriteGuard<'l, NudUserConfig> |
| where |
| Self: 'l; |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.link.nud_config::<I>().read() |
| } |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.link.nud_config::<I>().write() |
| } |
| } |
| |
| impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::EthernetDeviceStaticState> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = StaticEthernetDeviceState; |
| type Guard<'l> = &'l StaticEthernetDeviceState |
| where |
| Self: 'l ; |
| fn access(&self) -> Self::Guard<'_> { |
| &self.link.static_state |
| } |
| } |
| |
| impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::EthernetDeviceCounters> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = EthernetDeviceCounters; |
| type Guard<'l> = &'l EthernetDeviceCounters |
| where |
| Self: 'l ; |
| fn access(&self) -> Self::Guard<'_> { |
| &self.link.counters |
| } |
| } |
| |
| impl<BC: BindingsContext> RwLockFor<crate::lock_ordering::EthernetDeviceDynamicState> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = DynamicEthernetDeviceState; |
| type ReadGuard<'l> = crate::sync::RwLockReadGuard<'l, DynamicEthernetDeviceState> |
| where |
| Self: 'l; |
| type WriteGuard<'l> = crate::sync::RwLockWriteGuard<'l, DynamicEthernetDeviceState> |
| where |
| Self: 'l; |
| |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.link.dynamic_state.read() |
| } |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.link.dynamic_state.write() |
| } |
| } |
| |
| impl<BC: BindingsContext> RwLockFor<crate::lock_ordering::DeviceSockets> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = HeldDeviceSockets<BC>; |
| type ReadGuard<'l> = crate::sync::RwLockReadGuard<'l, HeldDeviceSockets<BC>> |
| where |
| Self: 'l ; |
| type WriteGuard<'l> = crate::sync::RwLockWriteGuard<'l, HeldDeviceSockets<BC>> |
| where |
| Self: 'l ; |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.sockets.read() |
| } |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.sockets.write() |
| } |
| } |
| |
| impl<BC: BindingsContext> LockFor<crate::lock_ordering::EthernetIpv6Nud> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = NudState<Ipv6, EthernetLinkDevice, BC>; |
| type Guard<'l> = crate::sync::LockGuard<'l, NudState<Ipv6, EthernetLinkDevice, BC>> |
| where |
| Self: 'l; |
| fn lock(&self) -> Self::Guard<'_> { |
| self.link.ipv6_nud.lock() |
| } |
| } |
| |
| impl<BC: BindingsContext> LockFor<crate::lock_ordering::EthernetIpv4Arp> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = ArpState<EthernetLinkDevice, BC>; |
| type Guard<'l> = crate::sync::LockGuard<'l, ArpState<EthernetLinkDevice, BC>> |
| where |
| Self: 'l; |
| fn lock(&self) -> Self::Guard<'_> { |
| self.link.ipv4_arp.lock() |
| } |
| } |
| |
| impl<BC: BindingsContext> LockFor<crate::lock_ordering::EthernetTxQueue> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = TransmitQueueState<(), Buf<Vec<u8>>, BufVecU8Allocator>; |
| type Guard<'l> = crate::sync::LockGuard<'l, TransmitQueueState<(), Buf<Vec<u8>>, BufVecU8Allocator>> |
| where |
| Self: 'l; |
| fn lock(&self) -> Self::Guard<'_> { |
| self.link.tx_queue.queue.lock() |
| } |
| } |
| |
| impl<BC: BindingsContext> LockFor<crate::lock_ordering::EthernetTxDequeue> |
| for IpLinkDeviceState<EthernetLinkDevice, BC> |
| { |
| type Data = DequeueState<(), Buf<Vec<u8>>>; |
| type Guard<'l> = crate::sync::LockGuard<'l, DequeueState<(), Buf<Vec<u8>>>> |
| where |
| Self: 'l; |
| fn lock(&self) -> Self::Guard<'_> { |
| self.link.tx_queue.deque.lock() |
| } |
| } |
| |
| impl<BC: BindingsContext> TransmitQueueBindingsContext<EthernetLinkDevice, EthernetDeviceId<BC>> |
| for BC |
| { |
| fn wake_tx_task(&mut self, device_id: &EthernetDeviceId<BC>) { |
| DeviceLayerEventDispatcher::wake_tx_task(self, &device_id.clone().into()) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::EthernetTxQueue>> |
| TransmitQueueCommon<EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| type Meta = (); |
| type Allocator = BufVecU8Allocator; |
| type Buffer = Buf<Vec<u8>>; |
| |
| fn parse_outgoing_frame(buf: &[u8]) -> Result<SentFrame<&[u8]>, ParseSentFrameError> { |
| SentFrame::try_parse_as_ethernet(buf) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::EthernetTxQueue>> |
| TransmitQueueContext<EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| fn with_transmit_queue_mut< |
| O, |
| F: FnOnce(&mut TransmitQueueState<Self::Meta, Self::Buffer, Self::Allocator>) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state(self, device_id, |mut state| { |
| let mut x = state.lock::<crate::lock_ordering::EthernetTxQueue>(); |
| cb(&mut x) |
| }) |
| } |
| |
| fn send_frame( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device_id: &Self::DeviceId, |
| meta: Self::Meta, |
| buf: Self::Buffer, |
| ) -> Result<(), DeviceSendFrameError<(Self::Meta, Self::Buffer)>> { |
| DeviceLayerEventDispatcher::send_ethernet_frame(bindings_ctx, device_id, buf).map_err( |
| |DeviceSendFrameError::DeviceNotReady(buf)| { |
| DeviceSendFrameError::DeviceNotReady((meta, buf)) |
| }, |
| ) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::EthernetTxDequeue>> |
| TransmitDequeueContext<EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| type TransmitQueueCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::EthernetTxDequeue>; |
| |
| fn with_dequed_packets_and_tx_queue_ctx< |
| O, |
| F: FnOnce(&mut DequeueState<Self::Meta, Self::Buffer>, &mut Self::TransmitQueueCtx<'_>) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let (mut x, mut locked) = |
| core_ctx_and_resource |
| .lock_with_and::<crate::lock_ordering::EthernetTxDequeue, _>(|c| c.right()); |
| cb(&mut x, &mut locked.cast_core_ctx()) |
| }, |
| ) |
| } |
| } |
| |
| /// Returns the type of frame if it should be delivered, otherwise `None`. |
| fn deliver_as( |
| static_state: &StaticEthernetDeviceState, |
| dynamic_state: &DynamicEthernetDeviceState, |
| dst_mac: &Mac, |
| ) -> Option<FrameDestination> { |
| if dynamic_state.promiscuous_mode { |
| return Some(FrameDestination::from_dest(*dst_mac, static_state.mac.get())); |
| } |
| UnicastAddr::new(*dst_mac) |
| .and_then(|u| { |
| (static_state.mac == u).then_some(FrameDestination::Individual { local: true }) |
| }) |
| .or_else(|| dst_mac.is_broadcast().then_some(FrameDestination::Broadcast)) |
| .or_else(|| { |
| MulticastAddr::new(*dst_mac).and_then(|a| { |
| dynamic_state |
| .link_multicast_groups |
| .contains(&a) |
| .then_some(FrameDestination::Multicast) |
| }) |
| }) |
| } |
| |
| /// A timer ID for Ethernet devices. |
| /// |
| /// `D` is the type of device ID that identifies different Ethernet devices. |
| #[derive(Clone, Eq, PartialEq, Debug, Hash, GenericOverIp)] |
| #[generic_over_ip()] |
| pub enum EthernetTimerId<D: device::WeakId> { |
| Arp(ArpTimerId<EthernetLinkDevice, D>), |
| Nudv6(NudTimerId<Ipv6, EthernetLinkDevice, D>), |
| } |
| |
| impl<I: Ip, D: device::WeakId> From<NudTimerId<I, EthernetLinkDevice, D>> for EthernetTimerId<D> { |
| fn from(id: NudTimerId<I, EthernetLinkDevice, D>) -> EthernetTimerId<D> { |
| I::map_ip(id, EthernetTimerId::Arp, EthernetTimerId::Nudv6) |
| } |
| } |
| |
| impl<CC, BC> TimerHandler<BC, EthernetTimerId<CC::WeakDeviceId>> for CC |
| where |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + TimerHandler<BC, NudTimerId<Ipv6, EthernetLinkDevice, CC::WeakDeviceId>> |
| + TimerHandler<BC, ArpTimerId<EthernetLinkDevice, CC::WeakDeviceId>>, |
| { |
| fn handle_timer(&mut self, bindings_ctx: &mut BC, id: EthernetTimerId<CC::WeakDeviceId>) { |
| match id { |
| EthernetTimerId::Arp(id) => self.handle_timer(bindings_ctx, id), |
| EthernetTimerId::Nudv6(id) => self.handle_timer(bindings_ctx, id), |
| } |
| } |
| } |
| |
| /// Send an IP packet in an Ethernet frame. |
| /// |
| /// `send_ip_frame` accepts a device ID, a local IP address, and a |
| /// serializer. It computes the routing information, serializes |
| /// the serializer, and sends the resulting buffer in a new Ethernet |
| /// frame. |
| pub(super) fn send_ip_frame<BC, CC, A, S>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device_id: &CC::DeviceId, |
| local_addr: SpecifiedAddr<A>, |
| body: S, |
| broadcast: Option<<A::Version as IpTypesIpExt>::BroadcastMarker>, |
| ) -> Result<(), S> |
| where |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId> |
| + DeviceSocketBindingsContext<CC::DeviceId> |
| + LinkResolutionContext<EthernetLinkDevice>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + NudHandler<A::Version, EthernetLinkDevice, BC> |
| + TransmitQueueHandler<EthernetLinkDevice, BC, Meta = ()> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters>, |
| A: IpAddress, |
| S: Serializer, |
| S::Buffer: BufferMut, |
| A::Version: EthernetIpExt + IpTypesIpExt, |
| { |
| fn increment_counter<I: Ip>(counters: &DeviceCounters) { |
| let () = I::map_ip( |
| (), |
| |()| counters.send_ipv4_frame.increment(), |
| |()| counters.send_ipv6_frame.increment(), |
| ); |
| } |
| core_ctx.with_per_resource_counters(device_id, increment_counter::<A::Version>); |
| core_ctx.with_counters(increment_counter::<A::Version>); |
| |
| trace!( |
| "ethernet::send_ip_frame: local_addr = {:?}; device = {:?}; broadcast = {:?}", |
| local_addr, |
| device_id, |
| broadcast |
| ); |
| |
| let body = body.with_size_limit(get_mtu(core_ctx, device_id).get() as usize); |
| |
| match broadcast { |
| Some(marker) => { |
| <A::Version as Ip>::map_ip::<_, ()>( |
| WrapBroadcastMarker(marker), |
| |WrapBroadcastMarker(())| (), |
| |WrapBroadcastMarker(never)| match never {}, |
| ); |
| send_as_ethernet_frame_to_dst( |
| core_ctx, |
| bindings_ctx, |
| device_id, |
| Mac::BROADCAST, |
| body, |
| A::Version::ETHER_TYPE, |
| ) |
| } |
| None => { |
| if let Some(multicast) = MulticastAddr::new(local_addr.get()) { |
| send_as_ethernet_frame_to_dst( |
| core_ctx, |
| bindings_ctx, |
| device_id, |
| Mac::from(&multicast), |
| body, |
| A::Version::ETHER_TYPE, |
| ) |
| } else { |
| NudHandler::<A::Version, _, _>::send_ip_packet_to_neighbor( |
| core_ctx, |
| bindings_ctx, |
| device_id, |
| local_addr, |
| body, |
| ) |
| } |
| } |
| } |
| .map_err(Nested::into_inner) |
| } |
| |
| /// Metadata for received ethernet frames. |
| pub struct RecvEthernetFrameMeta<D> { |
| /// The device a frame was received on. |
| pub device_id: D, |
| } |
| |
| impl DeviceReceiveFrameSpec for EthernetLinkDevice { |
| type FrameMetadata<D> = RecvEthernetFrameMeta<D>; |
| } |
| |
| impl<CC, BC> RecvFrameContext<BC, RecvEthernetFrameMeta<CC::DeviceId>> for CC |
| where |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId> |
| + DeviceSocketBindingsContext<CC::DeviceId> |
| + TracingContext, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + RecvFrameContext<BC, RecvIpFrameMeta<CC::DeviceId, Ipv4>> |
| + RecvFrameContext<BC, RecvIpFrameMeta<CC::DeviceId, Ipv6>> |
| + ArpPacketHandler<EthernetLinkDevice, BC> |
| + DeviceSocketHandler<EthernetLinkDevice, BC> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters> |
| + ResourceCounterContext<CC::DeviceId, EthernetDeviceCounters>, |
| { |
| fn receive_frame<B: BufferMut + Debug>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| metadata: RecvEthernetFrameMeta<CC::DeviceId>, |
| mut buffer: B, |
| ) { |
| trace_duration!(bindings_ctx, c"device::ethernet::receive_frame"); |
| let RecvEthernetFrameMeta { device_id } = metadata; |
| trace!("ethernet::receive_frame: device_id = {:?}", device_id); |
| self.increment(&device_id, |counters: &DeviceCounters| &counters.recv_frame); |
| // NOTE(joshlf): We do not currently validate that the Ethernet frame |
| // satisfies the minimum length requirement. We expect that if this |
| // requirement is necessary (due to requirements of the physical medium), |
| // the driver or hardware will have checked it, and that if this requirement |
| // is not necessary, it is acceptable for us to operate on a smaller |
| // Ethernet frame. If this becomes insufficient in the future, we may want |
| // to consider making this behavior configurable (at compile time, at |
| // runtime on a global basis, or at runtime on a per-device basis). |
| let (ethernet, whole_frame) = if let Ok(frame) = |
| buffer.parse_with_view::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck) |
| { |
| frame |
| } else { |
| self.increment(&device_id, |counters: &DeviceCounters| &counters.recv_parse_error); |
| trace!("ethernet::receive_frame: failed to parse ethernet frame"); |
| return; |
| }; |
| |
| let dst = ethernet.dst_mac(); |
| |
| let frame_dest = self.with_ethernet_state(&device_id, |static_state, dynamic_state| { |
| deliver_as(static_state, dynamic_state, &dst) |
| }); |
| |
| let frame_dst = match frame_dest { |
| None => { |
| self.increment(&device_id, |counters: &EthernetDeviceCounters| { |
| &counters.recv_ethernet_other_dest |
| }); |
| trace!( |
| "ethernet::receive_frame: destination mac {:?} not for device {:?}", |
| dst, |
| device_id |
| ); |
| return; |
| } |
| Some(frame_dest) => frame_dest, |
| }; |
| let ethertype = ethernet.ethertype(); |
| |
| self.handle_frame( |
| bindings_ctx, |
| &device_id, |
| ReceivedFrame::from_ethernet(ethernet, frame_dst).into(), |
| whole_frame, |
| ); |
| |
| match ethertype { |
| Some(EtherType::Arp) => { |
| let types = if let Ok(types) = peek_arp_types(buffer.as_ref()) { |
| types |
| } else { |
| return; |
| }; |
| match types { |
| (ArpHardwareType::Ethernet, ArpNetworkType::Ipv4) => { |
| ArpPacketHandler::handle_packet( |
| self, |
| bindings_ctx, |
| device_id, |
| frame_dst, |
| buffer, |
| ) |
| } |
| } |
| } |
| Some(EtherType::Ipv4) => { |
| self.increment(&device_id, |counters: &DeviceCounters| { |
| &counters.recv_ipv4_delivered |
| }); |
| self.receive_frame( |
| bindings_ctx, |
| RecvIpFrameMeta::<_, Ipv4>::new(device_id, Some(frame_dst)), |
| buffer, |
| ) |
| } |
| Some(EtherType::Ipv6) => { |
| self.increment(&device_id, |counters: &DeviceCounters| { |
| &counters.recv_ipv6_delivered |
| }); |
| self.receive_frame( |
| bindings_ctx, |
| RecvIpFrameMeta::<_, Ipv6>::new(device_id, Some(frame_dst)), |
| buffer, |
| ) |
| } |
| Some(EtherType::Other(_)) => { |
| self.increment(&device_id, |counters: &EthernetDeviceCounters| { |
| &counters.recv_unsupported_ethertype |
| }); |
| } |
| None => { |
| self.increment(&device_id, |counters: &EthernetDeviceCounters| { |
| &counters.recv_no_ethertype |
| }); |
| } |
| } |
| } |
| } |
| |
| /// Set the promiscuous mode flag on `device_id`. |
| // NB: We don't currently have a need to expose this function so it's defined as |
| // testonly. |
| #[cfg(test)] |
| pub(super) fn set_promiscuous_mode< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &mut CC, |
| device_id: &CC::DeviceId, |
| enabled: bool, |
| ) { |
| core_ctx.with_ethernet_state_mut(device_id, |_static_state, dynamic_state| { |
| dynamic_state.promiscuous_mode = enabled |
| }) |
| } |
| |
| /// Add `device_id` to a link multicast group `multicast_addr`. |
| /// |
| /// Calling `join_link_multicast` with the same `device_id` and `multicast_addr` |
| /// is completely safe. A counter will be kept for the number of times |
| /// `join_link_multicast` has been called with the same `device_id` and |
| /// `multicast_addr` pair. To completely leave a multicast group, |
| /// [`leave_link_multicast`] must be called the same number of times |
| /// `join_link_multicast` has been called for the same `device_id` and |
| /// `multicast_addr` pair. The first time `join_link_multicast` is called for a |
| /// new `device` and `multicast_addr` pair, the device will actually join the |
| /// multicast group. |
| /// |
| /// `join_link_multicast` is different from [`join_ip_multicast`] as |
| /// `join_link_multicast` joins an L2 multicast group, whereas |
| /// `join_ip_multicast` joins an L3 multicast group. |
| pub(super) fn join_link_multicast< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| device_id: &CC::DeviceId, |
| multicast_addr: MulticastAddr<Mac>, |
| ) { |
| core_ctx.with_ethernet_state_mut(device_id, |_static_state, dynamic_state| { |
| let groups = &mut dynamic_state.link_multicast_groups; |
| |
| match groups.insert(multicast_addr) { |
| InsertResult::Inserted(()) => { |
| trace!( |
| "ethernet::join_link_multicast: joining link multicast {:?}", |
| multicast_addr |
| ); |
| } |
| InsertResult::AlreadyPresent => { |
| trace!( |
| "ethernet::join_link_multicast: already joined link multicast {:?}", |
| multicast_addr, |
| ); |
| } |
| } |
| }) |
| } |
| |
| /// Remove `device_id` from a link multicast group `multicast_addr`. |
| /// |
| /// `leave_link_multicast` will attempt to remove `device_id` from the multicast |
| /// group `multicast_addr`. `device_id` may have "joined" the same multicast |
| /// address multiple times, so `device_id` will only leave the multicast group |
| /// once `leave_ip_multicast` has been called for each corresponding |
| /// [`join_link_multicast`]. That is, if `join_link_multicast` gets called 3 |
| /// times and `leave_link_multicast` gets called two times (after all 3 |
| /// `join_link_multicast` calls), `device_id` will still be in the multicast |
| /// group until the next (final) call to `leave_link_multicast`. |
| /// |
| /// `leave_link_multicast` is different from [`leave_ip_multicast`] as |
| /// `leave_link_multicast` leaves an L2 multicast group, whereas |
| /// `leave_ip_multicast` leaves an L3 multicast group. |
| /// |
| /// # Panics |
| /// |
| /// If `device_id` is not in the multicast group `multicast_addr`. |
| pub(super) fn leave_link_multicast< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| device_id: &CC::DeviceId, |
| multicast_addr: MulticastAddr<Mac>, |
| ) { |
| core_ctx.with_ethernet_state_mut(device_id, |_static_state, dynamic_state| { |
| let groups = &mut dynamic_state.link_multicast_groups; |
| |
| match groups.remove(multicast_addr) { |
| RemoveResult::Removed(()) => { |
| trace!("ethernet::leave_link_multicast: leaving link multicast {:?}", multicast_addr); |
| } |
| RemoveResult::StillPresent => { |
| trace!( |
| "ethernet::leave_link_multicast: not leaving link multicast {:?} as there are still listeners for it", |
| multicast_addr, |
| ); |
| } |
| RemoveResult::NotPresent => { |
| panic!( |
| "ethernet::leave_link_multicast: device {:?} has not yet joined link multicast {:?}", |
| device_id, |
| multicast_addr, |
| ); |
| } |
| } |
| }) |
| } |
| |
| /// Get the MTU associated with this device. |
| pub(super) fn get_mtu< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &mut CC, |
| device_id: &CC::DeviceId, |
| ) -> Mtu { |
| core_ctx.with_ethernet_state(device_id, |_static_state, dynamic_state| { |
| dynamic_state.max_frame_size.as_mtu() |
| }) |
| } |
| |
| impl< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId> |
| + DeviceSocketBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + TransmitQueueHandler<EthernetLinkDevice, BC, Meta = ()> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters>, |
| > SendFrameContext<BC, ArpFrameMetadata<EthernetLinkDevice, CC::DeviceId>> for CC |
| { |
| fn send_frame<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ArpFrameMetadata { device_id, dst_addr }: ArpFrameMetadata< |
| EthernetLinkDevice, |
| CC::DeviceId, |
| >, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| { |
| send_as_ethernet_frame_to_dst( |
| self, |
| bindings_ctx, |
| &device_id, |
| dst_addr, |
| body, |
| EtherType::Arp, |
| ) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::IpState<Ipv4>>> |
| ArpContext<EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| type ConfigCtx<'a> = |
| CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, crate::lock_ordering::EthernetIpv4Arp>>; |
| |
| type ArpSenderCtx<'a> = |
| CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, crate::lock_ordering::EthernetIpv4Arp>>; |
| |
| fn with_arp_state_mut_and_sender_ctx< |
| O, |
| F: FnOnce(&mut ArpState<EthernetLinkDevice, BC>, &mut Self::ArpSenderCtx<'_>) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let (mut arp, mut locked) = |
| core_ctx_and_resource |
| .lock_with_and::<crate::lock_ordering::EthernetIpv4Arp, _>(|c| c.right()); |
| let mut locked = |
| CoreCtxWithDeviceId { device_id, core_ctx: &mut locked.cast_core_ctx() }; |
| cb(&mut arp, &mut locked) |
| }, |
| ) |
| } |
| |
| fn get_protocol_addr( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device_id: &EthernetDeviceId<BC>, |
| ) -> Option<Ipv4Addr> { |
| device::integration::with_device_state(self, device_id, |mut state| { |
| let mut state = state.cast(); |
| let ipv4 = state.read_lock::<crate::lock_ordering::IpDeviceAddresses<Ipv4>>(); |
| let x = ipv4.iter().next().map(|addr| addr.addr().get()); |
| x |
| }) |
| } |
| |
| fn get_hardware_addr( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device_id: &EthernetDeviceId<BC>, |
| ) -> UnicastAddr<Mac> { |
| get_mac(self, device_id) |
| } |
| |
| fn with_arp_state_mut< |
| O, |
| F: FnOnce(&mut ArpState<EthernetLinkDevice, BC>, &mut Self::ConfigCtx<'_>) -> O, |
| >( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let (mut arp, mut locked) = |
| core_ctx_and_resource |
| .lock_with_and::<crate::lock_ordering::EthernetIpv4Arp, _>(|c| c.right()); |
| let mut locked = |
| CoreCtxWithDeviceId { device_id, core_ctx: &mut locked.cast_core_ctx() }; |
| cb(&mut arp, &mut locked) |
| }, |
| ) |
| } |
| |
| fn with_arp_state<O, F: FnOnce(&ArpState<EthernetLinkDevice, BC>) -> O>( |
| &mut self, |
| device_id: &EthernetDeviceId<BC>, |
| cb: F, |
| ) -> O { |
| device::integration::with_device_state_and_core_ctx( |
| self, |
| device_id, |
| |mut core_ctx_and_resource| { |
| let arp = core_ctx_and_resource |
| .lock_with::<crate::lock_ordering::EthernetIpv4Arp, _>(|c| c.right()); |
| cb(&arp) |
| }, |
| ) |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::IcmpAllSocketsSet<Ipv4>>> |
| NudIcmpContext<Ipv4, EthernetLinkDevice, BC> for CoreCtx<'_, BC, L> |
| { |
| fn send_icmp_dest_unreachable( |
| &mut self, |
| bindings_ctx: &mut BC, |
| frame: Buf<Vec<u8>>, |
| device_id: Option<&Self::DeviceId>, |
| original_src_ip: SocketIpAddr<Ipv4Addr>, |
| original_dst_ip: SocketIpAddr<Ipv4Addr>, |
| (header_len, fragment_type): (usize, Ipv4FragmentType), |
| ) { |
| crate::ip::icmp::send_icmpv4_host_unreachable( |
| self, |
| bindings_ctx, |
| device_id.map(|device_id| device_id.clone().into()).as_ref(), |
| // NB: link layer address resolution only happens for packets destined for |
| // a unicast address, so passing `None` as `FrameDestination` here is always |
| // correct since there's never a need to not send the ICMP error due to |
| // a multicast/broadcast destination. |
| None, |
| original_src_ip, |
| original_dst_ip, |
| frame, |
| header_len, |
| fragment_type, |
| ); |
| } |
| } |
| |
| impl<'a, BC: BindingsContext, L: LockBefore<crate::lock_ordering::NudConfig<Ipv4>>> ArpConfigContext |
| for CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, L>> |
| { |
| fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O { |
| let Self { device_id, core_ctx } = self; |
| device::integration::with_device_state(core_ctx, device_id, |mut state| { |
| let x = state.read_lock::<crate::lock_ordering::NudConfig<Ipv4>>(); |
| cb(&*x) |
| }) |
| } |
| } |
| |
| impl<'a, BC: BindingsContext, L: LockBefore<crate::lock_ordering::AllDeviceSockets>> |
| ArpSenderContext<EthernetLinkDevice, BC> for CoreCtxWithDeviceId<'a, CoreCtx<'a, BC, L>> |
| { |
| fn send_ip_packet_to_neighbor_link_addr<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| dst_mac: Mac, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| { |
| let Self { device_id, core_ctx } = self; |
| send_as_ethernet_frame_to_dst( |
| *core_ctx, |
| bindings_ctx, |
| device_id, |
| dst_mac, |
| body, |
| EtherType::Ipv4, |
| ) |
| } |
| } |
| impl< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC> |
| + TransmitQueueHandler<EthernetLinkDevice, BC, Meta = ()> |
| + ResourceCounterContext<CC::DeviceId, DeviceCounters>, |
| > SendFrameContext<BC, DeviceSocketMetadata<CC::DeviceId>> for CC |
| { |
| fn send_frame<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| metadata: DeviceSocketMetadata<CC::DeviceId>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| { |
| let DeviceSocketMetadata { device_id, header } = metadata; |
| match header { |
| Some(DatagramHeader { dest_addr, protocol }) => send_as_ethernet_frame_to_dst( |
| self, |
| bindings_ctx, |
| &device_id, |
| dest_addr, |
| body, |
| protocol, |
| ), |
| None => send_ethernet_frame(self, bindings_ctx, &device_id, body), |
| } |
| } |
| } |
| |
| pub(super) fn get_mac< |
| 'a, |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &'a mut CC, |
| device_id: &CC::DeviceId, |
| ) -> UnicastAddr<Mac> { |
| core_ctx.with_static_ethernet_device_state(device_id, |state| state.mac) |
| } |
| |
| pub(super) fn set_mtu< |
| BC: EthernetIpLinkDeviceBindingsContext<CC::DeviceId>, |
| CC: EthernetIpLinkDeviceDynamicStateContext<BC>, |
| >( |
| core_ctx: &mut CC, |
| device_id: &CC::DeviceId, |
| mtu: Mtu, |
| ) { |
| core_ctx.with_ethernet_state_mut(device_id, |static_state, dynamic_state| { |
| if let Some(mut frame_size ) = MaxEthernetFrameSize::from_mtu(mtu) { |
| // If `frame_size` is greater than what the device supports, set it |
| // to maximum frame size the device supports. |
| if frame_size > static_state.max_frame_size { |
| trace!("ethernet::ndp_device::set_mtu: MTU of {:?} is greater than the device {:?}'s max MTU of {:?}, using device's max MTU instead", mtu, device_id, static_state.max_frame_size.as_mtu()); |
| frame_size = static_state.max_frame_size; |
| } |
| trace!("ethernet::ndp_device::set_mtu: setting link MTU to {:?}", mtu); |
| dynamic_state.max_frame_size = frame_size; |
| } |
| }) |
| } |
| |
| /// An implementation of the [`LinkDevice`] trait for Ethernet devices. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub enum EthernetLinkDevice {} |
| |
| impl Device for EthernetLinkDevice {} |
| |
| impl LinkDevice for EthernetLinkDevice { |
| type Address = Mac; |
| } |
| |
| impl DeviceStateSpec for EthernetLinkDevice { |
| type Link<BT: DeviceLayerTypes> = EthernetDeviceState<BT>; |
| type External<BT: DeviceLayerTypes> = BT::EthernetDeviceState; |
| type CreationProperties = EthernetCreationProperties; |
| type Counters = EthernetDeviceCounters; |
| type TimerId<D: device::WeakId> = EthernetTimerId<D>; |
| |
| fn new_link_state< |
| CC: CoreTimerContext<Self::TimerId<CC::WeakDeviceId>, BC> + DeviceIdContext<Self>, |
| BC: DeviceLayerTypes + TimerContext2, |
| >( |
| bindings_ctx: &mut BC, |
| self_id: CC::WeakDeviceId, |
| EthernetCreationProperties { mac, max_frame_size }: Self::CreationProperties, |
| ) -> Self::Link<BC> { |
| let ipv4_arp = Mutex::new(ArpState::new(bindings_ctx, self_id.clone(), |arp| { |
| CC::convert_timer(EthernetTimerId::from(arp)) |
| })); |
| let ipv6_nud = Mutex::new(NudState::new(bindings_ctx, self_id, |nud| { |
| CC::convert_timer(EthernetTimerId::from(nud)) |
| })); |
| EthernetDeviceState { |
| counters: Default::default(), |
| ipv4_arp, |
| ipv6_nud, |
| ipv4_nud_config: Default::default(), |
| ipv6_nud_config: Default::default(), |
| static_state: StaticEthernetDeviceState { mac, max_frame_size }, |
| dynamic_state: RwLock::new(DynamicEthernetDeviceState::new(max_frame_size)), |
| tx_queue: Default::default(), |
| } |
| } |
| const IS_LOOPBACK: bool = false; |
| const DEBUG_TYPE: &'static str = "Ethernet"; |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use alloc::vec; |
| use assert_matches::assert_matches; |
| |
| use ip_test_macro::ip_test; |
| use net_declare::net_mac; |
| use net_types::ip::{AddrSubnet, IpAddr, IpVersion}; |
| use packet_formats::{ |
| ethernet::ETHERNET_MIN_BODY_LEN_NO_TAG, |
| icmp::IcmpDestUnreachable, |
| ip::{IpExt, IpPacketBuilder, IpProto}, |
| testdata::{dns_request_v4, dns_request_v6}, |
| testutil::{ |
| parse_ethernet_frame, parse_icmp_packet_in_ip_packet_in_ethernet_frame, |
| parse_ip_packet_in_ethernet_frame, |
| }, |
| }; |
| use rand::Rng; |
| use test_case::test_case; |
| |
| use super::*; |
| use crate::{ |
| context::testutil::{FakeFrameCtx, FakeInstant}, |
| device::{ |
| arp::ArpCounters, |
| socket::Frame, |
| testutil::{set_forwarding_enabled, FakeDeviceId, FakeWeakDeviceId}, |
| DeviceId, |
| }, |
| error::NotFoundError, |
| ip::device::{ |
| api::AddIpAddrSubnetError, |
| config::{IpDeviceConfigurationUpdate, Ipv6DeviceConfigurationUpdate}, |
| nud::{self, api::NeighborApi, DynamicNeighborUpdateSource}, |
| slaac::SlaacConfiguration, |
| IpAddressId as _, |
| }, |
| testutil::{ |
| add_arp_or_ndp_table_entry, new_rng, FakeEventDispatcherBuilder, TestIpExt, |
| DEFAULT_INTERFACE_METRIC, FAKE_CONFIG_V4, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| }; |
| |
| struct FakeEthernetCtx { |
| static_state: StaticEthernetDeviceState, |
| dynamic_state: DynamicEthernetDeviceState, |
| tx_queue: TransmitQueueState<(), Buf<Vec<u8>>, BufVecU8Allocator>, |
| counters: DeviceCounters, |
| per_device_counters: DeviceCounters, |
| ethernet_counters: EthernetDeviceCounters, |
| arp_counters: ArpCounters, |
| } |
| |
| impl FakeEthernetCtx { |
| fn new(mac: UnicastAddr<Mac>, max_frame_size: MaxEthernetFrameSize) -> FakeEthernetCtx { |
| FakeEthernetCtx { |
| static_state: StaticEthernetDeviceState { max_frame_size, mac }, |
| dynamic_state: DynamicEthernetDeviceState::new(max_frame_size), |
| tx_queue: Default::default(), |
| counters: Default::default(), |
| per_device_counters: Default::default(), |
| ethernet_counters: Default::default(), |
| arp_counters: Default::default(), |
| } |
| } |
| } |
| |
| type FakeBindingsCtx = crate::context::testutil::FakeBindingsCtx< |
| EthernetTimerId<FakeWeakDeviceId<FakeDeviceId>>, |
| nud::Event<Mac, FakeDeviceId, Ipv4, FakeInstant>, |
| (), |
| (), |
| >; |
| |
| type FakeCoreCtx = crate::context::testutil::WrappedFakeCoreCtx< |
| ArpState<EthernetLinkDevice, FakeBindingsCtx>, |
| FakeEthernetCtx, |
| FakeDeviceId, |
| FakeDeviceId, |
| >; |
| |
| type FakeInnerCtx = |
| crate::context::testutil::FakeCoreCtx<FakeEthernetCtx, FakeDeviceId, FakeDeviceId>; |
| |
| impl DeviceSocketHandler<EthernetLinkDevice, FakeBindingsCtx> for FakeCoreCtx { |
| fn handle_frame( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx, |
| device: &Self::DeviceId, |
| frame: Frame<&[u8]>, |
| whole_frame: &[u8], |
| ) { |
| self.inner.handle_frame(bindings_ctx, device, frame, whole_frame) |
| } |
| } |
| |
| impl CounterContext<DeviceCounters> for FakeCoreCtx { |
| fn with_counters<O, F: FnOnce(&DeviceCounters) -> O>(&self, cb: F) -> O { |
| cb(&self.as_ref().state.counters) |
| } |
| } |
| |
| impl CounterContext<DeviceCounters> for FakeInnerCtx { |
| fn with_counters<O, F: FnOnce(&DeviceCounters) -> O>(&self, cb: F) -> O { |
| cb(&self.state.counters) |
| } |
| } |
| |
| impl ResourceCounterContext<FakeDeviceId, DeviceCounters> for FakeCoreCtx { |
| fn with_per_resource_counters<O, F: FnOnce(&DeviceCounters) -> O>( |
| &mut self, |
| &FakeDeviceId: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| cb(&self.as_ref().state.per_device_counters) |
| } |
| } |
| |
| impl ResourceCounterContext<FakeDeviceId, DeviceCounters> for FakeInnerCtx { |
| fn with_per_resource_counters<O, F: FnOnce(&DeviceCounters) -> O>( |
| &mut self, |
| &FakeDeviceId: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| cb(&self.state.per_device_counters) |
| } |
| } |
| |
| impl CounterContext<EthernetDeviceCounters> for FakeCoreCtx { |
| fn with_counters<O, F: FnOnce(&EthernetDeviceCounters) -> O>(&self, cb: F) -> O { |
| cb(&self.as_ref().state.ethernet_counters) |
| } |
| } |
| |
| impl CounterContext<EthernetDeviceCounters> for FakeInnerCtx { |
| fn with_counters<O, F: FnOnce(&EthernetDeviceCounters) -> O>(&self, cb: F) -> O { |
| cb(&self.state.ethernet_counters) |
| } |
| } |
| |
| impl DeviceSocketHandler<EthernetLinkDevice, FakeBindingsCtx> for FakeInnerCtx { |
| fn handle_frame( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| _device: &Self::DeviceId, |
| _frame: Frame<&[u8]>, |
| _whole_frame: &[u8], |
| ) { |
| // No-op: don't deliver frames. |
| } |
| } |
| |
| impl EthernetIpLinkDeviceStaticStateContext for FakeCoreCtx { |
| fn with_static_ethernet_device_state<O, F: FnOnce(&StaticEthernetDeviceState) -> O>( |
| &mut self, |
| device_id: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| self.inner.with_static_ethernet_device_state(device_id, cb) |
| } |
| } |
| |
| impl EthernetIpLinkDeviceStaticStateContext for FakeInnerCtx { |
| fn with_static_ethernet_device_state<O, F: FnOnce(&StaticEthernetDeviceState) -> O>( |
| &mut self, |
| &FakeDeviceId: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| cb(&self.get_ref().static_state) |
| } |
| } |
| |
| impl EthernetIpLinkDeviceDynamicStateContext<FakeBindingsCtx> for FakeCoreCtx { |
| fn with_ethernet_state< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| self.inner.with_ethernet_state(device_id, cb) |
| } |
| |
| fn with_ethernet_state_mut< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &mut DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| device_id: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| self.inner.with_ethernet_state_mut(device_id, cb) |
| } |
| } |
| |
| impl EthernetIpLinkDeviceDynamicStateContext<FakeBindingsCtx> for FakeInnerCtx { |
| fn with_ethernet_state< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| &FakeDeviceId: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| let state = self.get_ref(); |
| cb(&state.static_state, &state.dynamic_state) |
| } |
| |
| fn with_ethernet_state_mut< |
| O, |
| F: FnOnce(&StaticEthernetDeviceState, &mut DynamicEthernetDeviceState) -> O, |
| >( |
| &mut self, |
| &FakeDeviceId: &FakeDeviceId, |
| cb: F, |
| ) -> O { |
| let state = self.get_mut(); |
| cb(&state.static_state, &mut state.dynamic_state) |
| } |
| } |
| |
| impl NudHandler<Ipv6, EthernetLinkDevice, FakeBindingsCtx> for FakeCoreCtx { |
| fn handle_neighbor_update( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| _device_id: &Self::DeviceId, |
| _neighbor: SpecifiedAddr<Ipv6Addr>, |
| _link_addr: Mac, |
| _is_confirmation: DynamicNeighborUpdateSource, |
| ) { |
| unimplemented!() |
| } |
| |
| fn flush(&mut self, _bindings_ctx: &mut FakeBindingsCtx, _device_id: &Self::DeviceId) { |
| unimplemented!() |
| } |
| |
| fn send_ip_packet_to_neighbor<S>( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| _device_id: &Self::DeviceId, |
| _neighbor: SpecifiedAddr<Ipv6Addr>, |
| _body: S, |
| ) -> Result<(), S> { |
| unimplemented!() |
| } |
| } |
| |
| impl<'a> ArpConfigContext for CoreCtxWithDeviceId<'a, FakeInnerCtx> { |
| fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O { |
| cb(&NudUserConfig::default()) |
| } |
| } |
| |
| impl ArpContext<EthernetLinkDevice, FakeBindingsCtx> for FakeCoreCtx { |
| type ConfigCtx<'a> = CoreCtxWithDeviceId<'a, FakeInnerCtx>; |
| |
| type ArpSenderCtx<'a> = CoreCtxWithDeviceId<'a, FakeInnerCtx>; |
| |
| fn with_arp_state_mut_and_sender_ctx< |
| O, |
| F: FnOnce( |
| &mut ArpState<EthernetLinkDevice, FakeBindingsCtx>, |
| &mut Self::ArpSenderCtx<'_>, |
| ) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner } = self; |
| cb(outer, &mut CoreCtxWithDeviceId { core_ctx: inner, device_id }) |
| } |
| |
| fn get_protocol_addr( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| _device_id: &Self::DeviceId, |
| ) -> Option<Ipv4Addr> { |
| unimplemented!() |
| } |
| |
| fn get_hardware_addr( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| _device_id: &Self::DeviceId, |
| ) -> UnicastAddr<Mac> { |
| self.inner.get_ref().static_state.mac |
| } |
| |
| fn with_arp_state_mut< |
| O, |
| F: FnOnce( |
| &mut ArpState<EthernetLinkDevice, FakeBindingsCtx>, |
| &mut Self::ConfigCtx<'_>, |
| ) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner } = self; |
| cb(outer, &mut CoreCtxWithDeviceId { core_ctx: inner, device_id }) |
| } |
| |
| fn with_arp_state<O, F: FnOnce(&ArpState<EthernetLinkDevice, FakeBindingsCtx>) -> O>( |
| &mut self, |
| FakeDeviceId: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| let Self { outer, inner: _ } = self; |
| cb(outer) |
| } |
| } |
| |
| impl ArpConfigContext for FakeInnerCtx { |
| fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O { |
| cb(&NudUserConfig::default()) |
| } |
| } |
| |
| impl<'a> ArpSenderContext<EthernetLinkDevice, FakeBindingsCtx> |
| for CoreCtxWithDeviceId<'a, FakeInnerCtx> |
| { |
| fn send_ip_packet_to_neighbor_link_addr<S>( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx, |
| link_addr: Mac, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer, |
| S::Buffer: BufferMut, |
| { |
| let Self { core_ctx, device_id } = self; |
| send_as_ethernet_frame_to_dst( |
| *core_ctx, |
| bindings_ctx, |
| device_id, |
| link_addr, |
| body, |
| EtherType::Ipv4, |
| ) |
| } |
| } |
| |
| impl TransmitQueueBindingsContext<EthernetLinkDevice, FakeDeviceId> for FakeBindingsCtx { |
| fn wake_tx_task(&mut self, FakeDeviceId: &FakeDeviceId) { |
| unimplemented!("unused by tests") |
| } |
| } |
| |
| impl TransmitQueueCommon<EthernetLinkDevice, FakeBindingsCtx> for FakeCoreCtx { |
| type Meta = (); |
| type Allocator = BufVecU8Allocator; |
| type Buffer = Buf<Vec<u8>>; |
| |
| fn parse_outgoing_frame(buf: &[u8]) -> Result<SentFrame<&[u8]>, ParseSentFrameError> { |
| FakeInnerCtx::parse_outgoing_frame(buf) |
| } |
| } |
| |
| impl TransmitQueueCommon<EthernetLinkDevice, FakeBindingsCtx> for FakeInnerCtx { |
| type Meta = (); |
| type Allocator = BufVecU8Allocator; |
| type Buffer = Buf<Vec<u8>>; |
| |
| fn parse_outgoing_frame(buf: &[u8]) -> Result<SentFrame<&[u8]>, ParseSentFrameError> { |
| SentFrame::try_parse_as_ethernet(buf) |
| } |
| } |
| |
| impl TransmitQueueContext<EthernetLinkDevice, FakeBindingsCtx> for FakeCoreCtx { |
| fn with_transmit_queue_mut< |
| O, |
| F: FnOnce(&mut TransmitQueueState<Self::Meta, Self::Buffer, Self::Allocator>) -> O, |
| >( |
| &mut self, |
| device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| self.inner.with_transmit_queue_mut(device_id, cb) |
| } |
| |
| fn send_frame( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx, |
| device_id: &Self::DeviceId, |
| (): Self::Meta, |
| buf: Self::Buffer, |
| ) -> Result<(), DeviceSendFrameError<(Self::Meta, Self::Buffer)>> { |
| TransmitQueueContext::send_frame(&mut self.inner, bindings_ctx, device_id, (), buf) |
| } |
| } |
| |
| impl TransmitQueueContext<EthernetLinkDevice, FakeBindingsCtx> for FakeInnerCtx { |
| fn with_transmit_queue_mut< |
| O, |
| F: FnOnce(&mut TransmitQueueState<Self::Meta, Self::Buffer, Self::Allocator>) -> O, |
| >( |
| &mut self, |
| _device_id: &Self::DeviceId, |
| cb: F, |
| ) -> O { |
| cb(&mut self.get_mut().tx_queue) |
| } |
| |
| fn send_frame( |
| &mut self, |
| _bindings_ctx: &mut FakeBindingsCtx, |
| device_id: &Self::DeviceId, |
| (): Self::Meta, |
| buf: Self::Buffer, |
| ) -> Result<(), DeviceSendFrameError<(Self::Meta, Self::Buffer)>> { |
| let frame_ctx: &mut FakeFrameCtx<_> = self.as_mut(); |
| frame_ctx.push(device_id.clone(), buf.as_ref().to_vec()); |
| Ok(()) |
| } |
| } |
| |
| impl DeviceIdContext<EthernetLinkDevice> for FakeCoreCtx { |
| type DeviceId = FakeDeviceId; |
| type WeakDeviceId = FakeWeakDeviceId<FakeDeviceId>; |
| } |
| |
| impl DeviceIdContext<EthernetLinkDevice> for FakeInnerCtx { |
| type DeviceId = FakeDeviceId; |
| type WeakDeviceId = FakeWeakDeviceId<FakeDeviceId>; |
| } |
| |
| impl CounterContext<ArpCounters> for FakeCoreCtx { |
| fn with_counters<O, F: FnOnce(&ArpCounters) -> O>(&self, cb: F) -> O { |
| cb(&self.as_ref().get_ref().arp_counters) |
| } |
| } |
| |
| fn contains_addr<A: IpAddress>( |
| core_ctx: &crate::testutil::FakeCoreCtx, |
| device: &DeviceId<crate::testutil::FakeBindingsCtx>, |
| addr: SpecifiedAddr<A>, |
| ) -> bool { |
| match addr.into() { |
| IpAddr::V4(addr) => { |
| crate::ip::device::IpDeviceStateContext::<Ipv4, _>::with_address_ids( |
| &mut core_ctx.context(), |
| device, |
| |mut addrs, _core_ctx| addrs.any(|a| a.addr().addr() == addr.get()), |
| ) |
| } |
| IpAddr::V6(addr) => { |
| crate::ip::device::IpDeviceStateContext::<Ipv6, _>::with_address_ids( |
| &mut core_ctx.context(), |
| device, |
| |mut addrs, _core_ctx| addrs.any(|a| a.addr().addr() == addr.get()), |
| ) |
| } |
| } |
| } |
| |
| #[test] |
| fn test_mtu() { |
| // Test that we send an Ethernet frame whose size is less than the MTU, |
| // and that we don't send an Ethernet frame whose size is greater than |
| // the MTU. |
| fn test(size: usize, expect_frames_sent: bool) { |
| let mut ctx = crate::context::testutil::FakeCtxWithCoreCtx::with_default_bindings_ctx( |
| |bindings_ctx| { |
| FakeCoreCtx::with_inner_and_outer_state( |
| FakeEthernetCtx::new( |
| FAKE_CONFIG_V4.local_mac, |
| IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| ), |
| ArpState::new(bindings_ctx, FakeWeakDeviceId(FakeDeviceId), Into::into), |
| ) |
| }, |
| ); |
| NeighborApi::<Ipv4, EthernetLinkDevice, _>::new(ctx.as_mut()) |
| .insert_static_entry( |
| &FakeDeviceId, |
| FAKE_CONFIG_V4.remote_ip.get(), |
| FAKE_CONFIG_V4.remote_mac.get(), |
| ) |
| .unwrap(); |
| let crate::testutil::ContextPair { core_ctx, bindings_ctx } = &mut ctx; |
| let result = send_ip_frame( |
| core_ctx, |
| bindings_ctx, |
| &FakeDeviceId, |
| FAKE_CONFIG_V4.remote_ip, |
| Buf::new(&mut vec![0; size], ..), |
| None, |
| ) |
| .map_err(|_serializer| ()); |
| let sent_frames = core_ctx.inner.frames().len(); |
| if expect_frames_sent { |
| assert_eq!(sent_frames, 1); |
| result.expect("should succeed"); |
| } else { |
| assert_eq!(sent_frames, 0); |
| result.expect_err("should fail"); |
| } |
| } |
| |
| test(usize::try_from(u32::from(Ipv6::MINIMUM_LINK_MTU)).unwrap(), true); |
| test(usize::try_from(u32::from(Ipv6::MINIMUM_LINK_MTU)).unwrap() + 1, false); |
| } |
| |
| #[test] |
| fn broadcast() { |
| let mut ctx = crate::context::testutil::FakeCtxWithCoreCtx::with_default_bindings_ctx( |
| |bindings_ctx| { |
| FakeCoreCtx::with_inner_and_outer_state( |
| FakeEthernetCtx::new(FAKE_CONFIG_V4.local_mac, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE), |
| ArpState::new(bindings_ctx, FakeWeakDeviceId(FakeDeviceId), Into::into), |
| ) |
| }, |
| ); |
| let crate::testutil::ContextPair { core_ctx, bindings_ctx } = &mut ctx; |
| send_ip_frame( |
| core_ctx, |
| bindings_ctx, |
| &FakeDeviceId, |
| FAKE_CONFIG_V4.remote_ip, |
| Buf::new(&mut vec![0; 100], ..), |
| /* broadcast */ Some(()), |
| ) |
| .map_err(|_serializer| ()) |
| .expect("send_ip_frame should succeed"); |
| let sent_frames = core_ctx.inner.frames().len(); |
| assert_eq!(sent_frames, 1); |
| let (FakeDeviceId, frame) = core_ctx.inner.frames()[0].clone(); |
| let (_body, _src_mac, dst_mac, _ether_type) = |
| parse_ethernet_frame(&frame, EthernetFrameLengthCheck::NoCheck).unwrap(); |
| assert_eq!(dst_mac, Mac::BROADCAST); |
| } |
| |
| #[ip_test] |
| #[test_case(true; "enabled")] |
| #[test_case(false; "disabled")] |
| fn test_receive_ip_frame<I: Ip + TestIpExt>(enable: bool) { |
| // Should only receive a frame if the device is enabled. |
| |
| let config = I::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let eth_device = |
| ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ); |
| let device = eth_device.clone().into(); |
| let mut bytes = match I::VERSION { |
| IpVersion::V4 => dns_request_v4::ETHERNET_FRAME, |
| IpVersion::V6 => dns_request_v6::ETHERNET_FRAME, |
| } |
| .bytes |
| .to_vec(); |
| |
| let mac_bytes = config.local_mac.bytes(); |
| bytes[0..6].copy_from_slice(&mac_bytes); |
| |
| let expected_received = if enable { |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| 1 |
| } else { |
| 0 |
| }; |
| |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device }, Buf::new(bytes, ..)); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().receive_ip_packet.get(), expected_received); |
| } |
| |
| #[test] |
| fn initialize_once() { |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: FAKE_CONFIG_V4.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| } |
| |
| fn is_forwarding_enabled<I: Ip>( |
| ctx: &mut crate::testutil::FakeCtx, |
| device: &DeviceId<crate::testutil::FakeBindingsCtx>, |
| ) -> bool { |
| match I::VERSION { |
| IpVersion::V4 => crate::device::testutil::is_forwarding_enabled::<_, Ipv4>(ctx, device), |
| IpVersion::V6 => crate::device::testutil::is_forwarding_enabled::<_, Ipv6>(ctx, device), |
| } |
| } |
| |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)] |
| fn test_set_ip_routing<I: Ip + TestIpExt + crate::IpExt>() { |
| fn check_other_is_forwarding_enabled<I: Ip>( |
| ctx: &mut crate::testutil::FakeCtx, |
| device: &DeviceId<crate::testutil::FakeBindingsCtx>, |
| expected: bool, |
| ) { |
| let enabled = match I::VERSION { |
| IpVersion::V4 => is_forwarding_enabled::<Ipv6>(ctx, device), |
| IpVersion::V6 => is_forwarding_enabled::<Ipv4>(ctx, device), |
| }; |
| |
| assert_eq!(enabled, expected); |
| } |
| |
| fn check_icmp<I: Ip>(buf: &[u8]) { |
| match I::VERSION { |
| IpVersion::V4 => { |
| let _ = parse_icmp_packet_in_ip_packet_in_ethernet_frame::< |
| Ipv4, |
| _, |
| IcmpDestUnreachable, |
| _, |
| >(buf, EthernetFrameLengthCheck::NoCheck, |_| {}) |
| .unwrap(); |
| } |
| IpVersion::V6 => { |
| let _ = parse_icmp_packet_in_ip_packet_in_ethernet_frame::< |
| Ipv6, |
| _, |
| IcmpDestUnreachable, |
| _, |
| >(buf, EthernetFrameLengthCheck::NoCheck, |_| {}) |
| .unwrap(); |
| } |
| } |
| } |
| |
| let src_ip = I::get_other_ip_address(3); |
| let src_mac = UnicastAddr::new(Mac::new([10, 11, 12, 13, 14, 15])).unwrap(); |
| let config = I::FAKE_CONFIG; |
| let frame_dst = FrameDestination::Individual { local: true }; |
| let mut rng = new_rng(70812476915813); |
| let mut body: Vec<u8> = core::iter::repeat_with(|| rng.gen()).take(100).collect(); |
| let buf = Buf::new(&mut body[..], ..) |
| .encapsulate(I::PacketBuilder::new( |
| src_ip.get(), |
| config.remote_ip.get(), |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // Test with netstack no forwarding |
| |
| let mut builder = FakeEventDispatcherBuilder::from_config(config.clone()); |
| let device_builder_id = 0; |
| add_arp_or_ndp_table_entry(&mut builder, device_builder_id, src_ip, src_mac); |
| let (mut ctx, device_ids) = builder.build(); |
| let device: DeviceId<_> = device_ids[device_builder_id].clone().into(); |
| |
| // Should not be a router (default). |
| assert!(!is_forwarding_enabled::<I>(&mut ctx, &device)); |
| check_other_is_forwarding_enabled::<I>(&mut ctx, &device, false); |
| |
| // Receiving a packet not destined for the node should only result in a |
| // dest unreachable message if routing is enabled. |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), buf.clone()); |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| |
| // Set routing and expect packets to be forwarded. |
| set_forwarding_enabled::<_, I>(&mut ctx, &device, true); |
| assert!(is_forwarding_enabled::<I>(&mut ctx, &device)); |
| // Should not update other Ip routing status. |
| check_other_is_forwarding_enabled::<I>(&mut ctx, &device, false); |
| |
| // Should route the packet since routing fully enabled (netstack & |
| // device). |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), buf.clone()); |
| { |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| let (packet_buf, _, _, packet_src_ip, packet_dst_ip, proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<I>( |
| &frame[..], |
| EthernetFrameLengthCheck::NoCheck, |
| ) |
| .unwrap(); |
| assert_eq!(src_ip.get(), packet_src_ip); |
| assert_eq!(config.remote_ip.get(), packet_dst_ip); |
| assert_eq!(proto, IpProto::Tcp.into()); |
| assert_eq!(body, packet_buf); |
| assert_eq!(ttl, 63); |
| } |
| |
| // Test routing a packet to an unknown address. |
| let buf_unknown_dest = Buf::new(&mut body[..], ..) |
| .encapsulate(I::PacketBuilder::new( |
| src_ip.get(), |
| // Addr must be remote, otherwise this will cause an NDP/ARP |
| // request rather than ICMP unreachable. |
| I::get_other_remote_ip_address(10).get(), |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), buf_unknown_dest); |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| check_icmp::<I>(&frame); |
| |
| // Attempt to unset router |
| set_forwarding_enabled::<_, I>(&mut ctx, &device, false); |
| assert!(!is_forwarding_enabled::<I>(&mut ctx, &device)); |
| check_other_is_forwarding_enabled::<I>(&mut ctx, &device, false); |
| |
| // Should not route packets anymore |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), buf); |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| } |
| |
| #[ip_test] |
| #[test_case(UnicastAddr::new(net_mac!("12:13:14:15:16:17")).unwrap(), true; "unicast")] |
| #[test_case(MulticastAddr::new(net_mac!("13:14:15:16:17:18")).unwrap(), false; "multicast")] |
| fn test_promiscuous_mode<I: Ip + TestIpExt + IpExt>( |
| other_mac: impl Witness<Mac>, |
| is_other_host: bool, |
| ) { |
| // Test that frames not destined for a device will still be accepted |
| // when the device is put into promiscuous mode. In all cases, frames |
| // that are destined for a device must always be accepted. |
| |
| let config = I::FAKE_CONFIG; |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(config.clone()).build(); |
| let eth_device = &device_ids[0]; |
| |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(I::PacketBuilder::new( |
| config.remote_ip.get(), |
| config.local_ip.get(), |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .encapsulate(EthernetFrameBuilder::new( |
| config.remote_mac.get(), |
| config.local_mac.get(), |
| I::ETHER_TYPE, |
| ETHERNET_MIN_BODY_LEN_NO_TAG, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // Accept packet destined for this device if promiscuous mode is off. |
| set_promiscuous_mode(&mut ctx.core_ctx(), ð_device, false); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf.clone()); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet_other_host.get(), 0); |
| |
| // Accept packet destined for this device if promiscuous mode is on. |
| set_promiscuous_mode(&mut ctx.core_ctx(), ð_device, true); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 2); |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet_other_host.get(), 0); |
| |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(I::PacketBuilder::new( |
| config.remote_ip.get(), |
| config.local_ip.get(), |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .encapsulate(EthernetFrameBuilder::new( |
| config.remote_mac.get(), |
| other_mac.get(), |
| I::ETHER_TYPE, |
| ETHERNET_MIN_BODY_LEN_NO_TAG, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // Reject packet not destined for this device if promiscuous mode is |
| // off. |
| set_promiscuous_mode(&mut ctx.core_ctx(), ð_device, false); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf.clone()); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 2); |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet_other_host.get(), 0); |
| |
| // Accept packet not destined for this device if promiscuous mode is on. |
| set_promiscuous_mode(&mut ctx.core_ctx(), ð_device, true); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 3); |
| assert_eq!( |
| ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet_other_host.get(), |
| u64::from(is_other_host) |
| ); |
| } |
| |
| #[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)] |
| #[ip_test] |
| fn test_add_remove_ip_addresses<I: Ip + TestIpExt + crate::IpExt>() { |
| let config = I::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| |
| let ip1 = I::get_other_ip_address(1); |
| let ip2 = I::get_other_ip_address(2); |
| let ip3 = I::get_other_ip_address(3); |
| |
| let prefix = I::Addr::BYTES * 8; |
| let as1 = AddrSubnet::new(ip1.get(), prefix).unwrap(); |
| let as2 = AddrSubnet::new(ip2.get(), prefix).unwrap(); |
| |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Add ip1 (ok) |
| ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, as1).unwrap(); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Add ip2 (ok) |
| ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, as2).unwrap(); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Del ip1 (ok) |
| ctx.core_api().device_ip::<I>().del_ip_addr(&device, ip1).unwrap(); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Del ip1 again (ip1 not found) |
| assert_eq!(ctx.core_api().device_ip::<I>().del_ip_addr(&device, ip1), Err(NotFoundError)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Add ip2 again (ip2 already exists) |
| assert_eq!( |
| ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, as2), |
| Err(AddIpAddrSubnetError::Exists), |
| ); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| |
| // Add ip2 with different subnet (ip2 already exists) |
| assert_eq!( |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device, AddrSubnet::new(ip2.get(), prefix - 1).unwrap()), |
| Err(AddIpAddrSubnetError::Exists), |
| ); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip3)); |
| } |
| |
| fn receive_simple_ip_packet_test<A: IpAddress>( |
| ctx: &mut crate::testutil::FakeCtx, |
| device: &DeviceId<crate::testutil::FakeBindingsCtx>, |
| src_ip: A, |
| dst_ip: A, |
| expected: u64, |
| ) where |
| A::Version: TestIpExt, |
| { |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(<<A::Version as IpExt>::PacketBuilder as IpPacketBuilder<_>>::new( |
| src_ip, |
| dst_ip, |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .into_inner(); |
| |
| ctx.test_api().receive_ip_packet::<A::Version, _>( |
| device, |
| Some(FrameDestination::Individual { local: true }), |
| buf, |
| ); |
| assert_eq!( |
| ctx.core_ctx.ip_counters::<A::Version>().dispatch_receive_ip_packet.get(), |
| expected |
| ); |
| } |
| |
| #[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)] |
| #[ip_test] |
| fn test_multiple_ip_addresses<I: Ip + TestIpExt + crate::IpExt>() { |
| let config = I::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| |
| let ip1 = I::get_other_ip_address(1); |
| let ip2 = I::get_other_ip_address(2); |
| let from_ip = I::get_other_ip_address(3).get(); |
| |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip2)); |
| |
| // Should not receive packets on any IP. |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 0); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 0); |
| |
| // Add ip1 to device. |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device, AddrSubnet::new(ip1.get(), I::Addr::BYTES * 8).unwrap()) |
| .unwrap(); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip2)); |
| |
| // Should receive packets on ip1 but not ip2 |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 1); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 1); |
| |
| // Add ip2 to device. |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device, AddrSubnet::new(ip2.get(), I::Addr::BYTES * 8).unwrap()) |
| .unwrap(); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| |
| // Should receive packets on both ips |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 2); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 3); |
| |
| // Remove ip1 |
| ctx.core_api().device_ip::<I>().del_ip_addr(&device, ip1).unwrap(); |
| assert!(!contains_addr(&ctx.core_ctx, &device, ip1)); |
| assert!(contains_addr(&ctx.core_ctx, &device, ip2)); |
| |
| // Should receive packets on ip2 |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 3); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 4); |
| } |
| |
| /// Test that we can join and leave a multicast group, but we only truly |
| /// leave it after calling `leave_ip_multicast` the same number of times as |
| /// `join_ip_multicast`. |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)] |
| fn test_ip_join_leave_multicast_addr_ref_count<I: Ip + TestIpExt + crate::IpExt>() { |
| let config = I::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| let multicast_addr = I::get_multicast_addr(3); |
| let mut test_api = ctx.test_api(); |
| |
| // Should not be in the multicast group yet. |
| assert!(!test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Join the multicast group. |
| test_api.join_ip_multicast(&device, multicast_addr); |
| assert!(test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Leave the multicast group. |
| test_api.leave_ip_multicast(&device, multicast_addr); |
| assert!(!test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Join the multicst group. |
| test_api.join_ip_multicast(&device, multicast_addr); |
| assert!(test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Join it again... |
| test_api.join_ip_multicast(&device, multicast_addr); |
| assert!(test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Leave it (still in it because we joined twice). |
| test_api.leave_ip_multicast(&device, multicast_addr); |
| assert!(test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Leave it again... (actually left now). |
| test_api.leave_ip_multicast(&device, multicast_addr); |
| assert!(!test_api.is_in_ip_multicast(&device, multicast_addr)); |
| } |
| |
| /// Test leaving a multicast group a device has not yet joined. |
| /// |
| /// # Panics |
| /// |
| /// This method should always panic as leaving an unjoined multicast group |
| /// is a panic condition. |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)] |
| #[should_panic(expected = "attempted to leave IP multicast group we were not a member of:")] |
| fn test_ip_leave_unjoined_multicast<I: Ip + TestIpExt + crate::IpExt>() { |
| let config = I::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| let multicast_addr = I::get_multicast_addr(3); |
| |
| let mut test_api = ctx.test_api(); |
| |
| // Should not be in the multicast group yet. |
| assert!(!test_api.is_in_ip_multicast(&device, multicast_addr)); |
| |
| // Leave it (this should panic). |
| test_api.leave_ip_multicast(&device, multicast_addr); |
| } |
| |
| #[test] |
| fn test_ipv6_duplicate_solicited_node_address() { |
| // Test that we still receive packets destined to a solicited-node |
| // multicast address of an IP address we deleted because another |
| // (distinct) IP address that is still assigned uses the same |
| // solicited-node multicast address. |
| |
| let config = Ipv6::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| |
| let ip1 = SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 1, 0, 0, 0, 1])).unwrap(); |
| let ip2 = SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 2, 0, 0, 0, 1])).unwrap(); |
| let from_ip = Ipv6Addr::new([0, 0, 0, 3, 0, 0, 0, 1]); |
| |
| // ip1 and ip2 are not equal but their solicited node addresses are the |
| // same. |
| assert_ne!(ip1, ip2); |
| assert_eq!(ip1.to_solicited_node_address(), ip2.to_solicited_node_address()); |
| let sn_addr = ip1.to_solicited_node_address().get(); |
| |
| let addr_sub1 = AddrSubnet::new(ip1.get(), 64).unwrap(); |
| let addr_sub2 = AddrSubnet::new(ip2.get(), 64).unwrap(); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<Ipv6>().dispatch_receive_ip_packet.get(), 0); |
| |
| // Add ip1 to the device. |
| // |
| // Should get packets destined for the solicited node address and ip1. |
| ctx.core_api().device_ip::<Ipv6>().add_ip_addr_subnet(&device, addr_sub1).unwrap(); |
| |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 1); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 1); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, sn_addr, 2); |
| |
| // Add ip2 to the device. |
| // |
| // Should get packets destined for the solicited node address, ip1 and |
| // ip2. |
| ctx.core_api().device_ip::<Ipv6>().add_ip_addr_subnet(&device, addr_sub2).unwrap(); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 3); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 4); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, sn_addr, 5); |
| |
| // Remove ip1 from the device. |
| // |
| // Should get packets destined for the solicited node address and ip2. |
| ctx.core_api().device_ip::<Ipv6>().del_ip_addr(&device, ip1).unwrap(); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip1.get(), 5); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, ip2.get(), 6); |
| receive_simple_ip_packet_test(&mut ctx, &device, from_ip, sn_addr, 7); |
| } |
| |
| #[test] |
| fn test_add_ip_addr_subnet_link_local() { |
| // Test that `add_ip_addr_subnet` allows link-local addresses. |
| |
| let config = Ipv6::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| |
| let eth_device = |
| ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ); |
| let device = eth_device.clone().into(); |
| let eth_device = eth_device.device_state(); |
| |
| // Enable the device and configure it to generate a link-local address. |
| let _: Ipv6DeviceConfigurationUpdate = ctx |
| .core_api() |
| .device_ip::<Ipv6>() |
| .update_configuration( |
| &device, |
| Ipv6DeviceConfigurationUpdate { |
| slaac_config: Some(SlaacConfiguration { |
| enable_stable_addresses: true, |
| ..Default::default() |
| }), |
| ip_config: IpDeviceConfigurationUpdate { |
| ip_enabled: Some(true), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ) |
| .unwrap(); |
| |
| // Verify that there is a single assigned address. |
| assert_eq!( |
| eth_device |
| .ip |
| .ipv6 |
| .ip_state |
| .addrs |
| .read() |
| .iter() |
| .map(|entry| entry.addr_sub().addr().get()) |
| .collect::<Vec<UnicastAddr<_>>>(), |
| [config.local_mac.to_ipv6_link_local().addr().get()] |
| ); |
| ctx.core_api() |
| .device_ip::<Ipv6>() |
| .add_ip_addr_subnet( |
| &device, |
| AddrSubnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), 128).unwrap(), |
| ) |
| .unwrap(); |
| // Assert that the new address got added. |
| let addr_subs: Vec<_> = eth_device |
| .ip |
| .ipv6 |
| .ip_state |
| .addrs |
| .read() |
| .iter() |
| .map(|entry| entry.addr_sub().addr().into()) |
| .collect::<Vec<Ipv6Addr>>(); |
| assert_eq!( |
| addr_subs, |
| [ |
| config.local_mac.to_ipv6_link_local().addr().get(), |
| Ipv6::LINK_LOCAL_UNICAST_SUBNET.network() |
| ] |
| ); |
| } |
| } |