blob: 3c89bd732ab59116675297073f3467ff2e7c2a79 [file] [log] [blame]
// 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;
use alloc::collections::VecDeque;
use alloc::vec::{IntoIter, Vec};
use core::fmt::Debug;
use core::iter::FilterMap;
use core::num::NonZeroU8;
use core::slice::Iter;
use log::{debug, trace};
use net_types::ethernet::Mac;
use net_types::ip::{AddrSubnet, Ip, IpAddr, IpAddress, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
use net_types::{
BroadcastAddress, LinkLocalAddr, LinkLocalAddress, MulticastAddr, MulticastAddress,
SpecifiedAddr, UnicastAddress, Witness,
};
use packet::{Buf, BufferMut, EmptyBuf, Nested, Serializer};
use specialize_ip_macro::specialize_ip_address;
use crate::context::{DualStateContext, FrameContext, InstantContext, StateContext, TimerHandler};
use crate::device::arp::{
self, ArpContext, ArpDeviceIdContext, ArpFrameMetadata, ArpHardwareType, ArpState, ArpTimerId,
};
use crate::device::link::LinkDevice;
use crate::device::ndp::{self, NdpContext, NdpHandler, NdpState, NdpTimerId};
use crate::device::{
AddressConfigurationType, AddressEntry, AddressError, AddressState, BufferIpDeviceContext,
DeviceIdContext, FrameDestination, IpDeviceContext, RecvIpFrameMeta, Tentative,
};
use crate::ip::gmp::igmp::{
IgmpContext, IgmpGroupState, IgmpHandler, IgmpPacketMetadata, IgmpTimerId,
};
use crate::ip::gmp::mld::{
MldContext, MldFrameMetadata, MldGroupState, MldHandler, MldReportDelay,
};
use crate::ip::gmp::{GroupJoinResult, GroupLeaveResult, MulticastGroupSet};
use crate::wire::arp::peek_arp_types;
use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck};
#[cfg(test)]
use crate::Context;
use crate::Instant;
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
}
}
}
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 {}";
}
);
/// A shorthand for `IpDeviceContext` with all of the appropriate type arguments
/// fixed to their Ethernet values.
pub(crate) trait EthernetIpDeviceContext:
IpDeviceContext<
EthernetLinkDevice,
EthernetTimerId<<Self as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetDeviceState<<Self as InstantContext>::Instant>,
>
{
}
impl<
C: IpDeviceContext<
EthernetLinkDevice,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetDeviceState<<C as InstantContext>::Instant>,
>,
> EthernetIpDeviceContext for C
{
}
/// A shorthand for `BufferIpDeviceContext` with all of the appropriate type
/// arguments fixed to their Ethernet values.
pub(super) trait BufferEthernetIpDeviceContext<B: BufferMut>:
BufferIpDeviceContext<
EthernetLinkDevice,
EthernetTimerId<<Self as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetDeviceState<<Self as InstantContext>::Instant>,
B,
>
{
}
impl<
B: BufferMut,
C: BufferIpDeviceContext<
EthernetLinkDevice,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetDeviceState<<C as InstantContext>::Instant>,
B,
>,
> BufferEthernetIpDeviceContext<B> for C
{
}
impl<C: EthernetIpDeviceContext>
DualStateContext<MulticastGroupSet<Ipv4Addr, IgmpGroupState<C::Instant>>, C::Rng, C::DeviceId>
for C
{
fn get_states_with(
&self,
device: C::DeviceId,
_id1: (),
) -> (&MulticastGroupSet<Ipv4Addr, IgmpGroupState<C::Instant>>, &C::Rng) {
let (state, rng) = self.get_states_with(device, ());
(&state.ip().ipv4_multicast_groups, rng)
}
fn get_states_mut_with(
&mut self,
device: C::DeviceId,
_id1: (),
) -> (&mut MulticastGroupSet<Ipv4Addr, IgmpGroupState<C::Instant>>, &mut C::Rng) {
let (state, rng) = self.get_states_mut_with(device, ());
(&mut state.ip_mut().ipv4_multicast_groups, rng)
}
}
impl<C: EthernetIpDeviceContext>
DualStateContext<MulticastGroupSet<Ipv6Addr, MldGroupState<C::Instant>>, C::Rng, C::DeviceId>
for C
{
fn get_states_with(
&self,
device: C::DeviceId,
_id1: (),
) -> (&MulticastGroupSet<Ipv6Addr, MldGroupState<C::Instant>>, &C::Rng) {
let (state, rng) = self.get_states_with(device, ());
(&state.ip().ipv6_multicast_groups, rng)
}
fn get_states_mut_with(
&mut self,
device: C::DeviceId,
_id1: (),
) -> (&mut MulticastGroupSet<Ipv6Addr, MldGroupState<C::Instant>>, &mut C::Rng) {
let (state, rng) = self.get_states_mut_with(device, ());
(&mut state.ip_mut().ipv6_multicast_groups, rng)
}
}
impl<C: EthernetIpDeviceContext> FrameContext<EmptyBuf, IgmpPacketMetadata<C::DeviceId>> for C {
fn send_frame<S: Serializer<Buffer = EmptyBuf>>(
&mut self,
meta: IgmpPacketMetadata<C::DeviceId>,
body: S,
) -> Result<(), S> {
send_ip_frame(self, meta.device, meta.dst_ip.into_specified(), body)
}
}
impl<C: EthernetIpDeviceContext> FrameContext<EmptyBuf, MldFrameMetadata<C::DeviceId>> for C {
fn send_frame<S: Serializer<Buffer = EmptyBuf>>(
&mut self,
meta: MldFrameMetadata<C::DeviceId>,
body: S,
) -> Result<(), S> {
send_ip_frame(self, meta.device, meta.dst_ip.into_specified(), body)
}
}
impl<C: EthernetIpDeviceContext> IgmpContext<EthernetLinkDevice> for C {
fn get_ip_addr_subnet(&self, device: C::DeviceId) -> Option<AddrSubnet<Ipv4Addr>> {
get_ip_addr_subnet(self, device)
}
fn igmp_enabled(&self, device: C::DeviceId) -> bool {
self.get_state_with(device).ip().igmp_enabled
}
}
impl<C: EthernetIpDeviceContext> MldContext<EthernetLinkDevice> for C {
fn get_ipv6_link_local_addr(&self, device: C::DeviceId) -> Option<LinkLocalAddr<Ipv6Addr>> {
get_ipv6_link_local_addr(self, device)
}
fn mld_enabled(&self, device: C::DeviceId) -> bool {
self.get_state_with(device).ip().mld_enabled
}
}
/// Builder for [`EthernetDeviceState`].
pub(crate) struct EthernetDeviceStateBuilder {
mac: Mac,
mtu: u32,
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, 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;
}
/// Build the `EthernetDeviceState` from this builder.
pub(super) fn build<I: Instant>(self) -> EthernetDeviceState<I> {
EthernetDeviceState {
mac: self.mac,
mtu: self.mtu,
hw_mtu: self.mtu,
link_multicast_groups: HashMap::new(),
ipv4_arp: ArpState::default(),
ndp: NdpState::new(self.ndp_configs),
pending_frames: HashMap::new(),
promiscuous_mode: false,
}
}
}
/// The state associated with an Ethernet device.
pub(crate) struct EthernetDeviceState<I: Instant> {
/// 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,
/// Link multicast groups this device has joined.
link_multicast_groups: HashMap<MulticastAddr<Mac>, usize>,
/// IPv4 ARP state.
ipv4_arp: ArpState<EthernetLinkDevice, Ipv4Addr>,
/// (IPv6) NDP state.
ndp: ndp::NdpState<EthernetLinkDevice, I>,
// 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<I: Instant> EthernetDeviceState<I> {
/// 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 == *dst_mac)
|| dst_mac.is_broadcast()
|| (MulticastAddr::new(*dst_mac)
.map(|a| self.link_multicast_groups.contains_key(&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;
}
/// 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>),
Igmp(IgmpTimerId<EthernetLinkDevice, D>),
Mld(MldReportDelay<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)
}
}
impl<D> From<IgmpTimerId<EthernetLinkDevice, D>> for EthernetTimerId<D> {
fn from(id: IgmpTimerId<EthernetLinkDevice, D>) -> EthernetTimerId<D> {
EthernetTimerId::Igmp(id)
}
}
impl<D> From<MldReportDelay<EthernetLinkDevice, D>> for EthernetTimerId<D> {
fn from(id: MldReportDelay<EthernetLinkDevice, D>) -> EthernetTimerId<D> {
EthernetTimerId::Mld(id)
}
}
/// Handle an Ethernet timer firing.
pub(super) fn handle_timer<C: EthernetIpDeviceContext>(
ctx: &mut C,
id: EthernetTimerId<C::DeviceId>,
) {
match id {
EthernetTimerId::Arp(id) => arp::handle_timer(ctx, id.into()),
EthernetTimerId::Ndp(id) => <C as NdpHandler<EthernetLinkDevice>>::handle_timer(ctx, id),
EthernetTimerId::Igmp(id) => TimerHandler::handle_timer(ctx, id),
EthernetTimerId::Mld(id) => TimerHandler::handle_timer(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!(
DeviceIdContext<EthernetLinkDevice>,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
NdpTimerId<EthernetLinkDevice, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetTimerId::Ndp(id),
id
);
impl_timer_context!(
DeviceIdContext<EthernetLinkDevice>,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
ArpTimerId<EthernetLinkDevice, Ipv4Addr, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetTimerId::Arp(id),
id
);
impl_timer_context!(
DeviceIdContext<EthernetLinkDevice>,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
IgmpTimerId<EthernetLinkDevice, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetTimerId::Igmp(id),
id
);
impl_timer_context!(
DeviceIdContext<EthernetLinkDevice>,
EthernetTimerId<<C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
MldReportDelay<EthernetLinkDevice, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
EthernetTimerId::Mld(id),
id
);
/// Initialize a device.
///
/// `initialize_device` sets the link-local address for `device_id` and performs DAD on it.
///
/// `device_id` MUST be ready to send packets before `initialize_device` is called.
pub(super) fn initialize_device<C: EthernetIpDeviceContext>(ctx: &mut C, device_id: C::DeviceId) {
//
// Assign a link-local address.
//
let state = ctx.get_state_with(device_id);
// There should be no way to add addresses to a device before it's
// initialized.
assert!(state.ip().ipv6_addr_sub.is_empty());
// Join the MAC-derived link-local address. Mark it as configured by SLAAC
// and not set to expire.
let addr_sub = state.link().mac.to_ipv6_link_local().into_witness();
add_ip_addr_subnet_inner(ctx, device_id, addr_sub, AddressConfigurationType::Slaac, None)
.expect(
"internal invariant violated: uninitialized device already had IP address assigned",
);
}
/// 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(super) fn send_ip_frame<
B: BufferMut,
C: EthernetIpDeviceContext + FrameContext<B, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
A: IpAddress,
S: Serializer<Buffer = B>,
>(
ctx: &mut C,
device_id: C::DeviceId,
local_addr: SpecifiedAddr<A>,
body: S,
) -> Result<(), S> {
trace!("ethernet::send_ip_frame: local_addr = {:?}; device = {:?}", local_addr, device_id);
let state = ctx.get_state_mut_with(device_id).link_mut();
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]
{
<C as NdpHandler<_>>::lookup(ctx, device_id, local_addr)
.ok_or(IpAddr::V6(local_addr))
}
}
};
match dst_mac {
Ok(dst_mac) => ctx
.send_frame(
device_id.into(),
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 = ctx.get_state_mut_with(device_id).link_mut();
// 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(super) fn receive_frame<B: BufferMut, C: BufferEthernetIpDeviceContext<B>>(
ctx: &mut C,
device_id: C::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 requierment. 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 !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, EtherType::Ipv4) => {
arp::receive_arp_packet(ctx, device_id, buffer)
}
types => debug!("got ARP packet for unsupported types: {:?}", types),
}
}
Some(EtherType::Ipv4) => {
ctx.receive_frame(RecvIpFrameMeta::<_, Ipv4>::new(device_id, frame_dst), buffer)
}
Some(EtherType::Ipv6) => {
ctx.receive_frame(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: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
enabled: bool,
) {
ctx.get_state_mut_with(device_id).link_mut().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`.
///
/// For IPv6, this only returns global (not link-local) addresses.
#[specialize_ip_address]
pub(super) fn get_ip_addr_subnet<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
) -> Option<AddrSubnet<A>> {
#[ipv4addr]
return get_assigned_ip_addr_subnets(ctx, device_id).nth(0);
#[ipv6addr]
return get_assigned_ip_addr_subnets(ctx, device_id).find(|a| {
let addr: SpecifiedAddr<Ipv6Addr> = a.addr();
!addr.is_linklocal()
});
}
/// Get the IP address and subnet pais associated with this device which are in
/// the assigned state.
///
/// Tentative IP addresses (addresses which are not yet fully bound to a device)
/// and deprecated IP addresses (addresses which have been assigned but should
/// no longer be used for new connections) will not be returned by
/// `get_assigned_ip_addr_subnets`.
///
/// Returns an [`Iterator`] of `AddrSubnet<A>`.
///
/// See [`Tentative`] and [`AddrSubnet`] for more information.
#[specialize_ip_address]
pub(super) fn get_assigned_ip_addr_subnets<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
) -> FilterMap<
Iter<AddressEntry<A, C::Instant>>,
fn(&AddressEntry<A, C::Instant>) -> Option<AddrSubnet<A>>,
> {
let state = ctx.get_state_with(device_id).ip();
#[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/subnet pairs associated with this device, including
/// tentative and deprecated addresses.
///
/// Returns an [`Iterator`] of `Tentative<AddrSubnet<A>>`.
///
/// See [`Tentative`] and [`AddrSubnet`] for more information.
#[specialize_ip_address]
pub(super) fn get_ip_addr_subnets<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
) -> Iter<AddressEntry<A, C::Instant>> {
let state = ctx.get_state_with(device_id).ip();
#[ipv4addr]
let addresses = &state.ipv4_addr_sub;
#[ipv6addr]
let addresses = &state.ipv6_addr_sub;
addresses.iter()
}
/// Get the state of an address on a device.
///
/// Returns `None` if `addr` is not associated with `device_id`.
pub(super) fn get_ip_addr_state<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
addr: &SpecifiedAddr<A>,
) -> Option<AddressState> {
get_ip_addr_state_inner(ctx, device_id, &addr.get(), None)
}
/// Get the state of an address on a device.
///
/// If `configuration_type` is provided, then only the state of an address of that
/// configuration type will be returned.
///
/// Returns `None` if `addr` is not associated with `device_id`.
// TODO(ghanan): Use `SpecializedAddr` for `addr`.
fn get_ip_addr_state_inner<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
addr: &A,
configuration_type: Option<AddressConfigurationType>,
) -> Option<AddressState> {
fn inner<A: IpAddress, I: Instant>(
addr_sub: &Vec<AddressEntry<A, I>>,
addr: A,
configuration_type: Option<AddressConfigurationType>,
) -> Option<AddressState> {
addr_sub.iter().find_map(|a| {
if a.addr_sub().addr().get() == addr
&& configuration_type.map_or(true, |x| x == a.configuration_type())
{
Some(a.state())
} else {
None
}
})
}
let state = ctx.get_state_with(device_id).ip();
addr.clone().with(
|addr| inner(&state.ipv4_addr_sub, addr, configuration_type),
|addr| inner(&state.ipv6_addr_sub, addr, configuration_type),
)
}
/// Adds an IP address and associated subnet to this device.
///
/// For IPv6, this function also joins the solicited-node multicast group and
/// begins performing Duplicate Address Detection (DAD).
pub(super) fn add_ip_addr_subnet<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
addr_sub: AddrSubnet<A>,
) -> Result<(), AddressError> {
// Add the IP address and mark it as a manually added address.
add_ip_addr_subnet_inner(ctx, device_id, addr_sub, AddressConfigurationType::Manual, None)
}
/// Adds an IP address and associated subnet to this device.
///
/// `configuration_type` is the way this address is being configured. See
/// [`AddressConfigurationType`] for more details.
///
/// For IPv6, this function also joins the solicited-node multicast group and
/// begins performing Duplicate Address Detection (DAD).
#[specialize_ip_address]
fn add_ip_addr_subnet_inner<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
addr_sub: AddrSubnet<A>,
configuration_type: AddressConfigurationType,
valid_until: Option<C::Instant>,
) -> Result<(), AddressError> {
let addr = addr_sub.addr().get();
if get_ip_addr_state_inner(ctx, device_id, &addr, None).is_some() {
return Err(AddressError::AlreadyExists);
}
let state = ctx.get_state_mut_with(device_id).ip_mut();
#[ipv4addr]
state.ipv4_addr_sub.push(AddressEntry::new(
addr_sub,
AddressState::Assigned,
configuration_type,
valid_until,
));
#[ipv6addr]
{
// First, join the solicited-node multicast group.
join_ip_multicast(ctx, device_id, addr.to_solicited_node_address());
let state = ctx.get_state_mut_with(device_id).ip_mut();
state.ipv6_addr_sub.push(AddressEntry::new(
addr_sub,
AddressState::Tentative,
configuration_type,
valid_until,
));
// Do Duplicate Address Detection on `addr`.
ctx.start_duplicate_address_detection(device_id, addr);
}
Ok(())
}
/// Removes an IP address and associated subnet from this device.
///
/// # Panics
///
/// Panics if `addr` is a link-local address.
// TODO(ghanan): Use a witness type to guarantee non-link-local-ness for `addr`.
pub(super) fn del_ip_addr<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
addr: &SpecifiedAddr<A>,
) -> Result<(), AddressError> {
del_ip_addr_inner(ctx, device_id, &addr.get(), None)
}
/// Removes an IP address and associated subnet from this device.
///
/// If `configuration_type` is provided, then only an address of that
/// configuration type will be removed.
fn del_ip_addr_inner<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
addr: &A,
configuration_type: Option<AddressConfigurationType>,
) -> Result<(), AddressError> {
// NOTE: We use two separate calls here rather than a single call to `.with`
// because both closures mutably borrow `builder`, and so they can't exist
// at the same time, which would be required in order to pass them both to
// `.with`.
addr.clone().with_v4(
|addr| {
let state = ctx.get_state_mut_with(device_id).ip_mut();
let original_size = state.ipv4_addr_sub.len();
if let Some(configuration_type) = configuration_type {
state.ipv4_addr_sub.retain(|x| {
(x.addr_sub().addr().get() != addr)
&& (x.configuration_type() == configuration_type)
});
} else {
state.ipv4_addr_sub.retain(|x| x.addr_sub().addr().get() != 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(())
},
Ok(()),
)?;
addr.clone().with_v6(
|addr| {
if let Some(state) = get_ip_addr_state_inner(ctx, device_id, &addr, configuration_type)
{
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.
ctx.cancel_duplicate_address_detection(device_id, addr);
}
} else {
return Err(AddressError::NotFound);
}
let state = ctx.get_state_mut_with(device_id).ip_mut();
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();
// 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(())
},
Ok(()),
)
}
/// Get a (non-tentative) IPv6 link-local address associated with this device.
///
/// No guarantee is made that two calls to this function will return the same
/// link-local address if multiple are available.
///
/// Returns `None` if `device_id` does not have a non-tentative link-local
/// address.
pub(super) fn get_ipv6_link_local_addr<C: EthernetIpDeviceContext>(
ctx: &C,
device_id: C::DeviceId,
) -> Option<LinkLocalAddr<Ipv6Addr>> {
ctx.get_state_with(device_id).ip().ipv6_addr_sub.iter().find_map(|a| {
if a.state().is_assigned() {
LinkLocalAddr::new(a.addr_sub().addr().get())
} else {
None
}
})
}
/// 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: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
multicast_addr: MulticastAddr<Mac>,
) {
let device_state = ctx.get_state_mut_with(device_id).link_mut();
let groups = &mut device_state.link_multicast_groups;
let counter = groups.entry(multicast_addr).or_insert(0);
*counter += 1;
if *counter == 1 {
trace!("ethernet::join_link_multicast: joining link multicast {:?}", multicast_addr,);
} else {
trace!(
"ethernet::join_link_multicast: already joinined link multicast {:?}, counter = {}",
multicast_addr,
*counter,
);
}
}
/// 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`.
fn leave_link_multicast<C: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
multicast_addr: MulticastAddr<Mac>,
) {
let device_state = ctx.get_state_mut_with(device_id).link_mut();
let groups = &mut device_state.link_multicast_groups;
// Will panic if `device_id` has not yet joined the multicast address.
let counter = groups.get_mut(&multicast_addr).unwrap();
if *counter == 1 {
trace!("ethernet::leave_link_multicast: leaving link multicast {:?}", multicast_addr,);
groups.remove(&multicast_addr);
} else {
*counter -= 1;
trace!(
"ethernet::leave_link_multicast: not leaving link multicast {:?} as there are still listeners for it, counter = {}",
multicast_addr,
*counter,
);
}
}
/// Add `device_id` to a multicast group `multicast_addr`.
///
/// Calling `join_ip_multicast` with the same `device_id` and `multicast_addr` is completely safe.
/// A counter will be kept for the number of times `join_ip_multicast` has been called with the
/// same `device_id` and `multicast_addr` pair. To completely leave a multicast group,
/// [`leave_ip_multicast`] must be called the same number of times `join_ip_multicast` has been
/// called for the same `device_id` and `multicast_addr` pair. The first time `join_ip_multicast` is
/// called for a new `device` and `multicast_addr` pair, the device will actually join the multicast
/// group.
///
/// `join_ip_multicast` is different from [`join_link_multicast`] as `join_ip_multicast` joins an
/// L3 multicast group, whereas `join_link_multicast` joins an L2 multicast group.
#[specialize_ip_address]
pub(super) fn join_ip_multicast<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
multicast_addr: MulticastAddr<A>,
) {
#[ipv4addr]
let res = ctx.igmp_join_group(device_id, multicast_addr);
#[ipv6addr]
let res = ctx.mld_join_group(device_id, multicast_addr);
match res {
GroupJoinResult::Joined(()) => {
let mac = MulticastAddr::from(&multicast_addr);
trace!(
"ethernet::join_ip_multicast: joining IP multicast {:?} and MAC multicast {:?}",
multicast_addr,
mac
);
join_link_multicast(ctx, device_id, mac);
}
GroupJoinResult::AlreadyMember => trace!(
"ethernet::join_ip_multicast: already joinined IP multicast {:?}",
multicast_addr,
),
}
}
/// Remove `device_id` from a multicast group `multicast_addr`.
///
/// `leave_ip_multicast` will attempt to remove `device_id` from a 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_ip_multicast`]. That is, if `join_ip_multicast` gets called 3 times and
/// `leave_ip_multicast` gets called two times (after all 3 `join_ip_multicast` calls), `device_id`
/// will still be in the multicast group until the next (final) call to `leave_ip_multicast`.
///
/// `leave_ip_multicast` is different from [`leave_link_multicast`] as `leave_ip_multicast` leaves
/// an L3 multicast group, whereas `leave_link_multicast` leaves an L2 multicast group.
///
/// # Panics
///
/// If `device_id` is not currently in the multicast group `multicast_addr`.
#[specialize_ip_address]
pub(super) fn leave_ip_multicast<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &mut C,
device_id: C::DeviceId,
multicast_addr: MulticastAddr<A>,
) {
#[ipv4addr]
let res = ctx.igmp_leave_group(device_id, multicast_addr);
#[ipv6addr]
let res = ctx.mld_leave_group(device_id, multicast_addr);
match res {
GroupLeaveResult::Left(()) => {
let mac = MulticastAddr::from(&multicast_addr);
trace!(
"ethernet::leave_ip_multicast: leaving IP multicast {} and MAC multicast {}",
multicast_addr,
mac
);
leave_link_multicast(ctx, device_id, mac);
}
GroupLeaveResult::StillMember => trace!(
"ethernet::leave_ip_multicast: not leaving IP multicast {} as there are still listeners for it",
multicast_addr,
),
GroupLeaveResult::NotMember => panic!(
"attempted to leave IP multicast group we were not a member of: {}",
multicast_addr,
),
}
}
/// Is `device` in the IP multicast group `multicast_addr`?
#[specialize_ip_address]
pub(super) fn is_in_ip_multicast<C: EthernetIpDeviceContext, A: IpAddress>(
ctx: &C,
device_id: C::DeviceId,
multicast_addr: MulticastAddr<A>,
) -> bool {
#[ipv4addr]
return ctx.get_state_with(device_id).ip().ipv4_multicast_groups.contains(&multicast_addr);
#[ipv6addr]
return ctx.get_state_with(device_id).ip().ipv6_multicast_groups.contains(&multicast_addr);
}
/// Get the MTU associated with this device.
pub(super) fn get_mtu<C: EthernetIpDeviceContext>(ctx: &C, device_id: C::DeviceId) -> u32 {
ctx.get_state_with(device_id).link().mtu
}
/// Get the hop limit for new IPv6 packets that will be sent out from `device_id`.
pub(super) fn get_ipv6_hop_limit<C: EthernetIpDeviceContext>(
ctx: &C,
device_id: C::DeviceId,
) -> NonZeroU8 {
ctx.get_state_with(device_id).ip().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.
pub(super) fn is_routing_enabled<C: EthernetIpDeviceContext, I: Ip>(
ctx: &C,
device_id: C::DeviceId,
) -> bool {
let state = &ctx.get_state_with(device_id).ip();
match I::VERSION {
IpVersion::V4 => state.route_ipv4,
IpVersion::V6 => state.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.
pub(super) fn set_routing_enabled_inner<C: EthernetIpDeviceContext, I: Ip>(
ctx: &mut C,
device_id: C::DeviceId,
enabled: bool,
) {
let state = ctx.get_state_mut_with(device_id).ip_mut();
match I::VERSION {
IpVersion::V4 => state.route_ipv4 = enabled,
IpVersion::V6 => 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.
// 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: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
addr: Ipv4Addr,
mac: Mac,
) {
arp::insert_static_neighbor(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: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
addr: Ipv6Addr,
mac: Mac,
) {
<C as NdpHandler<_>>::insert_static_neighbor(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: EthernetIpDeviceContext>(ctx: &mut C, device_id: C::DeviceId) {
arp::deinitialize(ctx, device_id);
<C as NdpHandler<_>>::deinitialize(ctx, device_id);
}
impl<C: EthernetIpDeviceContext> StateContext<ArpState<EthernetLinkDevice, Ipv4Addr>, C::DeviceId>
for C
{
fn get_state_with(&self, id: C::DeviceId) -> &ArpState<EthernetLinkDevice, Ipv4Addr> {
&self.get_state_with(id).link().ipv4_arp
}
fn get_state_mut_with(
&mut self,
id: C::DeviceId,
) -> &mut ArpState<EthernetLinkDevice, Ipv4Addr> {
&mut self.get_state_mut_with(id).link_mut().ipv4_arp
}
}
impl<
B: BufferMut,
C: EthernetIpDeviceContext
+ FrameContext<B, <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId>,
> FrameContext<B, ArpFrameMetadata<EthernetLinkDevice, C::DeviceId>> for C
{
fn send_frame<S: Serializer<Buffer = B>>(
&mut self,
meta: ArpFrameMetadata<EthernetLinkDevice, C::DeviceId>,
body: S,
) -> Result<(), S> {
let src = self.get_state_with(meta.device_id).link().mac;
self.send_frame(
meta.device_id,
body.encapsulate(EthernetFrameBuilder::new(src, meta.dst_addr, EtherType::Arp)),
)
.map_err(Nested::into_inner)
}
}
impl<C: EthernetIpDeviceContext> ArpDeviceIdContext<EthernetLinkDevice> for C {
type DeviceId = <C as DeviceIdContext<EthernetLinkDevice>>::DeviceId;
}
impl<C: EthernetIpDeviceContext> ArpContext<EthernetLinkDevice, Ipv4Addr> for C {
fn get_protocol_addr(
&self,
device_id: <C as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId,
) -> Option<Ipv4Addr> {
get_ip_addr_subnet::<_, Ipv4Addr>(self, device_id.into()).map(|a| a.addr().get())
}
fn get_hardware_addr(
&self,
device_id: <C as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId,
) -> Mac {
self.get_state_with(device_id.into()).link().mac
}
fn address_resolved(
&mut self,
device_id: <C as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId,
proto_addr: Ipv4Addr,
hw_addr: Mac,
) {
mac_resolved(self, device_id.into(), IpAddr::V4(proto_addr), hw_addr);
}
fn address_resolution_failed(
&mut self,
device_id: <C as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId,
proto_addr: Ipv4Addr,
) {
mac_resolution_failed(self, device_id.into(), IpAddr::V4(proto_addr));
}
fn address_resolution_expired(
&mut self,
_device_id: <C as ArpDeviceIdContext<EthernetLinkDevice>>::DeviceId,
_proto_addr: Ipv4Addr,
) {
log_unimplemented!((), "ArpContext::address_resolution_expired");
}
}
impl<C: EthernetIpDeviceContext> StateContext<NdpState<EthernetLinkDevice, C::Instant>, C::DeviceId>
for C
{
fn get_state_with(&self, id: C::DeviceId) -> &NdpState<EthernetLinkDevice, C::Instant> {
&self.get_state_with(id).link().ndp
}
fn get_state_mut_with(
&mut self,
id: C::DeviceId,
) -> &mut NdpState<EthernetLinkDevice, C::Instant> {
&mut self.get_state_mut_with(id).link_mut().ndp
}
}
impl<C: EthernetIpDeviceContext> NdpContext<EthernetLinkDevice> for C {
fn get_link_layer_addr(&self, device_id: C::DeviceId) -> Mac {
self.get_state_with(device_id).link().mac
}
fn get_interface_identifier(&self, device_id: C::DeviceId) -> [u8; 8] {
self.get_state_with(device_id).link().mac.to_eui64()
}
fn get_link_local_addr(
&self,
device_id: C::DeviceId,
) -> Option<Tentative<LinkLocalAddr<Ipv6Addr>>> {
self.get_state_with(device_id).ip().ipv6_addr_sub.iter().find_map(|a| {
let addr = LinkLocalAddr::new(a.addr_sub().addr().get())?;
Some(if a.state().is_tentative() {
Tentative::new_tentative(addr)
} else {
Tentative::new_permanent(addr)
})
})
}
fn get_ipv6_addr(&self, device_id: C::DeviceId) -> 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>(self, device_id) {
Some(addr_sub) => Some(addr_sub.addr().get()),
None => Self::get_link_local_addr(self, device_id)
.map(|a| a.try_into_permanent())
.map(|a| a.map(Witness::into_addr))
.unwrap_or(None),
}
}
type AddrEntriesIter = IntoIter<AddressEntry<Ipv6Addr, C::Instant>>;
fn get_ipv6_addr_entries(
&self,
device_id: C::DeviceId,
) -> IntoIter<AddressEntry<Ipv6Addr, C::Instant>> {
// TODO(joshlf): The fact that we clone the entire list of entries here
// is just so that we can avoid writing out a large, ugly function
// signature for `get_ipv6_addr_entries`. We would like to have the
// return value be `impl Iterator`, but impl trait is not yet supported
// on trait methods. Instead, `NdpContext` has the associated
// `AddrEntriesIter` type. However, due to lifetime issues, this
// precludes us from returning an iterator whose lifetime returns on the
// lifetime of `self` passed to this method, which in turn means that
// our only option is to return entries by value, which requires
// cloning. Since this isn't in the hot path, we accept the cost of
// cloning the vector, but it would be great if we could solve this in a
// better way.
let mut addrs = self.get_state_with(device_id).ip().ipv6_addr_sub.clone();
addrs.retain(|a| {
let addr: SpecifiedAddr<Ipv6Addr> = a.addr_sub().addr();
!addr.is_linklocal()
});
addrs.into_iter()
}
fn ipv6_addr_state(&self, device_id: C::DeviceId, address: &Ipv6Addr) -> Option<AddressState> {
let address = SpecifiedAddr::new(*address)?;
get_ip_addr_state::<_, Ipv6Addr>(self, device_id, &address)
}
fn address_resolved(&mut self, device_id: C::DeviceId, address: &Ipv6Addr, link_address: Mac) {
mac_resolved(self, device_id, IpAddr::V6(*address), link_address);
}
fn address_resolution_failed(&mut self, device_id: C::DeviceId, address: &Ipv6Addr) {
mac_resolution_failed(self, device_id, IpAddr::V6(*address));
}
fn duplicate_address_detected(&mut self, device_id: C::DeviceId, addr: Ipv6Addr) {
let state = self.get_state_mut_with(device_id).ip_mut();
let original_size = state.ipv6_addr_sub.len();
state.ipv6_addr_sub.retain(|x| x.addr_sub().addr().get() != addr);
assert_eq!(
state.ipv6_addr_sub.len(),
original_size - 1,
"duplicate address detected, but not in our list of addresses"
);
// Leave the the solicited-node multicast group.
leave_ip_multicast(self, 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(&mut self, device_id: C::DeviceId, addr: Ipv6Addr) {
trace!(
"ethernet::unique_address_determined: device_id = {:?}; addr = {:?}",
device_id,
addr
);
let state = self.get_state_mut_with(device_id).ip_mut();
if let Some(entry) =
state.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(&mut self, device_id: C::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 = self.get_state_mut_with(device_id).link_mut();
// 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(&mut self, device_id: Self::DeviceId, hop_limit: NonZeroU8) {
self.get_state_mut_with(device_id).ip_mut().ipv6_hop_limit = hop_limit;
}
fn add_slaac_addr_sub(
&mut self,
device_id: Self::DeviceId,
addr_sub: AddrSubnet<Ipv6Addr>,
valid_until: Self::Instant,
) -> Result<(), AddressError> {
trace!(
"ethernet::add_slaac_addr_sub: adding address {:?} on device {:?}",
addr_sub,
device_id
);
add_ip_addr_subnet_inner(
self,
device_id,
addr_sub,
AddressConfigurationType::Slaac,
Some(valid_until),
)
}
fn deprecate_slaac_addr(&mut self, device_id: Self::DeviceId, addr: &Ipv6Addr) {
trace!(
"ethernet::deprecate_slaac_addr: deprecating address {:?} on device {:?}",
addr,
device_id
);
let state = self.get_state_mut_with(device_id).ip_mut();
if let Some(entry) = state.ipv6_addr_sub.iter_mut().find(|a| {
(a.addr_sub().addr().get() == *addr)
&& a.configuration_type() == AddressConfigurationType::Slaac
}) {
match entry.state {
AddressState::Assigned => {
entry.state = AddressState::Deprecated;
}
AddressState::Tentative => {
trace!("ethernet::deprecate_slaac_addr: invalidating the deprecated tentative address {:?} on device {:?}", addr, device_id);
// If `addr` is currently tentative on `device_id`, the address should simply
// be invalidated as new connections should not use a deprecated address,
// and we should have no existing connections using a tentative address.
// We must have had an invalidation timeout if we just attempted to deprecate.
assert!(self
.cancel_timer(
ndp::NdpTimerId::new_invalidate_slaac_address(device_id, *addr).into()
)
.is_some());
Self::invalidate_slaac_addr(self, device_id, addr);
}
AddressState::Deprecated => unreachable!(
"We should never attempt to deprecate an already deprecated address"
),
}
} else {
panic!("Address is not configured via SLAAC on this device");
}
}
fn invalidate_slaac_addr(&mut self, device_id: Self::DeviceId, addr: &Ipv6Addr) {
trace!(
"ethernet::invalidate_slaac_addr: invalidating address {:?} on device {:?}",
addr,
device_id
);
// `unwrap` will panic if `addr` is not an address configured via SLAAC on `device_id`.
del_ip_addr_inner(self, device_id, addr, Some(AddressConfigurationType::Slaac)).unwrap();
}
fn update_slaac_addr_valid_until(
&mut self,
device_id: Self::DeviceId,
addr: &Ipv6Addr,
valid_until: Self::Instant,
) {
trace!(
"ethernet::update_slaac_addr_valid_until: updating address {:?}'s valid until instant to {:?} on device {:?}",
addr,
valid_until,
device_id
);
let state = self.get_state_mut_with(device_id).ip_mut();
if let Some(entry) = state.ipv6_addr_sub.iter_mut().find(|a| {
(a.addr_sub().addr().get() == *addr)
&& a.configuration_type() == AddressConfigurationType::Slaac
}) {
entry.valid_until = Some(valid_until);
} else {
panic!("Address is not configured via SLAAC on this device");
}
}
fn is_router(&self, device_id: Self::DeviceId) -> bool {
self.is_router_device::<Ipv6>(device_id)
}
fn send_ipv6_frame<S: Serializer<Buffer = EmptyBuf>>(
&mut self,
device_id: Self::DeviceId,
next_hop: Ipv6Addr,
body: S,
) -> Result<(), S> {
// `device_id` must not be uninitialized.
assert!(self.is_device_usable(device_id));
// TODO(joshlf): Wire `SpecifiedAddr` through the `ndp` module.
send_ip_frame(self, device_id, SpecifiedAddr::new(next_hop).unwrap(), body)
}
}
/// 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;
}
/// 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: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::DeviceId,
address: IpAddr,
dst_mac: Mac,
) {
let state = ctx.get_state_mut_with(device_id).link_mut();
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.send_frame(
device_id.into(),
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<C: EthernetIpDeviceContext>(
ctx: &mut C,
device_id: C::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 = ctx.get_state_mut_with(device_id).link_mut();
if let Some(_) = state.take_pending_frames(address) {
log_unimplemented!((), "ethernet mac resolution failed not implemented");
}
}
#[cfg(test)]
mod tests {
use packet::Buf;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use specialize_ip_macro::{ip_test, specialize_ip};
use super::*;
use crate::context::testutil::DummyInstant;
use crate::device::{
arp::ArpHandler, is_routing_enabled, set_routing_enabled, DeviceId, EthernetDeviceId,
IpLinkDeviceState,
};
use crate::ip::{
dispatch_receive_ip_packet_name, receive_ip_packet, DummyDeviceId, IpDeviceIdContext,
IpExt, IpPacketBuilder, IpProto,
};
use crate::testutil::{
add_arp_or_ndp_table_entry, get_counter_val, new_rng,
parse_icmp_packet_in_ip_packet_in_ethernet_frame, parse_ip_packet_in_ethernet_frame,
DummyEventDispatcher, DummyEventDispatcherBuilder, FakeCryptoRng, TestIpExt,
DUMMY_CONFIG_V4,
};
use crate::wire::icmp::{IcmpDestUnreachable, IcmpIpExt};
use crate::wire::testdata::{dns_request_v4, dns_request_v6};
use crate::StackStateBuilder;
struct DummyEthernetContext {
state: IpLinkDeviceState<DummyInstant, EthernetDeviceState<DummyInstant>>,
}
impl DummyEthernetContext {
fn new(mac: Mac, mtu: u32) -> DummyEthernetContext {
DummyEthernetContext {
state: IpLinkDeviceState::new(EthernetDeviceStateBuilder::new(mac, mtu).build()),
}
}
}
type DummyContext = crate::context::testutil::DummyContext<
DummyEthernetContext,
EthernetTimerId<DummyDeviceId>,
DummyDeviceId,
>;
impl
DualStateContext<
IpLinkDeviceState<DummyInstant, EthernetDeviceState<DummyInstant>>,
FakeCryptoRng<XorShiftRng>,
DummyDeviceId,
> for DummyContext
{
fn get_states_with(
&self,
_id0: DummyDeviceId,
_id1: (),
) -> (
&IpLinkDeviceState<DummyInstant, EthernetDeviceState<DummyInstant>>,
&FakeCryptoRng<XorShiftRng>,
) {
let (state, rng) = self.get_states_with((), ());
(&state.state, rng)
}
fn get_states_mut_with(
&mut self,
_id0: DummyDeviceId,
_id1: (),
) -> (
&mut IpLinkDeviceState<DummyInstant, EthernetDeviceState<DummyInstant>>,
&mut FakeCryptoRng<XorShiftRng>,
) {
let (state, rng) = self.get_states_mut_with((), ());
(&mut state.state, rng)
}
}
impl DeviceIdContext<EthernetLinkDevice> for DummyContext {
type DeviceId = DummyDeviceId;
}
impl IpDeviceIdContext for DummyContext {
type DeviceId = DummyDeviceId;
}
impl
IpDeviceContext<
EthernetLinkDevice,
EthernetTimerId<DummyDeviceId>,
EthernetDeviceState<DummyInstant>,
> for DummyContext
{
fn is_router_device<I: Ip>(&self, _device: DummyDeviceId) -> bool {
unimplemented!()
}
fn is_device_usable(&self, _device: DummyDeviceId) -> bool {
unimplemented!()
}
}
#[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 = DummyContext::with_state(DummyEthernetContext::new(
DUMMY_CONFIG_V4.local_mac,
Ipv6::MINIMUM_LINK_MTU.into(),
));
<DummyContext as ArpHandler<_, _>>::insert_static_neighbor(
&mut ctx,
DummyDeviceId,
DUMMY_CONFIG_V4.remote_ip.get(),
DUMMY_CONFIG_V4.remote_mac,
);
let _ = send_ip_frame(
&mut ctx,
DummyDeviceId,
DUMMY_CONFIG_V4.remote_ip,
Buf::new(&mut vec![0; size], ..),
);
assert_eq!(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::<DummyInstant>();
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 = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
#[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);
}
#[ip_test]
#[should_panic(expected = "assertion failed: is_device_initialized(ctx.state(), device)")]
fn receive_frame_uninitialized<I: Ip>() {
test_receive_ip_frame::<I>(false);
}
#[ip_test]
fn receive_frame_initialized<I: Ip>() {
test_receive_ip_frame::<I>(true);
}
#[specialize_ip]
fn test_send_ip_frame<I: Ip>(initialize: bool) {
//
// Should only send a frame if the device is initialized
//
let config = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
#[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.
let _ =
crate::device::send_ip_frame(&mut ctx, device, config.remote_ip, Buf::new(bytes, ..));
}
#[ip_test]
#[should_panic(expected = "assertion failed: is_device_usable(ctx.state(), device)")]
fn test_send_frame_uninitialized<I: Ip>() {
test_send_ip_frame::<I>(false);
}
#[ip_test]
fn test_send_frame_initialized<I: Ip>() {
test_send_ip_frame::<I>(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, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
}
#[test]
#[should_panic(expected = "assertion failed: state.is_uninitialized()")]
fn initialize_multiple() {
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device = ctx
.state_mut()
.add_ethernet_device(DUMMY_CONFIG_V4.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Should panic since we are already initialized.
crate::device::initialize_device(&mut ctx, device);
}
#[ip_test]
fn test_set_ip_routing<I: Ip + TestIpExt + IcmpIpExt + IpExt>() {
fn check_other_is_routing_enabled<I: Ip>(
ctx: &Context<DummyEventDispatcher>,
device: DeviceId,
expected: bool,
) {
let enabled = match I::VERSION {
IpVersion::V4 => is_routing_enabled::<_, Ipv6>(ctx, device),
IpVersion::V6 => is_routing_enabled::<_, Ipv4>(ctx, device),
};
assert_eq!(enabled, expected);
}
fn check_icmp<I: Ip>(buf: &[u8]) {
match I::VERSION {
IpVersion::V4 => {
let _ = parse_icmp_packet_in_ip_packet_in_ethernet_frame::<
Ipv4,
_,
IcmpDestUnreachable,
_,
>(buf, |_| {})
.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 = Mac::new([10, 11, 12, 13, 14, 15]);
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> = std::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,
))
.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 only result in a
// dest unreachable message if routing is enabled.
receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
// 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());
// Still should not send ICMP because device has routing disabled.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
//
// 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 not result in an
// unreachable message when routing is disabled.
receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf.clone());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
// 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(), 1);
println!("{:?}", buf.as_ref());
println!("{:?}", ctx.dispatcher().frames_sent()[0].1);
let (packet_buf, _, _, packet_src_ip, packet_dst_ip, proto, ttl) =
parse_ip_packet_in_ethernet_frame::<I>(&ctx.dispatcher().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);
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,
))
.serialize_vec_outer()
.ok()
.unwrap()
.unwrap_b();
receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, buf_unknown_dest);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
check_icmp::<I>(&ctx.dispatcher().frames_sent()[1].1);
// 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(), 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 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::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::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);
}
#[ip_test]
fn test_add_remove_ip_addresses<I: Ip + TestIpExt>() {
let config = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let ip1 = I::get_other_ip_address(1);
let ip2 = I::get_other_ip_address(2);
let ip3 = I::get_other_ip_address(3);
let prefix = I::Addr::BYTES * 8;
let as1 = AddrSubnet::new(ip1.get(), prefix).unwrap();
let as2 = AddrSubnet::new(ip2.get(), prefix).unwrap();
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, 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, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_some());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip3).is_none());
}
fn receive_simple_ip_packet_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);
}
#[ip_test]
fn test_multiple_ip_addresses<I: Ip + TestIpExt>() {
let config = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let ip1 = I::get_other_ip_address(1);
let ip2 = I::get_other_ip_address(2);
let from_ip = I::get_other_ip_address(3).get();
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_none());
// Should not receive packets on any ip.
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 0);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 0);
// Add ip1 to device.
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, device, &ip1).unwrap().is_assigned());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).is_none());
// Should receive packets on ip1 but not ip2
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 1);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 1);
// Add ip2 to device.
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, device, &ip1).unwrap().is_assigned());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).unwrap().is_assigned());
// Should receive packets on both ips
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 2);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 3);
// Remove ip1
crate::device::del_ip_addr(&mut ctx, device, &ip1).unwrap();
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip1).is_none());
assert!(crate::device::get_ip_addr_state(&ctx, device, &ip2).unwrap().is_assigned());
// Should receive packets on ip2
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 3);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 4);
}
/// Get a multicast address.
#[specialize_ip]
fn get_multicast_addr<I: Ip>() -> MulticastAddr<I::Addr> {
#[ipv4]
return MulticastAddr::new(Ipv4Addr::new([224, 0, 0, 1])).unwrap();
#[ipv6]
return MulticastAddr::new(Ipv6Addr::new([
0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
]))
.unwrap();
}
/// 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>() {
let config = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let multicast_addr = get_multicast_addr::<I>();
// Should not be in the multicast group yet.
assert!(!crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Join the multicast group.
crate::device::join_ip_multicast(&mut ctx, device, multicast_addr);
assert!(crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Leave the multicast group.
crate::device::leave_ip_multicast(&mut ctx, device, multicast_addr);
assert!(!crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Join the multicst group.
crate::device::join_ip_multicast(&mut ctx, device, multicast_addr);
assert!(crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Join it again...
crate::device::join_ip_multicast(&mut ctx, device, multicast_addr);
assert!(crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Leave it (still in it because we joined twice).
crate::device::leave_ip_multicast(&mut ctx, device, multicast_addr);
assert!(crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Leave it again... (actually left now).
crate::device::leave_ip_multicast(&mut ctx, device, multicast_addr);
assert!(!crate::device::is_in_ip_multicast(&mut 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>() {
let config = I::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let multicast_addr = get_multicast_addr::<I>();
// Should not be in the multicast group yet.
assert!(!crate::device::is_in_ip_multicast(&mut ctx, device, multicast_addr));
// Leave it (this should panic).
crate::device::leave_ip_multicast(&mut 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 mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let ip1 =
SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1]))
.unwrap();
let ip2 =
SpecifiedAddr::new(Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]))
.unwrap();
let from_ip = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 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(&mut 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 ctx, device, addr_sub1).unwrap();
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 1);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 1);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, sn_addr, 2);
// Add ip2 to the device.
//
// Should get packets destined for the solicited node address, ip1 and ip2.
crate::device::add_ip_addr_subnet(&mut ctx, device, addr_sub2).unwrap();
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 3);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 4);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, sn_addr, 5);
// Remove ip1 from the device.
//
// Should get packets destined for the solicited node address and ip2.
crate::device::del_ip_addr(&mut ctx, device, &ip1).unwrap();
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip1.get(), 5);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, ip2.get(), 6);
receive_simple_ip_packet_test(&mut ctx, device, from_ip, sn_addr, 7);
}
#[test]
fn test_get_ip_addr_subnet() {
//
// Test that `get_ip_addr_subnet` only returns non-local IPv6 addresses.
//
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
assert_eq!(device.id, 0);
let device = EthernetDeviceId(0);
// `initialize_device` adds the MAC-derived link-local IPv6 address.
initialize_device(&mut ctx, device);
let addr_sub = &ctx.state().device.ethernet.get(0).unwrap().device.ip.ipv6_addr_sub;
// Verify that there is a single assigned address - the MAC-derived link-local.
assert_eq!(addr_sub.len(), 1);
assert_eq!(
addr_sub[0].addr_sub().addr().get(),
config.local_mac.to_ipv6_link_local().addr().get()
);
// Verify that `get_ip_addr_subnet` returns no address since the only
// address present is link-local.
assert_eq!(get_ip_addr_subnet::<_, Ipv6Addr>(&ctx, device), None);
}
#[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 mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
assert_eq!(device.id, 0);
let device = EthernetDeviceId(0);
initialize_device(&mut ctx, device);
// Verify that there is a single assigned address.
assert_eq!(ctx.state().device.ethernet.get(0).unwrap().device.ip.ipv6_addr_sub.len(), 1);
add_ip_addr_subnet(
&mut ctx,
device,
AddrSubnet::new(Ipv6::LINK_LOCAL_UNICAST_SUBNET.network(), 128).unwrap(),
)
.unwrap();
// Assert that the new address got added.
let addr_sub = &ctx.state().device.ethernet.get(0).unwrap().device.ip.ipv6_addr_sub;
assert_eq!(addr_sub.len(), 2);
assert_eq!(addr_sub[1].addr_sub().addr().get(), Ipv6::LINK_LOCAL_UNICAST_SUBNET.network());
}
}