| // 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 std::collections::{HashMap, HashSet, VecDeque}; |
| use std::iter::FilterMap; |
| use std::num::NonZeroU8; |
| use std::slice::Iter; |
| |
| use log::{debug, trace}; |
| use net_types::ethernet::Mac; |
| use net_types::ip::{AddrSubnet, Ip, IpAddr, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use net_types::{ |
| BroadcastAddress, LinkLocalAddr, MulticastAddr, MulticastAddress, SpecifiedAddr, |
| UnicastAddress, Witness, |
| }; |
| use packet::{Buf, BufferMut, EmptyBuf, Nested, Serializer}; |
| use specialize_ip_macro::{specialize_ip, specialize_ip_address}; |
| |
| use crate::context::{FrameContext, StateContext, TimerContext}; |
| use crate::device::arp::{ |
| self, ArpContext, ArpFrameMetadata, ArpHardwareType, ArpState, ArpTimerId, |
| }; |
| use crate::device::ndp::{self, NdpState}; |
| use crate::device::{ |
| is_device_initialized, AddressEntry, AddressError, AddressState, DeviceId, DeviceLayerTimerId, |
| FrameDestination, Tentative, |
| }; |
| use crate::wire::arp::peek_arp_types; |
| use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder}; |
| use crate::{BufferDispatcher, Context, EventDispatcher, StackState, TimerId, TimerIdInner}; |
| |
| 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 |
| } |
| } |
| } |
| |
| impl ndp::LinkLayerAddress for Mac { |
| const BYTES_LENGTH: usize = 6; |
| |
| fn bytes(&self) -> &[u8] { |
| self.as_ref() |
| } |
| |
| fn from_bytes(bytes: &[u8]) -> Self { |
| // assert that contract is being held: |
| debug_assert!(bytes.len() == Self::BYTES_LENGTH); |
| let mut b = [0; Self::BYTES_LENGTH]; |
| b.copy_from_slice(bytes); |
| Self::new(b) |
| } |
| } |
| |
| create_protocol_enum!( |
| /// An EtherType number. |
| #[derive(Copy, Clone, Hash, Eq, PartialEq)] |
| pub(crate) enum EtherType: u16 { |
| Ipv4, 0x0800, "IPv4"; |
| Arp, 0x0806, "ARP"; |
| Ipv6, 0x86DD, "IPv6"; |
| _, "EtherType {}"; |
| } |
| ); |
| |
| /// Builder for [`EthernetDeviceState`]. |
| pub(crate) struct EthernetDeviceStateBuilder { |
| mac: Mac, |
| mtu: u32, |
| route_ipv4: bool, |
| route_ipv6: bool, |
| ndp_configs: ndp::NdpConfigurations, |
| } |
| |
| impl EthernetDeviceStateBuilder { |
| /// Create a new `EthernetDeviceStateBuilder`. |
| pub(crate) fn new(mac: 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, |
| route_ipv4: false, |
| route_ipv6: false, |
| ndp_configs: ndp::NdpConfigurations::default(), |
| } |
| } |
| |
| /// Update the NDP configurations that will be set on the ethernet |
| /// device. |
| pub(crate) fn set_ndp_configs(&mut self, v: ndp::NdpConfigurations) { |
| self.ndp_configs = v; |
| } |
| |
| /// Enable/disable IP packet routing. |
| pub(crate) fn set_route<I: Ip>(&mut self, v: bool) { |
| // We implement this method by using an inner function that gets specialized because when |
| // `specialize_ip_macro` generates the IP specific code, it doesn't properly handle a type's |
| // member functions. Instead lets say we have the following code: |
| // |
| // ``` |
| // #[specialize_ip] |
| // pub(crate) fn set_route<I: Ip>(&mut self, v: bool) { |
| // #[ipv4] |
| // self.route_ipv4 = v; |
| // |
| // #[ipv6] |
| // self.route_ipv6 = v; |
| // } |
| // ``` |
| // |
| // After `specialize_ip_macro` goes in and does its magic, we end up with the following |
| // (code irrelevant to this example has been replaced with "..."): |
| // |
| // ``` |
| // pub(crate) fn set_route<I: Ip>(&mut self, v: bool) { |
| // trait Ext: net_types::ip::Ip { |
| // ... |
| // fn f(&mut self, v: bool); |
| // ... |
| // } |
| // |
| // ... |
| // |
| // impl Ext for net_types::ip::Ipv4 { |
| // ... |
| // fn f(&mut self, v: bool) { self.route_ipv4 = v; } |
| // ... |
| // } |
| // |
| // impl Ext for net_types::ip::Ipv6 { |
| // ... |
| // fn f(&mut self, v: bool) { self.route_ipv6 = v; } |
| // ... |
| // } |
| // |
| // I::f::<>(self, v) |
| // } |
| // ``` |
| // |
| // Here we can see that the generated functions still use `&mut self` but in its context, |
| // `self` will refer to the `Ip` object instead of the original `self`, the |
| // `EthernetDeviceStateBuilder`. Having an inner function that has no `&mut self` arguments |
| // allows us to workaround this issue. |
| // |
| // TODO(ghanan): Use the desided code mentioned above once this bug is fixed. |
| #[specialize_ip] |
| fn inner<I: Ip>(builder: &mut EthernetDeviceStateBuilder, v: bool) { |
| #[ipv4] |
| builder.route_ipv4 = v; |
| |
| #[ipv6] |
| builder.route_ipv6 = v; |
| } |
| |
| inner::<I>(self, v) |
| } |
| |
| /// Build the `EthernetDeviceState` from this builder. |
| pub(crate) fn build<D: EventDispatcher>(self) -> EthernetDeviceState<D> { |
| let solicited_node_link_local_addr = |
| self.mac.to_ipv6_link_local().get().to_solicited_node_address(); |
| |
| let mut ipv6_multicast_groups = HashSet::new(); |
| ipv6_multicast_groups.insert(solicited_node_link_local_addr); |
| |
| let mut link_multicast_groups = HashSet::new(); |
| link_multicast_groups.insert(MulticastAddr::from(&solicited_node_link_local_addr)); |
| |
| // TODO(ghanan): Perform NDP's DAD on the link local address BEFORE receiving |
| // packets destined to it. |
| |
| EthernetDeviceState { |
| mac: self.mac, |
| mtu: self.mtu, |
| hw_mtu: self.mtu, |
| ipv6_hop_limit: ndp::HOP_LIMIT_DEFAULT, |
| ipv4_addr_sub: Vec::new(), |
| ipv6_addr_sub: Vec::new(), |
| ipv4_multicast_groups: HashSet::new(), |
| ipv6_multicast_groups, |
| link_multicast_groups, |
| ipv4_arp: ArpState::default(), |
| ndp: NdpState::new(self.ndp_configs), |
| route_ipv4: self.route_ipv4, |
| route_ipv6: self.route_ipv6, |
| pending_frames: HashMap::new(), |
| promiscuous_mode: false, |
| } |
| } |
| } |
| |
| /// The state associated with an Ethernet device. |
| pub(crate) struct EthernetDeviceState<D: EventDispatcher> { |
| /// Mac address of the device this state is for. |
| mac: 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, |
| |
| /// Default hop limit for new IPv6 packets sent from this device. |
| // TODO(ghanan): Once we separate out device-IP state from device-specific |
| // state, move this to some IPv6-device state. |
| ipv6_hop_limit: NonZeroU8, |
| |
| /// Assigned IPv4 addresses. |
| ipv4_addr_sub: Vec<AddressEntry<Ipv4Addr>>, |
| |
| /// Assigned IPv6 addresses. |
| /// |
| /// May be tentative (performing NDP's Duplicate Address Detection). |
| ipv6_addr_sub: Vec<AddressEntry<Ipv6Addr>>, |
| |
| /// IPv4 multicast groups this device has joined. |
| ipv4_multicast_groups: HashSet<MulticastAddr<Ipv4Addr>>, |
| |
| /// IPv6 multicast groups this device has joined. |
| ipv6_multicast_groups: HashSet<MulticastAddr<Ipv6Addr>>, |
| |
| /// Link multicast groups this device has joined. |
| link_multicast_groups: HashSet<MulticastAddr<Mac>>, |
| |
| /// IPv4 ARP state. |
| ipv4_arp: ArpState<Ipv4Addr, Mac>, |
| |
| /// (IPv6) NDP state. |
| ndp: ndp::NdpState<EthernetNdpDevice, D>, |
| |
| /// A flag indicating whether routing of IPv4 packets not destined for this device is |
| /// enabled. |
| /// |
| /// This flag controls whether or not packets can be routed from this device. That is, when a |
| /// packet arrives at a device it is not destined for, the packet can only be routed if the |
| /// device it arrived at has routing enabled and there exists another device that has a path |
| /// to the packet's destination, regardless the other device's routing ability. |
| /// |
| /// Default: `false`. |
| route_ipv4: bool, |
| |
| /// A flag indicating whether routing of IPv6 packets not destined for this device is |
| /// enabled. |
| /// |
| /// This flag controls whether or not packets can be routed from this device. That is, when a |
| /// packet arrives at a device it is not destined for, the packet can only be routed if the |
| /// device it arrived at has routing enabled and there exists another device that has a path |
| /// to the packet's destination, regardless the other device's routing ability. |
| /// |
| /// Default: `false`. |
| route_ipv6: bool, |
| |
| // pending_frames stores a list of serialized frames indexed by their |
| // desintation 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<D: EventDispatcher> EthernetDeviceState<D> { |
| /// 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(mut 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 == *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) |
| } |
| } |
| |
| /// An extension trait adding IP-related functionality to `Ipv4` and `Ipv6`. |
| pub(crate) trait EthernetIpExt: Ip { |
| const ETHER_TYPE: EtherType; |
| } |
| |
| impl<I: Ip> EthernetIpExt for I { |
| default const ETHER_TYPE: EtherType = EtherType::Ipv4; |
| } |
| |
| impl EthernetIpExt for Ipv4 { |
| const ETHER_TYPE: EtherType = EtherType::Ipv4; |
| } |
| |
| impl EthernetIpExt for Ipv6 { |
| const ETHER_TYPE: EtherType = EtherType::Ipv6; |
| } |
| |
| /// Send an IP packet in an Ethernet frame. |
| /// |
| /// `send_ip_frame` accepts a device ID, a local IP address, and a |
| /// `SerializationRequest`. It computes the routing information and serializes |
| /// the request in a new Ethernet frame and sends it. |
| #[specialize_ip_address] |
| pub(crate) fn send_ip_frame< |
| B: BufferMut, |
| D: BufferDispatcher<B>, |
| A: IpAddress, |
| S: Serializer<Buffer = B>, |
| >( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| local_addr: SpecifiedAddr<A>, |
| body: S, |
| ) -> Result<(), S> { |
| trace!("ethernet::send_ip_frame: local_addr = {:?}; device = {:?}", local_addr, device_id); |
| |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| let (local_mac, mtu) = (state.mac, state.mtu); |
| |
| let local_addr = local_addr.get(); |
| let dst_mac = match MulticastAddr::new(local_addr) { |
| Some(multicast) => Ok(Mac::from(&multicast)), |
| None => { |
| #[ipv4addr] |
| { |
| arp::lookup(ctx, device_id, local_mac, local_addr).ok_or(IpAddr::V4(local_addr)) |
| } |
| #[ipv6addr] |
| { |
| ndp::lookup::<_, EthernetNdpDevice>(ctx, device_id, local_addr) |
| .ok_or(IpAddr::V6(local_addr)) |
| } |
| } |
| }; |
| |
| match dst_mac { |
| Ok(dst_mac) => ctx |
| .dispatcher_mut() |
| .send_frame( |
| DeviceId::new_ethernet(device_id), |
| body.with_mtu(mtu as usize).encapsulate(EthernetFrameBuilder::new( |
| local_mac, |
| dst_mac, |
| A::Version::ETHER_TYPE, |
| )), |
| ) |
| .map_err(|ser| ser.into_inner().into_inner()), |
| Err(local_addr) => { |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| // 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, 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(crate) fn receive_frame<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| mut buffer: B, |
| ) { |
| trace!("ethernet::receive_frame: device_id = {:?}", device_id); |
| let frame = if let Ok(frame) = buffer.parse::<EthernetFrame<_>>() { |
| frame |
| } else { |
| trace!("ethernet::receive_frame: failed to parse ethernet frame"); |
| // TODO(joshlf): Do something else? |
| return; |
| }; |
| |
| let (src, dst) = (frame.src_mac(), frame.dst_mac()); |
| let device = DeviceId::new_ethernet(device_id); |
| |
| if !get_device_state(ctx.state(), device_id).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, EtherType::Ipv4) => { |
| crate::device::arp::receive_arp_packet(ctx, device_id, buffer) |
| } |
| types => debug!("got ARP packet for unsupported types: {:?}", types), |
| } |
| } |
| Some(EtherType::Ipv4) => crate::ip::receive_ipv4_packet(ctx, device, frame_dst, buffer), |
| Some(EtherType::Ipv6) => crate::ip::receive_ipv6_packet(ctx, device, frame_dst, buffer), |
| Some(EtherType::Other(_)) | None => {} // TODO(joshlf) |
| } |
| } |
| |
| /// Set the promiscuous mode flag on `device_id`. |
| pub(crate) fn set_promiscuous_mode<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| enabled: bool, |
| ) { |
| get_device_state_mut(ctx.state_mut(), device_id).promiscuous_mode = enabled; |
| } |
| |
| /// Get a single IP address for a device. |
| /// |
| /// Note, tentative IP addresses (addresses which are not yet fully bound to a |
| /// device) will not be returned by `get_ip_addr`. |
| #[specialize_ip_address] |
| pub(crate) fn get_ip_addr_subnet<D: EventDispatcher, A: IpAddress>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> Option<AddrSubnet<A>> { |
| get_ip_addr_subnets(state, device_id).nth(0) |
| } |
| |
| /// Get the IP address and subnet pais associated with this device. |
| /// |
| /// Note, tentative IP addresses (addresses which are not yet fully bound to a |
| /// device) will not be returned by `get_ip_addr_subnets`. |
| /// |
| /// Returns an [`Iterator`] of `AddrSubnet<A>`. |
| /// |
| /// See [`Tentative`] and [`AddrSubnet`] for more information. |
| #[specialize_ip_address] |
| pub(crate) fn get_ip_addr_subnets<D: EventDispatcher, A: IpAddress>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> FilterMap<Iter<AddressEntry<A>>, fn(&AddressEntry<A>) -> Option<AddrSubnet<A>>> { |
| let state = get_device_state(state, device_id); |
| |
| #[ipv4addr] |
| let addresses = &state.ipv4_addr_sub; |
| |
| #[ipv6addr] |
| let addresses = &state.ipv6_addr_sub; |
| |
| addresses.iter().filter_map( |
| |a| { |
| if a.state().is_assigned() { |
| Some(*a.addr_sub()) |
| } else { |
| None |
| } |
| }, |
| ) |
| } |
| |
| /// Get the IP address and subnet associated with this device, including tentative |
| /// addresses. |
| /// |
| /// Returns an [`Iterator`] of `Tentative<AddrSubnet<A>>`. |
| /// |
| /// See [`Tentative`] and [`AddrSubnet`] for more information. |
| #[specialize_ip_address] |
| pub(crate) fn get_ip_addr_subnets_with_tentative<D: EventDispatcher, A: IpAddress>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> FilterMap<Iter<AddressEntry<A>>, fn(&AddressEntry<A>) -> Option<Tentative<AddrSubnet<A>>>> { |
| let state = get_device_state(state, device_id); |
| |
| #[ipv4addr] |
| let addresses = &state.ipv4_addr_sub; |
| |
| #[ipv6addr] |
| let addresses = &state.ipv6_addr_sub; |
| |
| addresses.iter().filter_map(|a| { |
| if a.state().is_assigned() { |
| Some(Tentative::new_permanent(*a.addr_sub())) |
| } else if a.state().is_tentative() { |
| Some(Tentative::new_tentative(*a.addr_sub())) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| /// Get the state of an address on a device. |
| /// |
| /// Returns `None` if `addr` is not associated with `device_id`. |
| #[specialize_ip_address] |
| pub fn get_ip_addr_state<D: EventDispatcher, A: IpAddress>( |
| state: &StackState<D>, |
| device_id: usize, |
| addr: &SpecifiedAddr<A>, |
| ) -> Option<AddressState> { |
| let state = get_device_state(state, device_id); |
| |
| #[ipv4addr] |
| let addresses = &state.ipv4_addr_sub; |
| |
| #[ipv6addr] |
| let addresses = &state.ipv6_addr_sub; |
| |
| addresses.iter().find_map(|a| if a.addr_sub().addr() == *addr { Some(a.state()) } else { None }) |
| } |
| |
| /// Adds an IP address and associated subnet to this device. |
| #[specialize_ip_address] |
| pub(crate) fn add_ip_addr_subnet<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| addr_sub: AddrSubnet<A>, |
| ) -> Result<(), AddressError> { |
| let addr = addr_sub.addr(); |
| |
| if get_ip_addr_state(ctx.state(), device_id, &addr).is_some() { |
| return Err(AddressError::AlreadyExists); |
| } |
| |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| |
| #[ipv4addr] |
| state.ipv4_addr_sub.push(AddressEntry::new(addr_sub, AddressState::Assigned)); |
| |
| #[ipv6addr] |
| { |
| // First, join the solicited-node multicast group. |
| join_ip_multicast(ctx, device_id, addr.to_solicited_node_address()); |
| |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| state.ipv6_addr_sub.push(AddressEntry::new(addr_sub, AddressState::Tentative)); |
| ndp::start_duplicate_address_detection::<D, EthernetNdpDevice>(ctx, device_id, addr.get()); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Removes an IP address and associated subnet from this device. |
| #[specialize_ip_address] |
| pub(crate) fn del_ip_addr<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| addr: &SpecifiedAddr<A>, |
| ) -> Result<(), AddressError> { |
| #[ipv4addr] |
| { |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| |
| let original_size = state.ipv4_addr_sub.len(); |
| state.ipv4_addr_sub.retain(|x| x.addr_sub().addr() != *addr); |
| let new_size = state.ipv4_addr_sub.len(); |
| |
| if new_size == original_size { |
| return Err(AddressError::NotFound); |
| } |
| |
| assert_eq!(original_size - new_size, 1); |
| |
| Ok(()) |
| } |
| |
| #[ipv6addr] |
| { |
| if let Some(state) = get_ip_addr_state(ctx.state(), device_id, addr) { |
| if state.is_tentative() { |
| // Cancel current duplicate address detection for `addr` as we are |
| // removing this IP. |
| // |
| // `cancel_duplicate_address_detection` may panic if we are not |
| // performing DAD on `addr`. However, we will only reach here |
| // if `addr` is marked as tentative. If `addr` is marked as |
| // tentative, then we know that we are performing DAD on it. |
| // Given this, we know `cancel_duplicate_address_detection` will |
| // not panic. |
| ndp::cancel_duplicate_address_detection::<_, EthernetNdpDevice>( |
| ctx, |
| device_id, |
| addr.get(), |
| ); |
| } |
| } else { |
| return Err(AddressError::NotFound); |
| } |
| |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| |
| let original_size = state.ipv6_addr_sub.len(); |
| state.ipv6_addr_sub.retain(|x| x.addr_sub().addr() != *addr); |
| let new_size = state.ipv6_addr_sub.len(); |
| |
| // Since we just checked earlier if we had the address, we must have removed it |
| // now. |
| assert_eq!(original_size - new_size, 1); |
| |
| // Leave the the solicited-node multicast group. |
| leave_ip_multicast(ctx, device_id, addr.to_solicited_node_address()); |
| |
| Ok(()) |
| } |
| } |
| |
| /// Get the IPv6 link-local address associated with this device. |
| /// |
| /// The IPv6 link-local address returned is constructed from this device's MAC |
| /// address. |
| pub(crate) fn get_ipv6_link_local_addr<D: EventDispatcher>( |
| ctx: &Context<D>, |
| device_id: usize, |
| ) -> Option<LinkLocalAddr<Ipv6Addr>> { |
| // TODO(brunodalbo) the link local address is subject to the same collision |
| // verifications as prefix global addresses, we should keep a state machine |
| // about that check and cache the adopted address. For now, we just compose |
| // the link-local from the ethernet MAC. |
| Some(get_device_state(ctx.state(), device_id).mac.to_ipv6_link_local()) |
| } |
| |
| /// Add `device` to a multicast group `multicast_addr`. |
| /// |
| /// If `device` is already in the multicast group `multicast_addr`, |
| /// `join_ip_multicast` does nothing. |
| #[specialize_ip_address] |
| pub(crate) fn join_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| let device_state = get_device_state_mut(ctx.state_mut(), device_id); |
| let mac = MulticastAddr::from(&multicast_addr); |
| |
| trace!( |
| "ethernet::join_ip_multicast: joining IP multicast {:?} and MAC multicast {:?}", |
| multicast_addr, |
| mac |
| ); |
| |
| #[ipv4addr] |
| device_state.ipv4_multicast_groups.insert(multicast_addr); |
| |
| #[ipv6addr] |
| device_state.ipv6_multicast_groups.insert(multicast_addr); |
| |
| // TODO(ghanan): Make `EventDispatcher` aware of this to maintain a single source of truth. |
| device_state.link_multicast_groups.insert(mac); |
| } |
| |
| /// Remove `device` from a multicast group `multicast_addr`. |
| /// |
| /// If `device` is not in the multicast group `multicast_addr`, |
| /// `leave_ip_multicast` does nothing. |
| #[specialize_ip_address] |
| pub(crate) fn leave_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| let device_state = get_device_state_mut(ctx.state_mut(), device_id); |
| let mac = MulticastAddr::from(&multicast_addr); |
| |
| trace!( |
| "ethernet::leave_ip_multicast: leaving IP multicast {:?} and MAC multicast {:?}", |
| multicast_addr, |
| mac |
| ); |
| |
| // TODO(ghanan): Make `EventDispatcher` aware of this to maintain a single source of truth. |
| device_state.link_multicast_groups.remove(&mac); |
| |
| #[ipv4addr] |
| device_state.ipv4_multicast_groups.remove(&multicast_addr); |
| |
| #[ipv6addr] |
| device_state.ipv6_multicast_groups.remove(&multicast_addr); |
| } |
| |
| /// Is `device` in the IP multicast group `multicast_addr`? |
| #[specialize_ip_address] |
| pub(crate) fn is_in_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| device_id: usize, |
| multicast_addr: MulticastAddr<A>, |
| ) -> bool { |
| #[ipv4addr] |
| return get_device_state(ctx.state(), device_id) |
| .ipv4_multicast_groups |
| .contains(&multicast_addr); |
| |
| #[ipv6addr] |
| return get_device_state(ctx.state(), device_id) |
| .ipv6_multicast_groups |
| .contains(&multicast_addr); |
| } |
| |
| /// Get the MTU associated with this device. |
| pub(crate) fn get_mtu<D: EventDispatcher>(state: &StackState<D>, device_id: usize) -> u32 { |
| get_device_state(state, device_id).mtu |
| } |
| |
| /// Get the hop limit for new IPv6 packets that will be sent out from `device_id`. |
| pub(crate) fn get_ipv6_hop_limit<D: EventDispatcher>( |
| ctx: &Context<D>, |
| device_id: usize, |
| ) -> NonZeroU8 { |
| get_device_state(ctx.state(), device_id).ipv6_hop_limit |
| } |
| |
| /// Is IP packet routing enabled on `device_id`? |
| /// |
| /// Note, `true` does not necessarily mean that `device` is currently routing IP packets. It |
| /// only means that `device` is allowed to route packets. To route packets, this netstack must |
| /// be configured to allow IP packets to be routed if it was not destined for this node. |
| #[specialize_ip] |
| pub(crate) fn is_routing_enabled<D: EventDispatcher, I: Ip>( |
| ctx: &Context<D>, |
| device_id: usize, |
| ) -> bool { |
| #[ipv4] |
| return get_device_state(ctx.state(), device_id).route_ipv4; |
| |
| #[ipv6] |
| return get_device_state(ctx.state(), device_id).route_ipv6; |
| } |
| |
| /// Sets the IP packet routing flag on `device_id`. |
| /// |
| /// This method MUST NOT be called directly. It MUST only only called by |
| /// [`crate::device::set_routing_enabled`]. |
| /// |
| /// See [`crate::device::set_routing_enabled`] for more information. |
| #[specialize_ip] |
| pub(super) fn set_routing_enabled_inner<D: EventDispatcher, I: Ip>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| enabled: bool, |
| ) { |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| |
| #[ipv4] |
| state.route_ipv4 = enabled; |
| |
| #[ipv6] |
| state.route_ipv6 = enabled; |
| } |
| |
| /// 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. |
| pub(crate) fn insert_static_arp_table_entry<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| addr: Ipv4Addr, |
| mac: Mac, |
| ) { |
| crate::device::arp::insert_static(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. |
| #[cfg(test)] |
| pub(crate) fn insert_ndp_table_entry<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| addr: Ipv6Addr, |
| mac: Mac, |
| ) { |
| ndp::insert_neighbor::<D, EthernetNdpDevice>(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(crate) fn deinitialize<D: EventDispatcher>(ctx: &mut Context<D>, device_id: usize) { |
| crate::device::arp::deinitialize(ctx, device_id); |
| crate::device::ndp::deinitialize(ctx, device_id); |
| } |
| |
| fn get_device_state_mut<D: EventDispatcher>( |
| state: &mut StackState<D>, |
| device_id: usize, |
| ) -> &mut EthernetDeviceState<D> { |
| // TODO(joshlf): Sometimes we want lookups to be infallible (if we know that |
| // the device exists), but sometimes we want to report an error to the user. |
| // Right now, this is a DoS vector. |
| state |
| .device |
| .ethernet |
| .get_mut(device_id) |
| .unwrap_or_else(|| panic!("no such Ethernet device: {}", device_id)) |
| .device_mut() |
| } |
| |
| fn get_device_state<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> &EthernetDeviceState<D> { |
| // TODO(joshlf): Sometimes we want lookups to be infallible (if we know that |
| // the device exists), but sometimes we want to report an error to the user. |
| // Right now, this is a DoS vector. |
| state |
| .device |
| .ethernet |
| .get(device_id) |
| .unwrap_or_else(|| panic!("no such Ethernet device: {}", device_id)) |
| .device() |
| } |
| |
| impl<D: EventDispatcher> StateContext<usize, ArpState<Ipv4Addr, Mac>> for Context<D> { |
| fn get_state(&self, id: usize) -> &ArpState<Ipv4Addr, Mac> { |
| &get_device_state(self.state(), id).ipv4_arp |
| } |
| |
| fn get_state_mut(&mut self, id: usize) -> &mut ArpState<Ipv4Addr, Mac> { |
| &mut get_device_state_mut(self.state_mut(), id).ipv4_arp |
| } |
| } |
| |
| impl<D: EventDispatcher> TimerContext<ArpTimerId<usize, Ipv4Addr>> for Context<D> { |
| fn schedule_timer_instant( |
| &mut self, |
| time: D::Instant, |
| id: ArpTimerId<usize, Ipv4Addr>, |
| ) -> Option<D::Instant> { |
| self.dispatcher_mut() |
| .schedule_timeout_instant(time, TimerId::from(DeviceLayerTimerId::from(id))) |
| } |
| |
| fn cancel_timer(&mut self, id: ArpTimerId<usize, Ipv4Addr>) -> Option<D::Instant> { |
| self.dispatcher_mut().cancel_timeout(TimerId::from(DeviceLayerTimerId::from(id.clone()))) |
| } |
| |
| fn cancel_timers_with<F: FnMut(&ArpTimerId<usize, Ipv4Addr>) -> bool>(&mut self, mut f: F) { |
| self.dispatcher_mut().cancel_timeouts_with(|id| match id { |
| TimerId(TimerIdInner::DeviceLayer(DeviceLayerTimerId::ArpIpv4(id))) => f(id), |
| _ => false, |
| }) |
| } |
| |
| fn scheduled_instant(&self, id: ArpTimerId<usize, Ipv4Addr>) -> Option<D::Instant> { |
| self.dispatcher().scheduled_instant(TimerId::from(DeviceLayerTimerId::from(id))) |
| } |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> FrameContext<B, ArpFrameMetadata<usize, Mac>> |
| for Context<D> |
| { |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| meta: ArpFrameMetadata<usize, Mac>, |
| body: S, |
| ) -> Result<(), S> { |
| let src = get_device_state(self.state(), meta.device_id).mac; |
| self.dispatcher_mut() |
| .send_frame( |
| DeviceId::new_ethernet(meta.device_id), |
| body.encapsulate(EthernetFrameBuilder::new(src, meta.dst_addr, EtherType::Arp)), |
| ) |
| .map_err(Nested::into_inner) |
| } |
| } |
| |
| impl<D: EventDispatcher> ArpContext<Ipv4Addr, Mac> for Context<D> { |
| type DeviceId = usize; |
| |
| fn get_protocol_addr(&self, device_id: usize) -> Option<Ipv4Addr> { |
| get_ip_addr_subnet::<_, Ipv4Addr>(self.state(), device_id).map(|a| a.addr().get()) |
| } |
| |
| fn get_hardware_addr(&self, device_id: usize) -> Mac { |
| get_device_state(self.state(), device_id).mac |
| } |
| |
| fn address_resolved(&mut self, device_id: usize, proto_addr: Ipv4Addr, hw_addr: Mac) { |
| mac_resolved(self, device_id, IpAddr::V4(proto_addr), hw_addr); |
| } |
| |
| fn address_resolution_failed(&mut self, device_id: usize, proto_addr: Ipv4Addr) { |
| mac_resolution_failed(self, device_id, IpAddr::V4(proto_addr)); |
| } |
| } |
| |
| /// Dummy type used to implement NdpDevice |
| pub(crate) struct EthernetNdpDevice; |
| |
| impl ndp::NdpDevice for EthernetNdpDevice { |
| type LinkAddress = Mac; |
| |
| fn get_ndp_state_mut<D: EventDispatcher>( |
| state: &mut StackState<D>, |
| device_id: usize, |
| ) -> &mut ndp::NdpState<Self, D> { |
| &mut get_device_state_mut(state, device_id).ndp |
| } |
| |
| fn get_ndp_state<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> &ndp::NdpState<Self, D> { |
| &get_device_state(state, device_id).ndp |
| } |
| |
| fn get_link_layer_addr<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> Self::LinkAddress { |
| get_device_state(state, device_id).mac |
| } |
| |
| fn get_link_local_addr<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> Option<Tentative<Ipv6Addr>> { |
| let state = get_device_state(state, device_id); |
| Some(Tentative::new_permanent(state.mac.to_ipv6_link_local().get())) |
| } |
| |
| fn get_ipv6_addr<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| ) -> Option<Ipv6Addr> { |
| // Return a non tentative global address, or the link-local address if no non-tentative |
| // global addressses are associated with `device_id`. |
| |
| match get_ip_addr_subnet::<_, Ipv6Addr>(state, device_id) { |
| Some(addr_sub) => Some(addr_sub.addr().get()), |
| None => Some(get_device_state(state, device_id).mac.to_ipv6_link_local().get()), |
| } |
| } |
| |
| fn ipv6_addr_state<D: EventDispatcher>( |
| state: &StackState<D>, |
| device_id: usize, |
| address: &Ipv6Addr, |
| ) -> Option<AddressState> { |
| let address = SpecifiedAddr::new(*address)?; |
| |
| if let Some(state) = get_ip_addr_state::<_, Ipv6Addr>(state, device_id, &address) { |
| Some(state) |
| } else if get_device_state(state, device_id).mac.to_ipv6_link_local().get() == *address { |
| // TODO(ghanan): perform DAD on link local address instead of assuming |
| // it is safe to assign. |
| Some(AddressState::Assigned) |
| } else { |
| None |
| } |
| } |
| |
| fn send_ipv6_frame<D: EventDispatcher, S: Serializer<Buffer = EmptyBuf>>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| next_hop: Ipv6Addr, |
| body: S, |
| ) -> Result<(), S> { |
| // `device_id` must be initialized. |
| assert!(is_device_initialized(ctx.state(), Self::get_device_id(device_id))); |
| |
| // TODO(joshlf): Wire `SpecifiedAddr` through the `ndp` module. |
| send_ip_frame(ctx, device_id, SpecifiedAddr::new(next_hop).unwrap(), body) |
| } |
| |
| fn get_device_id(id: usize) -> DeviceId { |
| DeviceId::new_ethernet(id) |
| } |
| |
| fn address_resolved<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| address: &Ipv6Addr, |
| link_address: Self::LinkAddress, |
| ) { |
| mac_resolved(ctx, device_id, IpAddr::V6(*address), link_address); |
| } |
| |
| fn address_resolution_failed<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| address: &Ipv6Addr, |
| ) { |
| mac_resolution_failed(ctx, device_id, IpAddr::V6(*address)); |
| } |
| |
| fn duplicate_address_detected<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| addr: Ipv6Addr, |
| ) { |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| |
| let original_size = state.ipv6_addr_sub.len(); |
| state.ipv6_addr_sub.retain(|x| x.addr_sub().addr().get() != addr); |
| let new_size = state.ipv6_addr_sub.len(); |
| |
| // We must have removed the address. |
| assert_eq!(original_size - new_size, 1); |
| |
| // Leave the the solicited-node multicast group. |
| leave_ip_multicast(ctx, device_id, addr.to_solicited_node_address()); |
| |
| // TODO: we need to pick a different address depending on what flow we are using. |
| } |
| |
| fn unique_address_determined<D: EventDispatcher>( |
| state: &mut StackState<D>, |
| device_id: usize, |
| addr: Ipv6Addr, |
| ) { |
| trace!( |
| "ethernet::unique_address_determined: device_id = {:?}; addr = {:?}", |
| device_id, |
| addr |
| ); |
| |
| if let Some(entry) = get_device_state_mut(state, device_id) |
| .ipv6_addr_sub |
| .iter_mut() |
| .find(|a| a.addr_sub().addr().get() == addr) |
| { |
| entry.mark_permanent(); |
| } else { |
| panic!("Attempted to resolve an unknown tentative address"); |
| } |
| } |
| |
| fn set_mtu<D: EventDispatcher>(state: &mut StackState<D>, device_id: usize, 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 >= crate::ip::path_mtu::IPV6_MIN_MTU); |
| |
| let dev_state = get_device_state_mut(state, device_id); |
| |
| // 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; |
| } |
| |
| fn set_hop_limit<D: EventDispatcher>( |
| state: &mut StackState<D>, |
| device_id: usize, |
| hop_limit: NonZeroU8, |
| ) { |
| get_device_state_mut(state, device_id).ipv6_hop_limit = hop_limit; |
| } |
| |
| fn is_router<D: EventDispatcher>(ctx: &Context<D>, device_id: usize) -> bool { |
| crate::device::is_router_device::<_, Ipv6>(ctx, Self::get_device_id(device_id)) |
| } |
| } |
| |
| /// 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| address: IpAddr, |
| dst_mac: Mac, |
| ) { |
| let state = get_device_state_mut(ctx.state_mut(), device_id); |
| let device_id = DeviceId::new_ethernet(device_id); |
| 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 = ctx.dispatcher_mut().send_frame( |
| device_id, |
| frame.encapsulate(EthernetFrameBuilder::new(src_mac, 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device_id: usize, |
| 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 = get_device_state_mut(ctx.state_mut(), device_id); |
| if let Some(pending) = state.take_pending_frames(address) { |
| log_unimplemented!((), "ethernet mac resolution failed not implemented"); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use packet::Buf; |
| use rand::Rng; |
| use specialize_ip_macro::specialize_ip; |
| |
| use super::*; |
| use crate::device::{is_routing_enabled, set_routing_enabled}; |
| use crate::ip::{ |
| dispatch_receive_ip_packet_name, receive_ip_packet, IpExt, IpPacketBuilder, IpProto, |
| IPV6_MIN_MTU, |
| }; |
| use crate::testutil::{ |
| add_arp_or_ndp_table_entry, get_counter_val, get_dummy_config, get_other_ip_address, |
| new_rng, parse_icmp_packet_in_ip_packet_in_ethernet_frame, |
| parse_ip_packet_in_ethernet_frame, DummyEventDispatcher, DummyEventDispatcherBuilder, |
| DUMMY_CONFIG_V4, |
| }; |
| use crate::wire::icmp::{IcmpDestUnreachable, IcmpIpExt, IcmpMessage}; |
| use crate::wire::testdata::{dns_request_v4, dns_request_v6}; |
| use crate::StackStateBuilder; |
| |
| #[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 mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V4) |
| .build::<DummyEventDispatcher>(); |
| send_ip_frame(&mut ctx, 0, DUMMY_CONFIG_V4.remote_ip, Buf::new(&mut vec![0; size], ..)); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expect_frames_sent); |
| } |
| |
| // The Ethernet device MTU currently defaults to IPV6_MIN_MTU. |
| test(crate::ip::IPV6_MIN_MTU as usize, 1); |
| test(crate::ip::IPV6_MIN_MTU as usize + 1, 0); |
| } |
| |
| #[test] |
| fn test_pending_frames() { |
| let mut state = EthernetDeviceStateBuilder::new(DUMMY_CONFIG_V4.local_mac, IPV6_MIN_MTU) |
| .build::<DummyEventDispatcher>(); |
| let ip = IpAddr::V4(DUMMY_CONFIG_V4.local_ip.into_addr()); |
| state.add_pending_frame(ip, Buf::new(vec![1], ..)); |
| state.add_pending_frame(ip, Buf::new(vec![2], ..)); |
| state.add_pending_frame(ip, Buf::new(vec![3], ..)); |
| |
| // 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]); |
| } |
| |
| #[specialize_ip] |
| fn test_receive_ip_frame<I: Ip>(initialize: bool) { |
| // |
| // Should only receive a frame if the device is initialized |
| // |
| |
| let config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = ctx.state_mut().add_ethernet_device(config.local_mac, crate::ip::IPV6_MIN_MTU); |
| |
| #[ipv4] |
| let mut bytes = dns_request_v4::ETHERNET_FRAME.bytes.to_vec(); |
| |
| #[ipv6] |
| let mut bytes = dns_request_v6::ETHERNET_FRAME.bytes.to_vec(); |
| |
| let mac_bytes = config.local_mac.bytes(); |
| bytes[0..6].copy_from_slice(&mac_bytes); |
| |
| if initialize { |
| crate::device::initialize_device(&mut ctx, device); |
| } |
| |
| // Will panic if we do not initialize. |
| crate::device::receive_frame(&mut ctx, device, Buf::new(bytes, ..)); |
| |
| // If we did not initialize, we would not reach here since |
| // `receive_frame` would have paniced. |
| #[ipv4] |
| assert_eq!(get_counter_val(&mut ctx, "receive_ipv4_packet"), 1); |
| #[ipv6] |
| assert_eq!(get_counter_val(&mut ctx, "receive_ipv6_packet"), 1); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn receive_frame_ipv4_uninitialized() { |
| test_receive_ip_frame::<Ipv4>(false); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn receive_frame_ipv6_uninitialized() { |
| test_receive_ip_frame::<Ipv6>(false); |
| } |
| |
| #[test] |
| fn receive_frame_ipv4_initialized() { |
| test_receive_ip_frame::<Ipv4>(true); |
| } |
| |
| #[test] |
| fn receive_frame_ipv6_initialized() { |
| test_receive_ip_frame::<Ipv6>(true); |
| } |
| |
| #[specialize_ip] |
| fn test_send_ip_frame<I: Ip>(initialize: bool) { |
| // |
| // Should only send a frame if the device is initialized |
| // |
| |
| let config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = ctx.state_mut().add_ethernet_device(config.local_mac, crate::ip::IPV6_MIN_MTU); |
| |
| #[ipv4] |
| let mut bytes = dns_request_v4::ETHERNET_FRAME.bytes.to_vec(); |
| |
| #[ipv6] |
| let mut bytes = dns_request_v6::ETHERNET_FRAME.bytes.to_vec(); |
| |
| let mac_bytes = config.local_mac.bytes(); |
| bytes[6..12].copy_from_slice(&mac_bytes); |
| |
| if initialize { |
| crate::device::initialize_device(&mut ctx, device); |
| } |
| |
| // Will panic if we do not initialize. |
| crate::device::send_ip_frame(&mut ctx, device, config.remote_ip, Buf::new(bytes, ..)); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn send_frame_ipv4_uninitialized() { |
| test_send_ip_frame::<Ipv4>(false); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn send_frame_ipv6_uninitialized() { |
| test_send_ip_frame::<Ipv6>(false); |
| } |
| |
| #[test] |
| fn send_frame_ipv4_initialized() { |
| test_send_ip_frame::<Ipv4>(true); |
| } |
| |
| #[test] |
| fn send_frame_ipv6_initialized() { |
| test_send_ip_frame::<Ipv6>(true); |
| } |
| |
| #[test] |
| fn initialize_once() { |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = |
| ctx.state_mut().add_ethernet_device(DUMMY_CONFIG_V4.local_mac, crate::ip::IPV6_MIN_MTU); |
| crate::device::initialize_device(&mut ctx, device); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn initialize_multiple() { |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = |
| ctx.state_mut().add_ethernet_device(DUMMY_CONFIG_V4.local_mac, crate::ip::IPV6_MIN_MTU); |
| crate::device::initialize_device(&mut ctx, device); |
| |
| // Should panic since we are already initialized. |
| crate::device::initialize_device(&mut ctx, device); |
| } |
| |
| fn test_set_ip_routing<I: Ip>() |
| where |
| I: IcmpIpExt, |
| IcmpDestUnreachable: for<'a> IcmpMessage<I, &'a [u8]>, |
| { |
| #[specialize_ip] |
| fn check_other_is_routing_enabled<I: Ip>( |
| ctx: &Context<DummyEventDispatcher>, |
| device: DeviceId, |
| expected: bool, |
| ) { |
| #[ipv4] |
| assert_eq!(is_routing_enabled::<_, Ipv6>(ctx, device), expected); |
| |
| #[ipv6] |
| assert_eq!(is_routing_enabled::<_, Ipv4>(ctx, device), expected); |
| } |
| |
| #[specialize_ip] |
| fn check_icmp<I: Ip>(buf: &[u8]) { |
| #[ipv4] |
| let (src_mac, dst_mac, src_ip, dst_ip, message, code) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpDestUnreachable, _>( |
| buf, |
| |_| {}, |
| ) |
| .unwrap(); |
| |
| #[ipv6] |
| let (src_mac, dst_mac, src_ip, dst_ip, message, code) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, IcmpDestUnreachable, _>( |
| buf, |
| |_| {}, |
| ) |
| .unwrap(); |
| } |
| |
| let src_ip = get_other_ip_address::<I::Addr>(3); |
| let src_mac = Mac::new([10, 11, 12, 13, 14, 15]); |
| let config = get_dummy_config::<I::Addr>(); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| let mut rng = new_rng(70812476915813); |
| let mut body: Vec<u8> = std::iter::repeat_with(|| rng.gen()).take(100).collect(); |
| let buf = Buf::new(&mut body[..], ..) |
| .encapsulate(<I as IpExt>::PacketBuilder::new( |
| src_ip.get(), |
| config.remote_ip.get(), |
| 64, |
| IpProto::Tcp, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .unwrap_b(); |
| |
| // |
| // Test with netstack no fowarding |
| // |
| |
| let mut builder = DummyEventDispatcherBuilder::from_config(config.clone()); |
| add_arp_or_ndp_table_entry(&mut builder, device.id(), src_ip.get(), src_mac); |
| let mut ctx = builder.build(); |
| |
| // Should not be a router (default). |
| assert!(!is_routing_enabled::<_, I>(&ctx, device)); |
| check_other_is_routing_enabled::<I>(&ctx, device, false); |
| |
| // Receiving a packet not destined for the node should result in a dest unreachable message. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 1); |
| check_icmp::<I>(&ctx.dispatcher().frames_sent()[0].1); |
| |
| // Attempting to set router should work, but it still won't be able to |
| // route packets. |
| set_routing_enabled::<_, I>(&mut ctx, device, true); |
| assert!(is_routing_enabled::<_, I>(&ctx, device)); |
| // Should not update other Ip routing status. |
| check_other_is_routing_enabled::<I>(&ctx, device, false); |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 2); |
| check_icmp::<I>(&ctx.dispatcher().frames_sent()[1].1); |
| |
| // |
| // Test with netstack fowarding |
| // |
| |
| let mut state_builder = StackStateBuilder::default(); |
| state_builder.ipv4_builder().forward(true); |
| state_builder.ipv6_builder().forward(true); |
| // Most tests do not need NDP's DAD or router solicitation so disable it here. |
| let mut ndp_configs = ndp::NdpConfigurations::default(); |
| ndp_configs.set_dup_addr_detect_transmits(None); |
| ndp_configs.set_max_router_solicitations(None); |
| state_builder.device_builder().set_default_ndp_configs(ndp_configs); |
| let mut builder = DummyEventDispatcherBuilder::from_config(config.clone()); |
| add_arp_or_ndp_table_entry(&mut builder, device.id(), src_ip.get(), src_mac); |
| let mut ctx = builder.build_with(state_builder, DummyEventDispatcher::default()); |
| |
| // Should not be a router (default). |
| assert!(!is_routing_enabled::<_, I>(&ctx, device)); |
| check_other_is_routing_enabled::<I>(&ctx, device, false); |
| |
| // Receiving a packet not destined for the node should result in a dest unreachable message. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 1); |
| check_icmp::<I>(&ctx.dispatcher().frames_sent()[0].1); |
| |
| // Attempting to set router should work |
| set_routing_enabled::<_, I>(&mut ctx, device, true); |
| assert!(is_routing_enabled::<_, I>(&ctx, device)); |
| // Should not update other Ip routing status. |
| check_other_is_routing_enabled::<I>(&ctx, device, false); |
| |
| // Should route the packet since routing fully enabled (netstack & device). |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 2); |
| println!("{:?}", buf.as_ref()); |
| println!("{:?}", ctx.dispatcher().frames_sent()[1].1); |
| let (packet_buf, _, _, packet_src_ip, packet_dst_ip, proto) = |
| parse_ip_packet_in_ethernet_frame::<I>(&ctx.dispatcher().frames_sent()[1].1[..]) |
| .unwrap(); |
| assert_eq!(src_ip.get(), packet_src_ip); |
| assert_eq!(config.remote_ip.get(), packet_dst_ip); |
| assert_eq!(proto, IpProto::Tcp); |
| assert_eq!(body, packet_buf); |
| |
| // Attempt to unset router |
| set_routing_enabled::<_, I>(&mut ctx, device, false); |
| assert!(!is_routing_enabled::<_, I>(&ctx, device)); |
| check_other_is_routing_enabled::<I>(&ctx, device, false); |
| |
| // Should not route packets anymore |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 3); |
| check_icmp::<I>(&ctx.dispatcher().frames_sent()[2].1); |
| } |
| |
| #[test] |
| fn test_set_ipv4_routing() { |
| test_set_ip_routing::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_set_ipv6_routing() { |
| test_set_ip_routing::<Ipv6>(); |
| } |
| |
| fn test_promiscuous_mode<I: Ip>() { |
| // |
| // 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 = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone()) |
| .build::<DummyEventDispatcher>(); |
| 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 as IpExt>::PacketBuilder::new( |
| config.remote_ip.get(), |
| config.local_ip.get(), |
| 64, |
| IpProto::Tcp, |
| )) |
| .encapsulate(EthernetFrameBuilder::new( |
| config.remote_mac, |
| config.local_mac, |
| 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 ctx, device, false); |
| crate::device::receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut 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 ctx, device, true); |
| crate::device::receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 2); |
| |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(<I as IpExt>::PacketBuilder::new( |
| config.remote_ip.get(), |
| config.local_ip.get(), |
| 64, |
| IpProto::Tcp, |
| )) |
| .encapsulate(EthernetFrameBuilder::new(config.remote_mac, 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 ctx, device, false); |
| crate::device::receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut 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 ctx, device, true); |
| crate::device::receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 3); |
| } |
| |
| #[test] |
| fn test_promiscuous_mode_ipv4() { |
| test_promiscuous_mode::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_promiscuous_mode_ipv6() { |
| test_promiscuous_mode::<Ipv6>(); |
| } |
| |
| fn test_add_remove_ip_addresses<I: Ip>() { |
| let config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = ctx.state_mut().add_ethernet_device(config.local_mac, crate::ip::IPV6_MIN_MTU); |
| crate::device::initialize_device(&mut ctx, device); |
| |
| let ip1 = get_other_ip_address::<I::Addr>(1); |
| let ip2 = get_other_ip_address::<I::Addr>(2); |
| let ip3 = get_other_ip_address::<I::Addr>(3); |
| |
| let prefix = I::Addr::BYTES * 8; |
| let as1 = AddrSubnet::new(ip1.get(), prefix).unwrap(); |
| let as2 = AddrSubnet::new(ip2.get(), prefix).unwrap(); |
| |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Add ip1 (ok) |
| crate::device::add_ip_addr_subnet(&mut ctx, device, as1).unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Add ip2 (ok) |
| crate::device::add_ip_addr_subnet(&mut ctx, device, as2).unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Del ip1 (ok) |
| crate::device::del_ip_addr(&mut ctx, device, &ip1).unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Del ip1 again (ip1 not found) |
| assert_eq!( |
| crate::device::del_ip_addr(&mut ctx, device, &ip1).unwrap_err(), |
| AddressError::NotFound |
| ); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Add ip2 again (ip2 already exists) |
| assert_eq!( |
| crate::device::add_ip_addr_subnet(&mut ctx, device, as2).unwrap_err(), |
| AddressError::AlreadyExists |
| ); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| |
| // Add ip2 with different subnet (ip2 already exists) |
| assert_eq!( |
| crate::device::add_ip_addr_subnet( |
| &mut ctx, |
| device, |
| AddrSubnet::new(ip2.get(), prefix - 1).unwrap() |
| ) |
| .unwrap_err(), |
| AddressError::AlreadyExists |
| ); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_some()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip3).is_none()); |
| } |
| |
| #[test] |
| fn test_add_remove_ipv4_addresses() { |
| test_add_remove_ip_addresses::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_add_remove_ipv6_addresses() { |
| test_add_remove_ip_addresses::<Ipv6>(); |
| } |
| |
| fn test_multiple_ip_addresses<I: Ip>() { |
| fn inner_test<A: IpAddress>( |
| ctx: &mut Context<DummyEventDispatcher>, |
| 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, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .into_inner(); |
| |
| receive_ip_packet::<_, _, A::Version>(ctx, device, FrameDestination::Unicast, buf); |
| assert_eq!( |
| get_counter_val(ctx, dispatch_receive_ip_packet_name::<A::Version>()), |
| expected |
| ); |
| } |
| |
| let config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>(); |
| let device = ctx.state_mut().add_ethernet_device(config.local_mac, crate::ip::IPV6_MIN_MTU); |
| crate::device::initialize_device(&mut ctx, device); |
| |
| let ip1 = get_other_ip_address::<I::Addr>(1); |
| let ip2 = get_other_ip_address::<I::Addr>(2); |
| let from_ip = get_other_ip_address::<I::Addr>(3).get(); |
| |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_none()); |
| |
| // Should not receive packets on any ip. |
| inner_test(&mut ctx, device, from_ip, ip1.get(), 0); |
| inner_test(&mut ctx, device, from_ip, ip2.get(), 0); |
| |
| // Add ip1 to device. |
| crate::device::add_ip_addr_subnet( |
| &mut ctx, |
| device, |
| AddrSubnet::new(ip1.get(), I::Addr::BYTES * 8).unwrap(), |
| ) |
| .unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).unwrap().is_assigned()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).is_none()); |
| |
| // Should receive packets on ip1 but not ip2 |
| inner_test(&mut ctx, device, from_ip, ip1.get(), 1); |
| inner_test(&mut ctx, device, from_ip, ip2.get(), 1); |
| |
| // Add ip2 to device. |
| crate::device::add_ip_addr_subnet( |
| &mut ctx, |
| device, |
| AddrSubnet::new(ip2.get(), I::Addr::BYTES * 8).unwrap(), |
| ) |
| .unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).unwrap().is_assigned()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).unwrap().is_assigned()); |
| |
| // Should receive packets on both ips |
| inner_test(&mut ctx, device, from_ip, ip1.get(), 2); |
| inner_test(&mut ctx, device, from_ip, ip2.get(), 3); |
| |
| // Remove ip1 |
| crate::device::del_ip_addr(&mut ctx, device, &ip1).unwrap(); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip1).is_none()); |
| assert!(crate::device::get_ip_addr_state(ctx.state(), device, &ip2).unwrap().is_assigned()); |
| |
| // Should receive packets on ip2 |
| inner_test(&mut ctx, device, from_ip, ip1.get(), 3); |
| inner_test(&mut ctx, device, from_ip, ip2.get(), 4); |
| } |
| |
| #[test] |
| fn test_multiple_ipv4_addresses() { |
| test_multiple_ip_addresses::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_multiple_ipv6_addresses() { |
| test_multiple_ip_addresses::<Ipv6>(); |
| } |
| } |