| // 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::{collections::HashMap, collections::VecDeque, vec::Vec}; |
| use core::fmt::Debug; |
| |
| use log::{debug, trace}; |
| use net_types::{ |
| ethernet::Mac, |
| ip::{ |
| AddrSubnet, Ip, IpAddr, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, |
| UnicastOrMulticastIpv6Addr, |
| }, |
| BroadcastAddress, MulticastAddr, MulticastAddress, SpecifiedAddr, UnicastAddr, UnicastAddress, |
| Witness, |
| }; |
| use packet::{Buf, BufferMut, EmptyBuf, Nested, Serializer}; |
| use packet_formats::{ |
| arp::{peek_arp_types, ArpHardwareType, ArpNetworkType}, |
| ethernet::{ |
| EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt, |
| }, |
| utils::NonZeroDuration, |
| }; |
| |
| use crate::{ |
| context::{FrameContext, RngContext, StateContext}, |
| data_structures::ref_counted_hash_map::{InsertResult, RefCountedHashSet, RemoveResult}, |
| device::{ |
| arp::{self, ArpContext, ArpDeviceIdContext, ArpFrameMetadata, ArpState, ArpTimerId}, |
| link::LinkDevice, |
| ndp::{self, NdpContext, NdpHandler, NdpState, NdpTimerId}, |
| BufferIpLinkDeviceContext, DeviceIdContext, EthernetDeviceId, FrameDestination, |
| IpLinkDeviceContext, IpLinkDeviceNonSyncContext, RecvIpFrameMeta, |
| }, |
| error::ExistsError, |
| ip::device::state::{AddrConfig, IpDeviceState}, |
| NonSyncContext, SyncCtx, |
| }; |
| |
| const ETHERNET_MAX_PENDING_FRAMES: usize = 10; |
| |
| impl From<Mac> for FrameDestination { |
| fn from(mac: Mac) -> FrameDestination { |
| if mac.is_broadcast() { |
| FrameDestination::Broadcast |
| } else if mac.is_multicast() { |
| FrameDestination::Multicast |
| } else { |
| debug_assert!(mac.is_unicast()); |
| FrameDestination::Unicast |
| } |
| } |
| } |
| |
| /// The non-synchronized execution context for an Ethernet device. |
| pub(crate) trait EthernetIpLinkDeviceNonSyncContext<DeviceId>: |
| RngContext + IpLinkDeviceNonSyncContext<EthernetTimerId<DeviceId>> |
| { |
| } |
| impl<DeviceId, C: RngContext + IpLinkDeviceNonSyncContext<EthernetTimerId<DeviceId>>> |
| EthernetIpLinkDeviceNonSyncContext<DeviceId> for C |
| { |
| } |
| |
| /// The execution context for an Ethernet device. |
| pub(crate) trait EthernetIpLinkDeviceContext<C: EthernetIpLinkDeviceNonSyncContext<Self::DeviceId>>: |
| IpLinkDeviceContext< |
| EthernetLinkDevice, |
| C, |
| EthernetTimerId<<Self as DeviceIdContext<EthernetLinkDevice>>::DeviceId>, |
| > |
| { |
| /// Adds an IPv6 address to the device. |
| // TODO(https://fxbug.dev/72378): Remove this method once NDP operates at |
| // L3. |
| fn add_ipv6_addr_subnet( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr_sub: AddrSubnet<Ipv6Addr>, |
| config: AddrConfig<C::Instant>, |
| ) -> Result<(), ExistsError>; |
| |
| /// Joins an IPv6 multicast group. |
| // TODO(https://fxbug.dev/72378): Remove this method once NDP operates at |
| // L3. |
| fn join_ipv6_multicast( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| multicast_addr: MulticastAddr<Ipv6Addr>, |
| ); |
| |
| /// Leaves an IPv6 multicast group. |
| // TODO(https://fxbug.dev/72378): Remove this method once NDP operates at |
| // L3. |
| fn leave_ipv6_multicast( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| multicast_addr: MulticastAddr<Ipv6Addr>, |
| ); |
| } |
| |
| impl<NonSyncCtx: NonSyncContext> EthernetIpLinkDeviceContext<NonSyncCtx> for SyncCtx<NonSyncCtx> { |
| fn add_ipv6_addr_subnet( |
| &mut self, |
| ctx: &mut NonSyncCtx, |
| device_id: EthernetDeviceId, |
| addr_sub: AddrSubnet<Ipv6Addr>, |
| config: AddrConfig<NonSyncCtx::Instant>, |
| ) -> Result<(), ExistsError> { |
| crate::ip::device::add_ipv6_addr_subnet(self, ctx, device_id.into(), addr_sub, config) |
| } |
| |
| fn join_ipv6_multicast( |
| &mut self, |
| ctx: &mut NonSyncCtx, |
| device_id: Self::DeviceId, |
| multicast_addr: MulticastAddr<Ipv6Addr>, |
| ) { |
| crate::ip::device::join_ip_multicast::<Ipv6, _, _>( |
| self, |
| ctx, |
| device_id.into(), |
| multicast_addr, |
| ) |
| } |
| |
| fn leave_ipv6_multicast( |
| &mut self, |
| ctx: &mut NonSyncCtx, |
| device_id: Self::DeviceId, |
| multicast_addr: MulticastAddr<Ipv6Addr>, |
| ) { |
| crate::ip::device::leave_ip_multicast::<Ipv6, _, _>( |
| self, |
| ctx, |
| device_id.into(), |
| multicast_addr, |
| ) |
| } |
| } |
| |
| /// A shorthand for `BufferIpLinkDeviceContext` with all of the appropriate type |
| /// arguments fixed to their Ethernet values. |
| pub(super) trait BufferEthernetIpLinkDeviceContext< |
| C: EthernetIpLinkDeviceNonSyncContext<Self::DeviceId>, |
| B: BufferMut, |
| >: |
| EthernetIpLinkDeviceContext<C> |
| + BufferIpLinkDeviceContext< |
| EthernetLinkDevice, |
| C, |
| EthernetTimerId<<Self as DeviceIdContext<EthernetLinkDevice>>::DeviceId>, |
| B, |
| > |
| { |
| } |
| |
| impl< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| B: BufferMut, |
| SC: EthernetIpLinkDeviceContext<C> |
| + BufferIpLinkDeviceContext< |
| EthernetLinkDevice, |
| C, |
| EthernetTimerId<<SC as DeviceIdContext<EthernetLinkDevice>>::DeviceId>, |
| B, |
| >, |
| > BufferEthernetIpLinkDeviceContext<C, B> for SC |
| { |
| } |
| |
| /// Builder for [`EthernetDeviceState`]. |
| pub(crate) struct EthernetDeviceStateBuilder { |
| mac: UnicastAddr<Mac>, |
| mtu: u32, |
| } |
| |
| impl EthernetDeviceStateBuilder { |
| /// Create a new `EthernetDeviceStateBuilder`. |
| pub(crate) fn new(mac: UnicastAddr<Mac>, mtu: u32) -> Self { |
| // TODO(joshlf): Add a minimum MTU for all Ethernet devices such that |
| // you cannot create an `EthernetDeviceState` with an MTU 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 IPv6 minimum MTU. The easy path is |
| // to simply use the IPv6 minimum MTU 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 MTUs for those |
| // devices. |
| // |
| // A few questions: |
| // - How do we wire error information back up the call stack? Should |
| // this just return a Result or something? |
| Self { mac, mtu } |
| } |
| |
| /// Build the `EthernetDeviceState` from this builder. |
| pub(super) fn build(self) -> EthernetDeviceState { |
| EthernetDeviceState { |
| mac: self.mac, |
| mtu: self.mtu, |
| hw_mtu: self.mtu, |
| link_multicast_groups: RefCountedHashSet::default(), |
| ipv4_arp: ArpState::default(), |
| ndp: NdpState::new(), |
| pending_frames: HashMap::new(), |
| promiscuous_mode: false, |
| } |
| } |
| } |
| |
| /// The state associated with an Ethernet device. |
| pub(crate) struct EthernetDeviceState { |
| /// Mac address of the device this state is for. |
| mac: UnicastAddr<Mac>, |
| |
| /// The value this netstack assumes as the device's current MTU. |
| mtu: u32, |
| |
| /// The maximum MTU allowed by the hardware. |
| /// |
| /// `mtu` MUST NEVER be greater than `hw_mtu`. |
| hw_mtu: u32, |
| |
| /// Link multicast groups this device has joined. |
| link_multicast_groups: RefCountedHashSet<MulticastAddr<Mac>>, |
| |
| /// IPv4 ARP state. |
| ipv4_arp: ArpState<EthernetLinkDevice, Ipv4Addr>, |
| |
| /// (IPv6) NDP state. |
| ndp: ndp::NdpState<EthernetLinkDevice>, |
| |
| // pending_frames stores a list of serialized frames indexed by their |
| // destination IP addresses. The frames contain an entire EthernetFrame |
| // body and the MTU check is performed before queueing them here. |
| pending_frames: HashMap<IpAddr, VecDeque<Buf<Vec<u8>>>>, |
| |
| /// 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, |
| } |
| |
| impl EthernetDeviceState { |
| /// Adds a pending frame `frame` associated with `local_addr` to the list of |
| /// pending frames in the current device state. |
| /// |
| /// If an older frame had to be dropped because it exceeds the maximum |
| /// allowed number of pending frames, it is returned. |
| fn add_pending_frame( |
| &mut self, |
| local_addr: IpAddr, |
| frame: Buf<Vec<u8>>, |
| ) -> Option<Buf<Vec<u8>>> { |
| let buff = self.pending_frames.entry(local_addr).or_insert_with(Default::default); |
| buff.push_back(frame); |
| if buff.len() > ETHERNET_MAX_PENDING_FRAMES { |
| buff.pop_front() |
| } else { |
| None |
| } |
| } |
| |
| /// Takes all pending frames associated with address `local_addr`. |
| fn take_pending_frames( |
| &mut self, |
| local_addr: IpAddr, |
| ) -> Option<impl Iterator<Item = Buf<Vec<u8>>>> { |
| match self.pending_frames.remove(&local_addr) { |
| Some(buff) => Some(buff.into_iter()), |
| None => None, |
| } |
| } |
| |
| /// Is a packet with a destination MAC address, `dst`, destined for this |
| /// device? |
| /// |
| /// Returns `true` if this device is has `dst_mac` as its assigned MAC |
| /// address, `dst_mac` is the broadcast MAC address, or it is one of the |
| /// multicast MAC addresses the device has joined. |
| fn should_accept(&self, dst_mac: &Mac) -> bool { |
| (self.mac.get() == *dst_mac) |
| || dst_mac.is_broadcast() |
| || (MulticastAddr::new(*dst_mac) |
| .map(|a| self.link_multicast_groups.contains(&a)) |
| .unwrap_or(false)) |
| } |
| |
| /// Should a packet with destination MAC address, `dst`, be accepted by this |
| /// device? |
| /// |
| /// Returns `true` if this device is in promiscuous mode or the frame is |
| /// destined for this device. |
| fn should_deliver(&self, dst_mac: &Mac) -> bool { |
| self.promiscuous_mode || self.should_accept(dst_mac) |
| } |
| } |
| |
| /// A timer ID for Ethernet devices. |
| /// |
| /// `D` is the type of device ID that identifies different Ethernet devices. |
| #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] |
| pub(crate) enum EthernetTimerId<D> { |
| Arp(ArpTimerId<EthernetLinkDevice, Ipv4Addr, D>), |
| Ndp(NdpTimerId<EthernetLinkDevice, D>), |
| } |
| |
| impl<D> From<ArpTimerId<EthernetLinkDevice, Ipv4Addr, D>> for EthernetTimerId<D> { |
| fn from(id: ArpTimerId<EthernetLinkDevice, Ipv4Addr, D>) -> EthernetTimerId<D> { |
| EthernetTimerId::Arp(id) |
| } |
| } |
| |
| impl<D> From<NdpTimerId<EthernetLinkDevice, D>> for EthernetTimerId<D> { |
| fn from(id: NdpTimerId<EthernetLinkDevice, D>) -> EthernetTimerId<D> { |
| EthernetTimerId::Ndp(id) |
| } |
| } |
| |
| /// Handle an Ethernet timer firing. |
| pub(super) fn handle_timer< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| id: EthernetTimerId<SC::DeviceId>, |
| ) { |
| match id { |
| EthernetTimerId::Arp(id) => arp::handle_timer(sync_ctx, ctx, id.into()), |
| EthernetTimerId::Ndp(id) => <SC as NdpHandler<_, _>>::handle_timer(sync_ctx, ctx, id), |
| } |
| } |
| |
| // If we are provided with an impl of `TimerContext<EthernetTimerId<_>>`, then |
| // we can in turn provide impls of `TimerContext` for ARP, NDP, IGMP, and MLD |
| // timers. |
| impl_timer_context!( |
| DeviceId, |
| EthernetTimerId<DeviceId>, |
| ArpTimerId<EthernetLinkDevice, Ipv4Addr, DeviceId>, |
| EthernetTimerId::Arp(id), |
| id |
| ); |
| impl_timer_context!( |
| DeviceId, |
| EthernetTimerId<DeviceId>, |
| NdpTimerId<EthernetLinkDevice, DeviceId>, |
| EthernetTimerId::Ndp(id), |
| 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< |
| B: BufferMut, |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C> |
| + FrameContext<C, B, <SC as DeviceIdContext<EthernetLinkDevice>>::DeviceId>, |
| A: IpAddress, |
| S: Serializer<Buffer = B>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| local_addr: SpecifiedAddr<A>, |
| body: S, |
| ) -> Result<(), S> { |
| ctx.increment_counter("ethernet::send_ip_frame"); |
| |
| trace!("ethernet::send_ip_frame: local_addr = {:?}; device = {:?}", local_addr, device_id); |
| |
| let state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| let (local_mac, mtu) = (state.mac, state.mtu); |
| |
| let dst_mac = match local_addr.into() { |
| IpAddr::V4(local_addr) => match MulticastAddr::from_witness(local_addr) { |
| Some(multicast) => Ok(Mac::from(&multicast)), |
| None => arp::lookup(sync_ctx, ctx, device_id, local_mac.get(), local_addr.get()) |
| .ok_or(IpAddr::V4(local_addr)), |
| }, |
| IpAddr::V6(local_addr) => match UnicastOrMulticastIpv6Addr::from_specified(local_addr) { |
| UnicastOrMulticastIpv6Addr::Multicast(addr) => Ok(Mac::from(&addr)), |
| UnicastOrMulticastIpv6Addr::Unicast(addr) => { |
| <SC as NdpHandler<_, _>>::lookup(sync_ctx, ctx, device_id, addr) |
| .ok_or(IpAddr::V6(local_addr)) |
| } |
| }, |
| }; |
| |
| match dst_mac { |
| Ok(dst_mac) => sync_ctx |
| .send_frame( |
| ctx, |
| device_id.into(), |
| body.with_mtu(mtu as usize).encapsulate(EthernetFrameBuilder::new( |
| local_mac.get(), |
| dst_mac, |
| A::Version::ETHER_TYPE, |
| )), |
| ) |
| .map_err(|ser| ser.into_inner().into_inner()), |
| Err(local_addr) => { |
| let state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| // The `serialize_vec_outer` call returns an `Either<B, |
| // Buf<Vec<u8>>`. We could naively call `.as_ref().to_vec()` on it, |
| // but if it were the `Buf<Vec<u8>>` variant, we'd be unnecessarily |
| // allocating a new `Vec` when we already have one. Instead, we |
| // leave the `Buf<Vec<u8>>` variant as it is, and only convert the |
| // `B` variant by calling `map_a`. That gives us an |
| // `Either<Buf<Vec<u8>>, Buf<Vec<u8>>`, which we call `into_inner` |
| // on to get a `Buf<Vec<u8>>`. |
| let frame = body |
| .with_mtu(mtu as usize) |
| .serialize_vec_outer() |
| .map_err(|ser| ser.1.into_inner())? |
| .map_a(|buffer| Buf::new(buffer.as_ref().to_vec(), ..)) |
| .into_inner(); |
| let dropped = state |
| .add_pending_frame(local_addr.transpose::<SpecifiedAddr<IpAddr>>().get(), frame); |
| if let Some(_dropped) = dropped { |
| // TODO(brunodalbo): Is it ok to silently just let this drop? Or |
| // should the IP layer be notified in any way? |
| log_unimplemented!((), "Ethernet dropped frame because ran out of allowable space"); |
| } |
| Ok(()) |
| } |
| } |
| } |
| |
| /// Receive an Ethernet frame from the network. |
| pub(super) fn receive_frame< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| B: BufferMut, |
| SC: BufferEthernetIpLinkDeviceContext<C, B>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| mut buffer: B, |
| ) { |
| trace!("ethernet::receive_frame: device_id = {:?}", device_id); |
| // 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 frame = if let Ok(frame) = |
| buffer.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck) |
| { |
| frame |
| } else { |
| trace!("ethernet::receive_frame: failed to parse ethernet frame"); |
| // TODO(joshlf): Do something else? |
| return; |
| }; |
| |
| let (_, dst) = (frame.src_mac(), frame.dst_mac()); |
| |
| if !sync_ctx.get_state_with(device_id).link.should_deliver(&dst) { |
| trace!("ethernet::receive_frame: destination mac {:?} not for device {:?}", dst, device_id); |
| return; |
| } |
| |
| let frame_dst = FrameDestination::from(dst); |
| |
| match frame.ethertype() { |
| Some(EtherType::Arp) => { |
| let types = if let Ok(types) = peek_arp_types(buffer.as_ref()) { |
| types |
| } else { |
| // TODO(joshlf): Do something else here? |
| return; |
| }; |
| match types { |
| (ArpHardwareType::Ethernet, ArpNetworkType::Ipv4) => { |
| arp::handle_packet(sync_ctx, ctx, device_id, buffer) |
| } |
| } |
| } |
| Some(EtherType::Ipv4) => sync_ctx.receive_frame( |
| ctx, |
| RecvIpFrameMeta::<_, Ipv4>::new(device_id, frame_dst), |
| buffer, |
| ), |
| Some(EtherType::Ipv6) => sync_ctx.receive_frame( |
| ctx, |
| RecvIpFrameMeta::<_, Ipv6>::new(device_id, frame_dst), |
| buffer, |
| ), |
| Some(EtherType::Other(_)) | None => {} // TODO(joshlf) |
| } |
| } |
| |
| /// Set the promiscuous mode flag on `device_id`. |
| pub(super) fn set_promiscuous_mode< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| _ctx: &mut C, |
| device_id: SC::DeviceId, |
| enabled: bool, |
| ) { |
| sync_ctx.get_state_mut_with(device_id).link.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< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| _ctx: &mut C, |
| device_id: SC::DeviceId, |
| multicast_addr: MulticastAddr<Mac>, |
| ) { |
| let device_state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| |
| let groups = &mut device_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< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| _ctx: &mut C, |
| device_id: SC::DeviceId, |
| multicast_addr: MulticastAddr<Mac>, |
| ) { |
| let device_state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| |
| let groups = &mut device_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< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &SC, |
| device_id: SC::DeviceId, |
| ) -> u32 { |
| sync_ctx.get_state_with(device_id).link.mtu |
| } |
| |
| /// Insert a static entry into this device's ARP table. |
| /// |
| /// This will cause any conflicting dynamic entry to be removed, and |
| /// any future conflicting gratuitous ARPs to be ignored. |
| // TODO(rheacock): remove `cfg(test)` when this is used. Will probably be called |
| // by a pub fn in the device mod. |
| #[cfg(test)] |
| pub(super) fn insert_static_arp_table_entry< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| addr: Ipv4Addr, |
| mac: Mac, |
| ) { |
| arp::insert_static_neighbor(sync_ctx, ctx, device_id, addr, mac) |
| } |
| |
| /// Insert an entry into this device's NDP table. |
| /// |
| /// This method only gets called when testing to force set a neighbor's link |
| /// address so that lookups succeed immediately, without doing address |
| /// resolution. |
| // TODO(rheacock): Remove when this is called from non-test code. |
| #[cfg(test)] |
| pub(super) fn insert_ndp_table_entry< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| addr: UnicastAddr<Ipv6Addr>, |
| mac: Mac, |
| ) { |
| <SC as NdpHandler<_, _>>::insert_static_neighbor(sync_ctx, ctx, device_id, addr, mac) |
| } |
| |
| /// Deinitializes and cleans up state for ethernet devices |
| /// |
| /// After this function is called, the ethernet device should not be used and |
| /// nothing else should be done with the state. |
| pub(super) fn deinitialize< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| ) { |
| arp::deinitialize(sync_ctx, ctx, device_id); |
| <SC as NdpHandler<_, _>>::deinitialize(sync_ctx, ctx, device_id); |
| } |
| |
| impl<C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, SC: EthernetIpLinkDeviceContext<C>> |
| StateContext<C, ArpState<EthernetLinkDevice, Ipv4Addr>, SC::DeviceId> for SC |
| { |
| fn get_state_with(&self, id: SC::DeviceId) -> &ArpState<EthernetLinkDevice, Ipv4Addr> { |
| &self.get_state_with(id).link.ipv4_arp |
| } |
| |
| fn get_state_mut_with( |
| &mut self, |
| id: SC::DeviceId, |
| ) -> &mut ArpState<EthernetLinkDevice, Ipv4Addr> { |
| &mut self.get_state_mut_with(id).link.ipv4_arp |
| } |
| } |
| |
| impl< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| B: BufferMut, |
| SC: EthernetIpLinkDeviceContext<C> |
| + FrameContext<C, B, <SC as DeviceIdContext<EthernetLinkDevice>>::DeviceId>, |
| > FrameContext<C, B, ArpFrameMetadata<EthernetLinkDevice, SC::DeviceId>> for SC |
| { |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut C, |
| meta: ArpFrameMetadata<EthernetLinkDevice, SC::DeviceId>, |
| body: S, |
| ) -> Result<(), S> { |
| let src = self.get_state_with(meta.device_id).link.mac; |
| self.send_frame( |
| ctx, |
| meta.device_id, |
| body.encapsulate(EthernetFrameBuilder::new(src.get(), meta.dst_addr, EtherType::Arp)), |
| ) |
| .map_err(Nested::into_inner) |
| } |
| } |
| |
| impl<C: DeviceIdContext<EthernetLinkDevice>> ArpDeviceIdContext<EthernetLinkDevice> for C { |
| type DeviceId = <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId; |
| } |
| |
| impl<C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, SC: EthernetIpLinkDeviceContext<C>> |
| ArpContext<EthernetLinkDevice, Ipv4Addr, C> for SC |
| { |
| fn get_protocol_addr( |
| &self, |
| _ctx: &mut C, |
| device_id: <SC as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId, |
| ) -> Option<Ipv4Addr> { |
| self.get_state_with(device_id.into()) |
| .ip |
| .ipv4 |
| .ip_state |
| .iter_addrs() |
| .next() |
| .cloned() |
| .map(|addr| addr.addr().get()) |
| } |
| fn get_hardware_addr( |
| &self, |
| _ctx: &mut C, |
| device_id: <SC as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId, |
| ) -> UnicastAddr<Mac> { |
| self.get_state_with(device_id.into()).link.mac |
| } |
| fn address_resolved( |
| &mut self, |
| ctx: &mut C, |
| device_id: <SC as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId, |
| proto_addr: Ipv4Addr, |
| hw_addr: Mac, |
| ) { |
| mac_resolved(self, ctx, device_id.into(), IpAddr::V4(proto_addr), hw_addr); |
| } |
| fn address_resolution_failed( |
| &mut self, |
| ctx: &mut C, |
| device_id: <SC as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId, |
| proto_addr: Ipv4Addr, |
| ) { |
| mac_resolution_failed(self, ctx, device_id.into(), IpAddr::V4(proto_addr)); |
| } |
| fn address_resolution_expired( |
| &mut self, |
| _ctx: &mut C, |
| _device_id: <SC as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId, |
| _proto_addr: Ipv4Addr, |
| ) { |
| log_unimplemented!((), "ArpContext::address_resolution_expired"); |
| } |
| } |
| |
| impl<C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, SC: EthernetIpLinkDeviceContext<C>> |
| StateContext<C, NdpState<EthernetLinkDevice>, SC::DeviceId> for SC |
| { |
| fn get_state_with(&self, id: SC::DeviceId) -> &NdpState<EthernetLinkDevice> { |
| &self.get_state_with(id).link.ndp |
| } |
| |
| fn get_state_mut_with(&mut self, id: SC::DeviceId) -> &mut NdpState<EthernetLinkDevice> { |
| &mut self.get_state_mut_with(id).link.ndp |
| } |
| } |
| |
| pub(super) fn get_mac< |
| 'a, |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &'a SC, |
| device_id: SC::DeviceId, |
| ) -> &'a UnicastAddr<Mac> { |
| &sync_ctx.get_state_with(device_id).link.mac |
| } |
| |
| impl<C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, SC: EthernetIpLinkDeviceContext<C>> |
| NdpContext<EthernetLinkDevice, C> for SC |
| { |
| fn get_retrans_timer(&self, device_id: Self::DeviceId) -> NonZeroDuration { |
| self.get_state_with(device_id).ip.ipv6.retrans_timer |
| } |
| |
| fn get_link_layer_addr(&self, device_id: SC::DeviceId) -> UnicastAddr<Mac> { |
| get_mac(self, device_id).clone() |
| } |
| |
| fn get_ip_device_state(&self, device_id: Self::DeviceId) -> &IpDeviceState<C::Instant, Ipv6> { |
| &self.get_state_with(device_id).ip.ipv6.ip_state |
| } |
| |
| fn get_ip_device_state_mut( |
| &mut self, |
| device_id: Self::DeviceId, |
| ) -> &mut IpDeviceState<C::Instant, Ipv6> { |
| &mut self.get_state_mut_with(device_id).ip.ipv6.ip_state |
| } |
| |
| fn send_ipv6_frame<S: Serializer<Buffer = EmptyBuf>>( |
| &mut self, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| next_hop: SpecifiedAddr<Ipv6Addr>, |
| body: S, |
| ) -> Result<(), S> { |
| // TODO(joshlf): Wire `SpecifiedAddr` through the `ndp` module. |
| send_ip_frame(self, ctx, device_id, next_hop, body) |
| } |
| |
| fn address_resolved( |
| &mut self, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| address: &UnicastAddr<Ipv6Addr>, |
| link_address: Mac, |
| ) { |
| mac_resolved(self, ctx, device_id, IpAddr::V6(address.get()), link_address); |
| } |
| |
| fn address_resolution_failed( |
| &mut self, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| address: &UnicastAddr<Ipv6Addr>, |
| ) { |
| mac_resolution_failed(self, ctx, device_id, IpAddr::V6(address.get())); |
| } |
| |
| fn set_mtu(&mut self, _ctx: &mut C, device_id: SC::DeviceId, mut mtu: u32) { |
| // TODO(ghanan): Should this new MTU be updated only from the netstack's |
| // perspective or be exposed to the device hardware? |
| |
| // `mtu` must not be less than the minimum IPv6 MTU. |
| assert!(mtu >= Ipv6::MINIMUM_LINK_MTU.into()); |
| |
| let dev_state = &mut self.get_state_mut_with(device_id).link; |
| |
| // If `mtu` is greater than what the device supports, set `mtu` to the |
| // maximum MTU the device supports. |
| if mtu > dev_state.hw_mtu { |
| 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, dev_state.hw_mtu); |
| mtu = dev_state.hw_mtu; |
| } |
| |
| trace!("ethernet::ndp_device::set_mtu: setting link MTU to {:?}", mtu); |
| dev_state.mtu = mtu; |
| } |
| } |
| |
| /// An implementation of the [`LinkDevice`] trait for Ethernet devices. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub(crate) struct EthernetLinkDevice; |
| |
| impl LinkDevice for EthernetLinkDevice { |
| type Address = Mac; |
| type State = EthernetDeviceState; |
| } |
| |
| /// Sends out any pending frames that are waiting for link layer address |
| /// resolution. |
| /// |
| /// `mac_resolved` is the common logic used when a link layer address is |
| /// resolved either by ARP or NDP. |
| fn mac_resolved< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| address: IpAddr, |
| dst_mac: Mac, |
| ) { |
| let state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| let src_mac = state.mac; |
| let ether_type = match &address { |
| IpAddr::V4(_) => EtherType::Ipv4, |
| IpAddr::V6(_) => EtherType::Ipv6, |
| }; |
| if let Some(pending) = state.take_pending_frames(address) { |
| for frame in pending { |
| // NOTE(brunodalbo): We already performed MTU checking when we saved |
| // the buffer waiting for address resolution. It should be noted |
| // that the MTU check back then didn't account for ethernet frame |
| // padding required by EthernetFrameBuilder, but that's fine (as it |
| // stands right now) because the MTU is guaranteed to be larger |
| // than an Ethernet minimum frame body size. |
| let res = sync_ctx.send_frame( |
| ctx, |
| device_id.into(), |
| frame.encapsulate(EthernetFrameBuilder::new(src_mac.get(), dst_mac, ether_type)), |
| ); |
| if let Err(_) = res { |
| // TODO(joshlf): Do we want to handle this differently? |
| debug!("Failed to send pending frame; MTU changed since frame was queued"); |
| } |
| } |
| } |
| } |
| |
| /// Clears out any pending frames that are waiting for link layer address |
| /// resolution. |
| /// |
| /// `mac_resolution_failed` is the common logic used when a link layer address |
| /// fails to resolve either by ARP or NDP. |
| fn mac_resolution_failed< |
| C: EthernetIpLinkDeviceNonSyncContext<SC::DeviceId>, |
| SC: EthernetIpLinkDeviceContext<C>, |
| >( |
| sync_ctx: &mut SC, |
| _ctx: &mut C, |
| device_id: SC::DeviceId, |
| address: IpAddr, |
| ) { |
| // TODO(brunodalbo) what do we do here in regards to the pending frames? |
| // NDP's RFC explicitly states unreachable ICMP messages must be generated: |
| // "If no Neighbor Advertisement is received after MAX_MULTICAST_SOLICIT |
| // solicitations, address resolution has failed. The sender MUST return |
| // ICMP destination unreachable indications with code 3 |
| // (Address Unreachable) for each packet queued awaiting address |
| // resolution." |
| // For ARP, we don't have such a clear statement on the RFC, it would make |
| // sense to do the same thing though. |
| let state = &mut sync_ctx.get_state_mut_with(device_id).link; |
| if let Some(_) = state.take_pending_frames(address) { |
| log_unimplemented!((), "ethernet mac resolution failed not implemented"); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use alloc::vec; |
| |
| use assert_matches::assert_matches; |
| use net_types::ip::IpVersion; |
| use packet::Buf; |
| use packet_formats::{ |
| icmp::{IcmpDestUnreachable, IcmpIpExt}, |
| ip::{IpExt, IpPacketBuilder, IpProto}, |
| testdata::{dns_request_v4, dns_request_v6}, |
| testutil::{ |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame, parse_ip_packet_in_ethernet_frame, |
| }, |
| }; |
| use rand::Rng; |
| use specialize_ip_macro::ip_test; |
| |
| use super::*; |
| use crate::{ |
| context::testutil::DummyInstant, |
| device::{ |
| arp::ArpHandler, testutil::DeviceTestIpExt, DeviceId, DeviceIdInner, EthernetDeviceId, |
| IpLinkDeviceState, |
| }, |
| error::NotFoundError, |
| ip::{ |
| device::{ |
| is_ipv4_routing_enabled, is_ipv6_routing_enabled, set_routing_enabled, |
| state::AssignedAddress, |
| }, |
| dispatch_receive_ip_packet_name, receive_ip_packet, DummyDeviceId, |
| }, |
| testutil::{ |
| add_arp_or_ndp_table_entry, assert_empty, get_counter_val, new_rng, |
| DummyEventDispatcherBuilder, TestIpExt, DUMMY_CONFIG_V4, |
| }, |
| Ctx, |
| }; |
| |
| struct DummyEthernetCtx { |
| state: IpLinkDeviceState<DummyInstant, EthernetDeviceState>, |
| } |
| |
| impl DummyEthernetCtx { |
| fn new(mac: UnicastAddr<Mac>, mtu: u32) -> DummyEthernetCtx { |
| DummyEthernetCtx { |
| state: IpLinkDeviceState::new(EthernetDeviceStateBuilder::new(mac, mtu).build()), |
| } |
| } |
| } |
| |
| type DummyNonSyncCtx = |
| crate::context::testutil::DummyNonSyncCtx<EthernetTimerId<DummyDeviceId>, (), ()>; |
| |
| type DummyCtx = |
| crate::context::testutil::DummySyncCtx<DummyEthernetCtx, DummyDeviceId, DummyDeviceId>; |
| |
| impl |
| StateContext< |
| DummyNonSyncCtx, |
| IpLinkDeviceState<DummyInstant, EthernetDeviceState>, |
| DummyDeviceId, |
| > for DummyCtx |
| { |
| fn get_state_with( |
| &self, |
| _id0: DummyDeviceId, |
| ) -> &IpLinkDeviceState<DummyInstant, EthernetDeviceState> { |
| &self.get_ref().state |
| } |
| |
| fn get_state_mut_with( |
| &mut self, |
| _id0: DummyDeviceId, |
| ) -> &mut IpLinkDeviceState<DummyInstant, EthernetDeviceState> { |
| &mut self.get_mut().state |
| } |
| } |
| |
| impl EthernetIpLinkDeviceContext<DummyNonSyncCtx> for DummyCtx { |
| fn add_ipv6_addr_subnet( |
| &mut self, |
| _ctx: &mut DummyNonSyncCtx, |
| _device_id: DummyDeviceId, |
| _addr_sub: AddrSubnet<Ipv6Addr>, |
| _config: AddrConfig<DummyInstant>, |
| ) -> Result<(), ExistsError> { |
| unimplemented!() |
| } |
| |
| fn join_ipv6_multicast( |
| &mut self, |
| _ctx: &mut DummyNonSyncCtx, |
| _device_id: DummyDeviceId, |
| _multicast_addr: MulticastAddr<Ipv6Addr>, |
| ) { |
| unimplemented!() |
| } |
| |
| fn leave_ipv6_multicast( |
| &mut self, |
| _ctx: &mut DummyNonSyncCtx, |
| _device_id: DummyDeviceId, |
| _multicast_addr: MulticastAddr<Ipv6Addr>, |
| ) { |
| unimplemented!() |
| } |
| } |
| |
| impl DeviceIdContext<EthernetLinkDevice> for DummyCtx { |
| type DeviceId = DummyDeviceId; |
| } |
| |
| impl IpLinkDeviceContext<EthernetLinkDevice, DummyNonSyncCtx, EthernetTimerId<DummyDeviceId>> |
| for DummyCtx |
| { |
| } |
| |
| fn contains_addr<A: IpAddress>( |
| ctx: &crate::testutil::DummySyncCtx, |
| device: DeviceId, |
| addr: SpecifiedAddr<A>, |
| ) -> bool |
| where |
| A::Version: DeviceTestIpExt<DummyInstant>, |
| { |
| <A::Version as DeviceTestIpExt<_>>::get_ip_device_state(ctx, device) |
| .iter_addrs() |
| .any(|a| a.addr() == addr) |
| } |
| |
| fn is_in_ip_multicast<A: IpAddress>( |
| ctx: &crate::testutil::DummySyncCtx, |
| device: DeviceId, |
| addr: MulticastAddr<A>, |
| ) -> bool |
| where |
| A::Version: DeviceTestIpExt<DummyInstant>, |
| { |
| <A::Version as DeviceTestIpExt<_>>::get_ip_device_state(ctx, device) |
| .multicast_groups |
| .contains(&addr) |
| } |
| |
| #[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: usize) { |
| let crate::context::testutil::DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| crate::context::testutil::DummyCtx::with_sync_ctx(DummyCtx::with_state( |
| DummyEthernetCtx::new(DUMMY_CONFIG_V4.local_mac, Ipv6::MINIMUM_LINK_MTU.into()), |
| )); |
| <DummyCtx as ArpHandler<_, _, _>>::insert_static_neighbor( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| DUMMY_CONFIG_V4.remote_mac.get(), |
| ); |
| let _ = send_ip_frame( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DummyDeviceId, |
| DUMMY_CONFIG_V4.remote_ip, |
| Buf::new(&mut vec![0; size], ..), |
| ); |
| assert_eq!(sync_ctx.frames().len(), expect_frames_sent); |
| } |
| |
| test(Ipv6::MINIMUM_LINK_MTU.into(), 1); |
| test(usize::from(Ipv6::MINIMUM_LINK_MTU) + 1, 0); |
| } |
| |
| #[test] |
| fn test_pending_frames() { |
| let mut state = EthernetDeviceStateBuilder::new( |
| DUMMY_CONFIG_V4.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ) |
| .build(); |
| let ip = IpAddr::V4(DUMMY_CONFIG_V4.local_ip.get()); |
| assert_matches!(state.add_pending_frame(ip, Buf::new(vec![1], ..)), None); |
| assert_matches!(state.add_pending_frame(ip, Buf::new(vec![2], ..)), None); |
| assert_matches!(state.add_pending_frame(ip, Buf::new(vec![3], ..)), None); |
| |
| // Check that we're accumulating correctly... |
| assert_eq!(3, state.take_pending_frames(ip).unwrap().count()); |
| // ...and that take_pending_frames clears all the buffered data. |
| assert!(state.take_pending_frames(ip).is_none()); |
| |
| for i in 0..ETHERNET_MAX_PENDING_FRAMES { |
| assert!(state.add_pending_frame(ip, Buf::new(vec![i as u8], ..)).is_none()); |
| } |
| // Check that adding more than capacity will drop the older buffers as |
| // a proper FIFO queue. |
| assert_eq!(0, state.add_pending_frame(ip, Buf::new(vec![255], ..)).unwrap().as_ref()[0]); |
| assert_eq!(1, state.add_pending_frame(ip, Buf::new(vec![255], ..)).unwrap().as_ref()[0]); |
| assert_eq!(2, state.add_pending_frame(ip, Buf::new(vec![255], ..)).unwrap().as_ref()[0]); |
| } |
| |
| fn test_receive_ip_frame<I: Ip + TestIpExt>(enable: bool) { |
| // Should only receive a frame if the device is enabled. |
| |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.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 sync_ctx, &mut non_sync_ctx, device); |
| 1 |
| } else { |
| 0 |
| }; |
| |
| crate::device::receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, Buf::new(bytes, ..)) |
| .expect("error receiving frame"); |
| |
| let counter = match I::VERSION { |
| IpVersion::V4 => "receive_ipv4_packet", |
| IpVersion::V6 => "receive_ipv6_packet", |
| }; |
| assert_eq!(get_counter_val(&non_sync_ctx, counter), expected_received); |
| } |
| |
| // TODO(https://fxbug.dev/102105): Unify these when #[ip_test] works with |
| // #[test_case]. |
| #[ip_test] |
| fn receive_frame_disabled<I: Ip + TestIpExt>() { |
| test_receive_ip_frame::<I>(false); |
| } |
| |
| #[ip_test] |
| fn receive_frame_enabled<I: Ip + TestIpExt>() { |
| test_receive_ip_frame::<I>(true); |
| } |
| |
| fn test_send_ip_frame<I: Ip + TestIpExt>(enable: bool) { |
| // Should only send a frame if the device is enabled. |
| |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = crate::testutil::DummyCtx::default(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| |
| let expected_sent = if enable { |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device); |
| 1 |
| } else { |
| 0 |
| }; |
| |
| match I::VERSION { |
| IpVersion::V4 => { |
| let addr = SpecifiedAddr::new(dns_request_v4::IPV4_PACKET.metadata.dst_ip).unwrap(); |
| crate::device::insert_static_arp_table_entry( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| addr.get(), |
| config.remote_mac, |
| ) |
| .expect("insert static ARP entry"); |
| |
| crate::ip::device::send_ip_frame::<Ipv4, _, _, _, _>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| addr, |
| Buf::new(dns_request_v4::IPV4_PACKET.bytes.to_vec(), ..), |
| ) |
| .expect("error sending IPv4 frame") |
| } |
| |
| IpVersion::V6 => { |
| let addr = UnicastAddr::new(dns_request_v6::IPV6_PACKET.metadata.dst_ip).unwrap(); |
| crate::device::insert_ndp_table_entry( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| addr, |
| config.remote_mac.get(), |
| ) |
| .expect("insert static NDP entry"); |
| crate::ip::device::send_ip_frame::<Ipv6, _, _, _, _>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| addr.into_specified(), |
| Buf::new(dns_request_v6::IPV6_PACKET.bytes.to_vec(), ..), |
| ) |
| .expect("error sending IPv6 frame") |
| } |
| } |
| |
| assert_eq!(get_counter_val(&non_sync_ctx, "ethernet::send_ip_frame"), expected_sent); |
| } |
| |
| // TODO(https://fxbug.dev/102105): Unify these when #[ip_test] works with |
| // #[test_case]. |
| #[ip_test] |
| fn test_send_frame_disabled<I: Ip + TestIpExt>() { |
| test_send_ip_frame::<I>(false); |
| } |
| |
| #[ip_test] |
| fn test_send_frame_enabled<I: Ip + TestIpExt>() { |
| test_send_ip_frame::<I>(true); |
| } |
| |
| #[test] |
| fn initialize_once() { |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| DUMMY_CONFIG_V4.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device); |
| } |
| |
| fn is_routing_enabled<I: Ip>( |
| sync_ctx: &crate::testutil::DummySyncCtx, |
| device: DeviceId, |
| ) -> bool { |
| match I::VERSION { |
| IpVersion::V4 => is_ipv4_routing_enabled(sync_ctx, device), |
| IpVersion::V6 => is_ipv6_routing_enabled(sync_ctx, device), |
| } |
| } |
| |
| #[ip_test] |
| fn test_set_ip_routing<I: Ip + TestIpExt + IcmpIpExt + IpExt>() { |
| fn check_other_is_routing_enabled<I: Ip>( |
| sync_ctx: &crate::testutil::DummySyncCtx, |
| device: DeviceId, |
| expected: bool, |
| ) { |
| let enabled = match I::VERSION { |
| IpVersion::V4 => is_routing_enabled::<Ipv6>(sync_ctx, device), |
| IpVersion::V6 => is_routing_enabled::<Ipv4>(sync_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, |_| {}) |
| .unwrap(); |
| } |
| IpVersion::V6 => { |
| let _ = parse_icmp_packet_in_ip_packet_in_ethernet_frame::< |
| Ipv6, |
| _, |
| IcmpDestUnreachable, |
| _, |
| >(buf, |_| {}) |
| .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::DUMMY_CONFIG; |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| 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 = DummyEventDispatcherBuilder::from_config(config.clone()); |
| let device_builder_id = 0; |
| add_arp_or_ndp_table_entry(&mut builder, device_builder_id, src_ip.get(), src_mac); |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = builder.build(); |
| |
| // Should not be a router (default). |
| assert!(!is_routing_enabled::<I>(&sync_ctx, device)); |
| check_other_is_routing_enabled::<I>(&sync_ctx, device, false); |
| |
| // Receiving a packet not destined for the node should only result in a |
| // dest unreachable message if routing is enabled. |
| receive_ip_packet::<_, _, I>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| frame_dst, |
| buf.clone(), |
| ); |
| assert_empty(non_sync_ctx.frames_sent().iter()); |
| |
| // Set routing and expect packets to be forwarded. |
| set_routing_enabled::<_, _, I>(&mut sync_ctx, &mut non_sync_ctx, device, true) |
| .expect("error setting routing enabled"); |
| assert!(is_routing_enabled::<I>(&sync_ctx, device)); |
| // Should not update other Ip routing status. |
| check_other_is_routing_enabled::<I>(&sync_ctx, device, false); |
| |
| // Should route the packet since routing fully enabled (netstack & |
| // device). |
| receive_ip_packet::<_, _, I>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| frame_dst, |
| buf.clone(), |
| ); |
| assert_eq!(non_sync_ctx.frames_sent().len(), 1); |
| let (packet_buf, _, _, packet_src_ip, packet_dst_ip, proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<I>(&non_sync_ctx.frames_sent()[0].1[..]).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(); |
| receive_ip_packet::<_, _, I>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| frame_dst, |
| buf_unknown_dest, |
| ); |
| assert_eq!(non_sync_ctx.frames_sent().len(), 2); |
| check_icmp::<I>(&non_sync_ctx.frames_sent()[1].1); |
| |
| // Attempt to unset router |
| set_routing_enabled::<_, _, I>(&mut sync_ctx, &mut non_sync_ctx, device, false) |
| .expect("error setting routing enabled"); |
| assert!(!is_routing_enabled::<I>(&sync_ctx, device)); |
| check_other_is_routing_enabled::<I>(&sync_ctx, device, false); |
| |
| // Should not route packets anymore |
| receive_ip_packet::<_, _, I>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| frame_dst, |
| buf.clone(), |
| ); |
| assert_eq!(non_sync_ctx.frames_sent().len(), 2); |
| } |
| |
| #[ip_test] |
| fn test_promiscuous_mode<I: Ip + TestIpExt + IpExt>() { |
| // 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::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = |
| DummyEventDispatcherBuilder::from_config(config.clone()).build(); |
| let device = DeviceId::new_ethernet(0); |
| let other_mac = Mac::new([13, 14, 15, 16, 17, 18]); |
| |
| 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, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // Accept packet destined for this device if promiscuous mode is off. |
| crate::device::set_promiscuous_mode(&mut sync_ctx, &mut non_sync_ctx, device, false) |
| .expect("error setting promiscuous mode"); |
| crate::device::receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone()) |
| .expect("error receiving frame"); |
| assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| |
| // Accept packet destined for this device if promiscuous mode is on. |
| crate::device::set_promiscuous_mode(&mut sync_ctx, &mut non_sync_ctx, device, true) |
| .expect("error setting promiscuous mode"); |
| crate::device::receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone()) |
| .expect("error receiving frame"); |
| assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 2); |
| |
| 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, |
| I::ETHER_TYPE, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // Reject packet not destined for this device if promiscuous mode is |
| // off. |
| crate::device::set_promiscuous_mode(&mut sync_ctx, &mut non_sync_ctx, device, false) |
| .expect("error setting promiscuous mode"); |
| crate::device::receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone()) |
| .expect("error receiving frame"); |
| assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 2); |
| |
| // Accept packet not destined for this device if promiscuous mode is on. |
| crate::device::set_promiscuous_mode(&mut sync_ctx, &mut non_sync_ctx, device, true) |
| .expect("error setting promiscuous mode"); |
| crate::device::receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone()) |
| .expect("error receiving frame"); |
| assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 3); |
| } |
| |
| #[ip_test] |
| fn test_add_remove_ip_addresses<I: Ip + TestIpExt + DeviceTestIpExt<DummyInstant>>() { |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_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(&sync_ctx, device, ip1)); |
| assert!(!contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Add ip1 (ok) |
| crate::device::add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, as1).unwrap(); |
| assert!(contains_addr(&sync_ctx, device, ip1)); |
| assert!(!contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Add ip2 (ok) |
| crate::device::add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, as2).unwrap(); |
| assert!(contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Del ip1 (ok) |
| crate::device::del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, &ip1).unwrap(); |
| assert!(!contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Del ip1 again (ip1 not found) |
| assert_eq!( |
| crate::device::del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, &ip1), |
| Err(NotFoundError) |
| ); |
| assert!(!contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Add ip2 again (ip2 already exists) |
| assert_eq!( |
| crate::device::add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, as2) |
| .unwrap_err(), |
| ExistsError, |
| ); |
| assert!(!contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| |
| // Add ip2 with different subnet (ip2 already exists) |
| assert_eq!( |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| AddrSubnet::new(ip2.get(), prefix - 1).unwrap() |
| ) |
| .unwrap_err(), |
| ExistsError, |
| ); |
| assert!(!contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| assert!(!contains_addr(&sync_ctx, device, ip3)); |
| } |
| |
| fn receive_simple_ip_packet_test<A: IpAddress>( |
| sync_ctx: &mut crate::testutil::DummySyncCtx, |
| non_sync_ctx: &mut crate::testutil::DummyNonSyncCtx, |
| device: DeviceId, |
| src_ip: A, |
| dst_ip: A, |
| expected: usize, |
| ) { |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(<A::Version as IpExt>::PacketBuilder::new( |
| src_ip, |
| dst_ip, |
| 64, |
| IpProto::Tcp.into(), |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .into_inner(); |
| |
| receive_ip_packet::<_, _, A::Version>( |
| sync_ctx, |
| non_sync_ctx, |
| device, |
| FrameDestination::Unicast, |
| buf, |
| ); |
| assert_eq!( |
| get_counter_val(non_sync_ctx, dispatch_receive_ip_packet_name::<A::Version>()), |
| expected |
| ); |
| } |
| |
| #[ip_test] |
| fn test_multiple_ip_addresses<I: Ip + TestIpExt + DeviceTestIpExt<DummyInstant>>() { |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_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(&sync_ctx, device, ip1)); |
| assert!(!contains_addr(&sync_ctx, device, ip2)); |
| |
| // Should not receive packets on any IP. |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 0, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 0, |
| ); |
| |
| // Add ip1 to device. |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| AddrSubnet::new(ip1.get(), I::Addr::BYTES * 8).unwrap(), |
| ) |
| .unwrap(); |
| assert!(contains_addr(&sync_ctx, device, ip1)); |
| assert!(!contains_addr(&sync_ctx, device, ip2)); |
| |
| // Should receive packets on ip1 but not ip2 |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 1, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 1, |
| ); |
| |
| // Add ip2 to device. |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| AddrSubnet::new(ip2.get(), I::Addr::BYTES * 8).unwrap(), |
| ) |
| .unwrap(); |
| assert!(contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| |
| // Should receive packets on both ips |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 2, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 3, |
| ); |
| |
| // Remove ip1 |
| crate::device::del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, &ip1).unwrap(); |
| assert!(!contains_addr(&sync_ctx, device, ip1)); |
| assert!(contains_addr(&sync_ctx, device, ip2)); |
| |
| // Should receive packets on ip2 |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 3, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 4, |
| ); |
| } |
| |
| fn join_ip_multicast<A: IpAddress, NonSyncCtx: NonSyncContext>( |
| sync_ctx: &mut SyncCtx<NonSyncCtx>, |
| ctx: &mut NonSyncCtx, |
| device: DeviceId, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| match multicast_addr.into() { |
| IpAddr::V4(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv4, _, _>( |
| sync_ctx, |
| ctx, |
| device, |
| multicast_addr, |
| ), |
| IpAddr::V6(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv6, _, _>( |
| sync_ctx, |
| ctx, |
| device, |
| multicast_addr, |
| ), |
| } |
| } |
| |
| fn leave_ip_multicast<A: IpAddress, NonSyncCtx: NonSyncContext>( |
| sync_ctx: &mut SyncCtx<NonSyncCtx>, |
| ctx: &mut NonSyncCtx, |
| device: DeviceId, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| match multicast_addr.into() { |
| IpAddr::V4(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv4, _, _>( |
| sync_ctx, |
| ctx, |
| device, |
| multicast_addr, |
| ), |
| IpAddr::V6(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv6, _, _>( |
| sync_ctx, |
| ctx, |
| device, |
| multicast_addr, |
| ), |
| } |
| } |
| |
| /// 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] |
| fn test_ip_join_leave_multicast_addr_ref_count< |
| I: Ip + TestIpExt + DeviceTestIpExt<DummyInstant>, |
| >() { |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device); |
| |
| let multicast_addr = I::get_multicast_addr(3); |
| |
| // Should not be in the multicast group yet. |
| assert!(!is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Join the multicast group. |
| join_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Leave the multicast group. |
| leave_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(!is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Join the multicst group. |
| join_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Join it again... |
| join_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Leave it (still in it because we joined twice). |
| leave_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Leave it again... (actually left now). |
| leave_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, device, multicast_addr); |
| assert!(!is_in_ip_multicast(&sync_ctx, 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] |
| #[should_panic(expected = "attempted to leave IP multicast group we were not a member of:")] |
| fn test_ip_leave_unjoined_multicast<I: Ip + TestIpExt + DeviceTestIpExt<DummyInstant>>() { |
| let config = I::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device); |
| |
| let multicast_addr = I::get_multicast_addr(3); |
| |
| // Should not be in the multicast group yet. |
| assert!(!is_in_ip_multicast(&sync_ctx, device, multicast_addr)); |
| |
| // Leave it (this should panic). |
| leave_ip_multicast(&mut sync_ctx, &mut non_sync_ctx, 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::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into(), |
| ); |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_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!(get_counter_val(&non_sync_ctx, "dispatch_receive_ip_packet"), 0); |
| |
| // Add ip1 to the device. |
| // |
| // Should get packets destined for the solicited node address and ip1. |
| crate::device::add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, addr_sub1) |
| .unwrap(); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 1, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 1, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| sn_addr, |
| 2, |
| ); |
| |
| // Add ip2 to the device. |
| // |
| // Should get packets destined for the solicited node address, ip1 and |
| // ip2. |
| crate::device::add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, addr_sub2) |
| .unwrap(); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 3, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 4, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| sn_addr, |
| 5, |
| ); |
| |
| // Remove ip1 from the device. |
| // |
| // Should get packets destined for the solicited node address and ip2. |
| crate::device::del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, &ip1).unwrap(); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip1.get(), |
| 5, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device, |
| from_ip, |
| ip2.get(), |
| 6, |
| ); |
| receive_simple_ip_packet_test( |
| &mut sync_ctx, |
| &mut non_sync_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::DUMMY_CONFIG; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build(); |
| let device = EthernetDeviceId(0); |
| assert_eq!( |
| crate::add_ethernet_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| config.local_mac, |
| Ipv6::MINIMUM_LINK_MTU.into() |
| ), |
| DeviceIdInner::Ethernet(device).into() |
| ); |
| |
| crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device.into()); |
| // Verify that there is a single assigned address. |
| assert_eq!( |
| sync_ctx |
| .state |
| .device |
| .ethernet |
| .get(0) |
| .unwrap() |
| .ip |
| .ipv6 |
| .ip_state |
| .iter_addrs() |
| .map(|entry| entry.addr_sub().addr()) |
| .collect::<Vec<_>>(), |
| [config.local_mac.to_ipv6_link_local().addr().get()] |
| ); |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device.into(), |
| AddrSubnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), 128).unwrap(), |
| ) |
| .unwrap(); |
| // Assert that the new address got added. |
| let addr_subs: Vec<_> = sync_ctx |
| .state |
| .device |
| .ethernet |
| .get(0) |
| .unwrap() |
| .ip |
| .ipv6 |
| .ip_state |
| .iter_addrs() |
| .map(|entry| entry.addr_sub().addr().get()) |
| .collect(); |
| assert_eq!( |
| addr_subs, |
| [ |
| config.local_mac.to_ipv6_link_local().addr().get(), |
| Ipv6::LINK_LOCAL_UNICAST_SUBNET.network() |
| ] |
| ); |
| } |
| } |