blob: 8860cb3893d948c6df7a9cf7da40cceb0063e94c [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 device layer.
pub(crate) mod arp;
pub(crate) mod ethernet;
pub(crate) mod link;
pub(crate) mod loopback;
pub(crate) mod ndp;
mod state;
use alloc::{boxed::Box, vec::Vec};
use core::{
fmt::{self, Debug, Display, Formatter},
marker::PhantomData,
};
use derivative::Derivative;
use log::{debug, trace};
use net_types::{
ethernet::Mac,
ip::{
AddrSubnet, AddrSubnetEither, Ip, IpAddr, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr,
Ipv6SourceAddr, Subnet,
},
MulticastAddr, SpecifiedAddr, UnicastAddr,
};
use packet::{Buf, BufferMut, EmptyBuf, Serializer};
use packet_formats::icmp::ndp::NdpPacket;
use zerocopy::ByteSlice;
use crate::{
context::{
CounterContext, FrameContext, NonTestCtxMarker, RecvFrameContext, StateContext,
TimerContext,
},
data_structures::{IdMap, IdMapCollectionKey},
device::{
ethernet::{
EthernetDeviceState, EthernetDeviceStateBuilder, EthernetLinkDevice, EthernetTimerId,
},
link::LinkDevice,
loopback::LoopbackDeviceState,
ndp::NdpPacketHandler,
state::IpLinkDeviceState,
},
error::{ExistsError, NotFoundError, NotSupportedError},
ip::{
device::{
state::{
AddrConfig, DualStackIpDeviceState, Ipv4DeviceConfiguration, Ipv4DeviceState,
Ipv6DeviceConfiguration, Ipv6DeviceState,
},
BufferIpDeviceContext, IpDeviceContext, Ipv6DeviceContext,
},
IpDeviceId, IpDeviceIdContext,
},
BufferNonSyncContext, Instant, NonSyncContext, SyncCtx,
};
/// An execution context which provides a `DeviceId` type for various device
/// layer internals to share.
pub(crate) trait DeviceIdContext<D: LinkDevice> {
type DeviceId: Copy + Display + Debug + Eq + Send + Sync + 'static;
}
struct RecvIpFrameMeta<D, I: Ip> {
device: D,
frame_dst: FrameDestination,
_marker: PhantomData<I>,
}
impl<D, I: Ip> RecvIpFrameMeta<D, I> {
fn new(device: D, frame_dst: FrameDestination) -> RecvIpFrameMeta<D, I> {
RecvIpFrameMeta { device, frame_dst, _marker: PhantomData }
}
}
/// The non-synchronized execution context for an IP device.
pub(crate) trait IpLinkDeviceNonSyncContext<TimerId>:
TimerContext<TimerId> + CounterContext
{
}
impl<TimerId, C: TimerContext<TimerId> + CounterContext> IpLinkDeviceNonSyncContext<TimerId> for C {}
/// The context provided by the device layer to a particular IP device
/// implementation.
///
/// A blanket implementation is provided for all types that implement
/// the inherited traits.
pub(crate) trait IpLinkDeviceContext<D: LinkDevice, C: IpLinkDeviceNonSyncContext<TimerId>, TimerId>:
DeviceIdContext<D>
+ StateContext<C, IpLinkDeviceState<C::Instant, D::State>, Self::DeviceId>
+ FrameContext<C, EmptyBuf, Self::DeviceId>
+ FrameContext<C, Buf<Vec<u8>>, Self::DeviceId>
{
}
impl<
D: LinkDevice,
C: IpLinkDeviceNonSyncContext<TimerId>,
TimerId,
SC: NonTestCtxMarker
+ DeviceIdContext<D>
+ StateContext<C, IpLinkDeviceState<C::Instant, D::State>, Self::DeviceId>
+ FrameContext<C, EmptyBuf, Self::DeviceId>
+ FrameContext<C, Buf<Vec<u8>>, Self::DeviceId>,
> IpLinkDeviceContext<D, C, TimerId> for SC
{
}
/// `IpLinkDeviceContext` with an extra `B: BufferMut` parameter.
///
/// `BufferIpLinkDeviceContext` is used when sending a frame is required.
trait BufferIpLinkDeviceContext<
D: LinkDevice,
C: IpLinkDeviceNonSyncContext<TimerId>,
TimerId,
B: BufferMut,
>:
IpLinkDeviceContext<D, C, TimerId>
+ FrameContext<C, B, <Self as DeviceIdContext<D>>::DeviceId>
+ RecvFrameContext<C, B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv4>>
+ RecvFrameContext<C, B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv6>>
{
}
impl<
D: LinkDevice,
C: IpLinkDeviceNonSyncContext<TimerId>,
TimerId,
B: BufferMut,
SC: IpLinkDeviceContext<D, C, TimerId>
+ FrameContext<C, B, <Self as DeviceIdContext<D>>::DeviceId>
+ RecvFrameContext<C, B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv4>>
+ RecvFrameContext<C, B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv6>>,
> BufferIpLinkDeviceContext<D, C, TimerId, B> for SC
{
}
impl<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>>
RecvFrameContext<NonSyncCtx, B, RecvIpFrameMeta<EthernetDeviceId, Ipv4>>
for SyncCtx<NonSyncCtx>
{
fn receive_frame(
&mut self,
ctx: &mut NonSyncCtx,
metadata: RecvIpFrameMeta<EthernetDeviceId, Ipv4>,
frame: B,
) {
crate::ip::receive_ipv4_packet(
self,
ctx,
metadata.device.into(),
metadata.frame_dst,
frame,
);
}
}
impl<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>>
RecvFrameContext<NonSyncCtx, B, RecvIpFrameMeta<EthernetDeviceId, Ipv6>>
for SyncCtx<NonSyncCtx>
{
fn receive_frame(
&mut self,
ctx: &mut NonSyncCtx,
metadata: RecvIpFrameMeta<EthernetDeviceId, Ipv6>,
frame: B,
) {
crate::ip::receive_ipv6_packet(
self,
ctx,
metadata.device.into(),
metadata.frame_dst,
frame,
);
}
}
impl<NonSyncCtx: NonSyncContext>
StateContext<
NonSyncCtx,
IpLinkDeviceState<NonSyncCtx::Instant, EthernetDeviceState>,
EthernetDeviceId,
> for SyncCtx<NonSyncCtx>
{
fn get_state_with(
&self,
id0: EthernetDeviceId,
) -> &IpLinkDeviceState<NonSyncCtx::Instant, EthernetDeviceState> {
&self.state.device.ethernet.get(id0.0).unwrap()
}
fn get_state_mut_with(
&mut self,
id0: EthernetDeviceId,
) -> &mut IpLinkDeviceState<NonSyncCtx::Instant, EthernetDeviceState> {
self.state.device.ethernet.get_mut(id0.0).unwrap()
}
}
fn get_ip_device_state<NonSyncCtx: NonSyncContext>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> &DualStackIpDeviceState<NonSyncCtx::Instant> {
match device.inner() {
DeviceIdInner::Ethernet(EthernetDeviceId(id)) => {
&ctx.state.device.ethernet.get(id).unwrap().ip
}
DeviceIdInner::Loopback => &ctx.state.device.loopback.as_ref().unwrap().ip,
}
}
fn get_ip_device_state_mut<NonSyncCtx: NonSyncContext>(
ctx: &mut SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> &mut DualStackIpDeviceState<NonSyncCtx::Instant> {
match device.inner() {
DeviceIdInner::Ethernet(EthernetDeviceId(id)) => {
&mut ctx.state.device.ethernet.get_mut(id).unwrap().ip
}
DeviceIdInner::Loopback => &mut ctx.state.device.loopback.as_mut().unwrap().ip,
}
}
fn iter_devices<NonSyncCtx: NonSyncContext>(
ctx: &SyncCtx<NonSyncCtx>,
) -> impl Iterator<Item = DeviceId> + '_ {
let DeviceLayerState { ethernet, loopback } = &ctx.state.device;
ethernet
.iter()
.map(|(id, _state)| DeviceId::new_ethernet(id))
.chain(loopback.iter().map(|_state| DeviceIdInner::Loopback.into()))
}
fn get_mtu<NonSyncCtx: NonSyncContext>(ctx: &SyncCtx<NonSyncCtx>, device: DeviceId) -> u32 {
match device.inner() {
DeviceIdInner::Ethernet(id) => self::ethernet::get_mtu(ctx, id),
DeviceIdInner::Loopback => self::loopback::get_mtu(ctx),
}
}
fn join_link_multicast_group<NonSyncCtx: NonSyncContext, A: IpAddress>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device_id: DeviceId,
multicast_addr: MulticastAddr<A>,
) {
match device_id.inner() {
DeviceIdInner::Ethernet(id) => self::ethernet::join_link_multicast(
sync_ctx,
ctx,
id,
MulticastAddr::from(&multicast_addr),
),
DeviceIdInner::Loopback => {}
}
}
fn leave_link_multicast_group<NonSyncCtx: NonSyncContext, A: IpAddress>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device_id: DeviceId,
multicast_addr: MulticastAddr<A>,
) {
match device_id.inner() {
DeviceIdInner::Ethernet(id) => self::ethernet::leave_link_multicast(
sync_ctx,
ctx,
id,
MulticastAddr::from(&multicast_addr),
),
DeviceIdInner::Loopback => {}
}
}
impl<NonSyncCtx: NonSyncContext> IpDeviceContext<Ipv4, NonSyncCtx> for SyncCtx<NonSyncCtx> {
fn get_ip_device_state(&self, device: DeviceId) -> &Ipv4DeviceState<NonSyncCtx::Instant> {
&get_ip_device_state(self, device).ipv4
}
fn get_ip_device_state_mut(
&mut self,
device: DeviceId,
) -> &mut Ipv4DeviceState<NonSyncCtx::Instant> {
&mut get_ip_device_state_mut(self, device).ipv4
}
fn iter_devices(&self) -> Box<dyn Iterator<Item = DeviceId> + '_> {
Box::new(iter_devices(self))
}
fn get_mtu(&self, device_id: Self::DeviceId) -> u32 {
get_mtu(self, device_id)
}
fn join_link_multicast_group(
&mut self,
ctx: &mut NonSyncCtx,
device_id: Self::DeviceId,
multicast_addr: MulticastAddr<Ipv4Addr>,
) {
join_link_multicast_group(self, ctx, device_id, multicast_addr)
}
fn leave_link_multicast_group(
&mut self,
ctx: &mut NonSyncCtx,
device_id: Self::DeviceId,
multicast_addr: MulticastAddr<Ipv4Addr>,
) {
leave_link_multicast_group(self, ctx, device_id, multicast_addr)
}
}
fn send_ip_frame<
B: BufferMut,
NonSyncCtx: BufferNonSyncContext<B>,
S: Serializer<Buffer = B>,
A: IpAddress,
>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
local_addr: SpecifiedAddr<A>,
body: S,
) -> Result<(), S> {
match device.inner() {
DeviceIdInner::Ethernet(id) => {
self::ethernet::send_ip_frame(sync_ctx, ctx, id, local_addr, body)
}
DeviceIdInner::Loopback => self::loopback::send_ip_frame(sync_ctx, ctx, local_addr, body),
}
}
impl<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>> BufferIpDeviceContext<Ipv4, NonSyncCtx, B>
for SyncCtx<NonSyncCtx>
{
fn send_ip_frame<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut NonSyncCtx,
device: DeviceId,
local_addr: SpecifiedAddr<Ipv4Addr>,
body: S,
) -> Result<(), S> {
send_ip_frame(self, ctx, device, local_addr, body)
}
}
impl<NonSyncCtx: NonSyncContext> IpDeviceContext<Ipv6, NonSyncCtx> for SyncCtx<NonSyncCtx> {
fn get_ip_device_state(&self, device: DeviceId) -> &Ipv6DeviceState<NonSyncCtx::Instant> {
&get_ip_device_state(self, device).ipv6
}
fn get_ip_device_state_mut(
&mut self,
device: DeviceId,
) -> &mut Ipv6DeviceState<NonSyncCtx::Instant> {
&mut get_ip_device_state_mut(self, device).ipv6
}
fn iter_devices(&self) -> Box<dyn Iterator<Item = DeviceId> + '_> {
Box::new(iter_devices(self))
}
fn get_mtu(&self, device_id: Self::DeviceId) -> u32 {
get_mtu(self, device_id)
}
fn join_link_multicast_group(
&mut self,
ctx: &mut NonSyncCtx,
device_id: Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
) {
join_link_multicast_group(self, ctx, device_id, multicast_addr)
}
fn leave_link_multicast_group(
&mut self,
ctx: &mut NonSyncCtx,
device_id: Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
) {
leave_link_multicast_group(self, ctx, device_id, multicast_addr)
}
}
impl<NonSyncCtx: NonSyncContext> Ipv6DeviceContext<NonSyncCtx> for SyncCtx<NonSyncCtx> {
fn get_link_layer_addr_bytes(&self, device_id: Self::DeviceId) -> Option<&[u8]> {
match device_id.inner() {
DeviceIdInner::Ethernet(id) => Some(ethernet::get_mac(self, id).as_ref().as_ref()),
DeviceIdInner::Loopback => None,
}
}
fn get_eui64_iid(&self, device_id: Self::DeviceId) -> Option<[u8; 8]> {
match device_id.inner() {
DeviceIdInner::Ethernet(id) => {
Some(ethernet::get_mac(self, id).to_eui64_with_magic(Mac::DEFAULT_EUI_MAGIC))
}
DeviceIdInner::Loopback => None,
}
}
}
impl<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>> BufferIpDeviceContext<Ipv6, NonSyncCtx, B>
for SyncCtx<NonSyncCtx>
{
fn send_ip_frame<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut NonSyncCtx,
device: DeviceId,
local_addr: SpecifiedAddr<Ipv6Addr>,
body: S,
) -> Result<(), S> {
send_ip_frame(self, ctx, device, local_addr, body)
}
}
impl<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>>
FrameContext<NonSyncCtx, B, EthernetDeviceId> for SyncCtx<NonSyncCtx>
{
fn send_frame<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut NonSyncCtx,
device: EthernetDeviceId,
frame: S,
) -> Result<(), S> {
DeviceLayerEventDispatcher::send_frame(ctx, device.into(), frame)
}
}
/// Device IDs identifying Ethernet devices.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) struct EthernetDeviceId(usize);
impl Debug for EthernetDeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let device: DeviceId = self.clone().into();
write!(f, "{:?}", device)
}
}
impl Display for EthernetDeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let device: DeviceId = self.clone().into();
write!(f, "{}", device)
}
}
impl From<usize> for EthernetDeviceId {
fn from(id: usize) -> EthernetDeviceId {
EthernetDeviceId(id)
}
}
/// The identifier for timer events in the device layer.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub(crate) struct DeviceLayerTimerId(DeviceLayerTimerIdInner);
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
enum DeviceLayerTimerIdInner {
/// A timer event for an Ethernet device.
Ethernet(EthernetTimerId<EthernetDeviceId>),
}
impl From<EthernetTimerId<EthernetDeviceId>> for DeviceLayerTimerId {
fn from(id: EthernetTimerId<EthernetDeviceId>) -> DeviceLayerTimerId {
DeviceLayerTimerId(DeviceLayerTimerIdInner::Ethernet(id))
}
}
impl<NonSyncCtx: NonSyncContext> DeviceIdContext<EthernetLinkDevice> for SyncCtx<NonSyncCtx> {
type DeviceId = EthernetDeviceId;
}
impl_timer_context!(
DeviceLayerTimerId,
EthernetTimerId<EthernetDeviceId>,
DeviceLayerTimerId(DeviceLayerTimerIdInner::Ethernet(id)),
id
);
/// Handle a timer event firing in the device layer.
pub(crate) fn handle_timer<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
id: DeviceLayerTimerId,
) {
match id.0 {
DeviceLayerTimerIdInner::Ethernet(id) => ethernet::handle_timer(sync_ctx, ctx, id),
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) enum DeviceIdInner {
Ethernet(EthernetDeviceId),
Loopback,
}
/// An ID identifying a device.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct DeviceId(pub(crate) DeviceIdInner);
impl From<DeviceIdInner> for DeviceId {
fn from(id: DeviceIdInner) -> DeviceId {
DeviceId(id)
}
}
impl From<EthernetDeviceId> for DeviceId {
fn from(id: EthernetDeviceId) -> DeviceId {
DeviceIdInner::Ethernet(id).into()
}
}
impl DeviceId {
/// Construct a new `DeviceId` for an Ethernet device.
pub(crate) const fn new_ethernet(id: usize) -> DeviceId {
DeviceId(DeviceIdInner::Ethernet(EthernetDeviceId(id)))
}
pub(crate) fn inner(self) -> DeviceIdInner {
let DeviceId(id) = self;
id
}
}
impl IpDeviceId for DeviceId {
fn is_loopback(&self) -> bool {
match self.inner() {
DeviceIdInner::Loopback => true,
DeviceIdInner::Ethernet(_) => false,
}
}
}
impl Display for DeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
match self.inner() {
DeviceIdInner::Ethernet(EthernetDeviceId(id)) => write!(f, "Ethernet({})", id),
DeviceIdInner::Loopback => write!(f, "Loopback"),
}
}
}
impl Debug for DeviceId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
Display::fmt(self, f)
}
}
impl IdMapCollectionKey for DeviceId {
const VARIANT_COUNT: usize = 2;
fn get_id(&self) -> usize {
match self.inner() {
DeviceIdInner::Ethernet(EthernetDeviceId(id)) => id,
DeviceIdInner::Loopback => 0,
}
}
fn get_variant(&self) -> usize {
match self.inner() {
DeviceIdInner::Ethernet(_) => 0,
DeviceIdInner::Loopback => 1,
}
}
}
// TODO(joshlf): Does the IP layer ever need to distinguish between broadcast
// and multicast frames?
/// The type of address used as the source address in a device-layer frame:
/// unicast or broadcast.
///
/// `FrameDestination` is used to implement RFC 1122 section 3.2.2 and RFC 4443
/// section 2.4.e, which govern when to avoid sending an ICMP error message for
/// ICMP and ICMPv6 respectively.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum FrameDestination {
/// A unicast address - one which is neither multicast nor broadcast.
Unicast,
/// A multicast address; if the addressing scheme supports overlap between
/// multicast and broadcast, then broadcast addresses should use the
/// `Broadcast` variant.
Multicast,
/// A broadcast address; if the addressing scheme supports overlap between
/// multicast and broadcast, then broadcast addresses should use the
/// `Broadcast` variant.
Broadcast,
}
impl FrameDestination {
/// Is this `FrameDestination::Multicast`?
pub(crate) fn is_multicast(self) -> bool {
self == FrameDestination::Multicast
}
/// Is this `FrameDestination::Broadcast`?
pub(crate) fn is_broadcast(self) -> bool {
self == FrameDestination::Broadcast
}
}
/// The state associated with the device layer.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub(crate) struct DeviceLayerState<I: Instant> {
ethernet: IdMap<IpLinkDeviceState<I, EthernetDeviceState>>,
loopback: Option<IpLinkDeviceState<I, LoopbackDeviceState>>,
}
impl<I: Instant> DeviceLayerState<I> {
/// Add a new ethernet device to the device layer.
///
/// `add` adds a new `EthernetDeviceState` with the given MAC address and
/// MTU. The MTU will be taken as a limit on the size of Ethernet payloads -
/// the Ethernet header is not counted towards the MTU.
pub(crate) fn add_ethernet_device(&mut self, mac: UnicastAddr<Mac>, mtu: u32) -> DeviceId {
let Self { ethernet, loopback: _ } = self;
let id = ethernet
.push(IpLinkDeviceState::new(EthernetDeviceStateBuilder::new(mac, mtu).build()));
debug!("adding Ethernet device with ID {} and MTU {}", id, mtu);
DeviceId::new_ethernet(id)
}
/// Adds a new loopback device to the device layer.
pub(crate) fn add_loopback_device(&mut self, mtu: u32) -> Result<DeviceId, ExistsError> {
let Self { ethernet: _, loopback } = self;
if let Some(IpLinkDeviceState { .. }) = loopback {
return Err(ExistsError);
}
*loopback = Some(IpLinkDeviceState::new(LoopbackDeviceState::new(mtu)));
debug!("added loopback device");
Ok(DeviceIdInner::Loopback.into())
}
}
/// An event dispatcher for the device layer.
///
/// See the `EventDispatcher` trait in the crate root for more details.
pub trait DeviceLayerEventDispatcher<B: BufferMut> {
/// Send a frame to a device driver.
///
/// If there was an MTU error while attempting to serialize the frame, the
/// original serializer is returned in the `Err` variant. All other errors
/// (for example, errors in allocating a buffer) are silently ignored and
/// reported as success.
fn send_frame<S: Serializer<Buffer = B>>(
&mut self,
device: DeviceId,
frame: S,
) -> Result<(), S>;
}
/// Remove a device from the device layer.
///
/// # Panics
///
/// Panics if `device` does not refer to an existing device.
pub fn remove_device<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
) {
// Uninstall all routes associated with the device.
crate::ip::del_device_routes::<Ipv4, _, _>(sync_ctx, ctx, &device);
crate::ip::del_device_routes::<Ipv6, _, _>(sync_ctx, ctx, &device);
match device.inner() {
DeviceIdInner::Ethernet(id) => {
crate::device::ethernet::deinitialize(sync_ctx, ctx, id);
let EthernetDeviceId(id) = id;
let _: IpLinkDeviceState<_, _> = sync_ctx
.state
.device
.ethernet
.remove(id)
.unwrap_or_else(|| panic!("no such Ethernet device: {}", id));
debug!("removing Ethernet device with ID {}", id);
}
DeviceIdInner::Loopback => {
let _: IpLinkDeviceState<_, _> =
sync_ctx.state.device.loopback.take().expect("loopback device does not exist");
debug!("removing Loopback device");
}
}
}
/// Adds a new Ethernet device to the stack.
pub fn add_ethernet_device<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
mac: UnicastAddr<Mac>,
mtu: u32,
) -> DeviceId {
let id = sync_ctx.state.device.add_ethernet_device(mac, mtu);
const LINK_LOCAL_SUBNET: Subnet<Ipv6Addr> = net_declare::net_subnet_v6!("fe80::/64");
crate::add_route(
sync_ctx,
ctx,
crate::AddableEntryEither::new(LINK_LOCAL_SUBNET.into(), Some(id), None)
.expect("create link local entry"),
)
// Adding the link local route must succeed: we're providing correct
// arguments and the device has just been created.
.expect("add link local route");
id
}
/// Adds a new device loopback device to the stack.
///
/// Only one loopback device may be installed at any point in time.
pub fn add_loopback_device<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
mtu: u32,
) -> Result<DeviceId, crate::error::ExistsError> {
sync_ctx.state.device.add_loopback_device(mtu)
}
/// Receive a device layer frame from the network.
pub fn receive_frame<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
buffer: B,
) -> Result<(), NotSupportedError> {
match device.inner() {
DeviceIdInner::Ethernet(id) => Ok(self::ethernet::receive_frame(sync_ctx, ctx, id, buffer)),
DeviceIdInner::Loopback => Err(NotSupportedError),
}
}
/// Set the promiscuous mode flag on `device`.
// TODO(rheacock): remove `allow(dead_code)` when this is used.
#[allow(dead_code)]
pub(crate) fn set_promiscuous_mode<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
enabled: bool,
) -> Result<(), NotSupportedError> {
match device.inner() {
DeviceIdInner::Ethernet(id) => {
Ok(self::ethernet::set_promiscuous_mode(sync_ctx, ctx, id, enabled))
}
DeviceIdInner::Loopback => Err(NotSupportedError),
}
}
/// 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(crate) fn add_ip_addr_subnet<NonSyncCtx: NonSyncContext, A: IpAddress>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr_sub: AddrSubnet<A>,
) -> Result<(), ExistsError> {
trace!("add_ip_addr_subnet: adding addr {:?} to device {:?}", addr_sub, device);
match addr_sub.into() {
AddrSubnetEither::V4(addr_sub) => {
crate::ip::device::add_ipv4_addr_subnet(sync_ctx, ctx, device, addr_sub)
}
AddrSubnetEither::V6(addr_sub) => crate::ip::device::add_ipv6_addr_subnet(
sync_ctx,
ctx,
device,
addr_sub,
AddrConfig::Manual,
),
}
}
/// Removes an IP address and associated subnet from this device.
///
/// Should only be called on user action.
pub(crate) fn del_ip_addr<NonSyncCtx: NonSyncContext, A: IpAddress>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr: &SpecifiedAddr<A>,
) -> Result<(), NotFoundError> {
trace!("del_ip_addr: removing addr {:?} from device {:?}", addr, device);
match Into::into(*addr) {
IpAddr::V4(addr) => crate::ip::device::del_ipv4_addr(sync_ctx, ctx, device, &addr),
IpAddr::V6(addr) => crate::ip::device::del_ipv6_addr_with_reason(
sync_ctx,
ctx,
device,
&addr,
crate::ip::device::state::DelIpv6AddrReason::ManualAction,
),
}
}
// Temporary blanket impl until we switch over entirely to the traits defined in
// the `context` module.
impl<NonSyncCtx: NonSyncContext, I: Ip> IpDeviceIdContext<I> for SyncCtx<NonSyncCtx> {
type DeviceId = DeviceId;
fn loopback_id(&self) -> Option<DeviceId> {
self.state.device.loopback.as_ref().map(|_state| DeviceIdInner::Loopback.into())
}
}
/// 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<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr: Ipv4Addr,
mac: UnicastAddr<Mac>,
) -> Result<(), NotSupportedError> {
match device.inner() {
DeviceIdInner::Ethernet(id) => {
Ok(self::ethernet::insert_static_arp_table_entry(sync_ctx, ctx, id, addr, mac.into()))
}
DeviceIdInner::Loopback => Err(NotSupportedError),
}
}
/// 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(crate) fn insert_ndp_table_entry<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr: UnicastAddr<Ipv6Addr>,
mac: Mac,
) -> Result<(), NotSupportedError> {
match device.inner() {
DeviceIdInner::Ethernet(id) => {
Ok(self::ethernet::insert_ndp_table_entry(sync_ctx, ctx, id, addr, mac))
}
DeviceIdInner::Loopback => Err(NotSupportedError),
}
}
/// Gets the IPv4 Configuration for a `device`.
pub fn get_ipv4_configuration<NonSyncCtx: NonSyncContext>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> Ipv4DeviceConfiguration {
crate::ip::device::get_ipv4_configuration(ctx, device)
}
/// Gets the IPv6 Configuration for a `device`.
pub fn get_ipv6_configuration<NonSyncCtx: NonSyncContext>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> Ipv6DeviceConfiguration {
crate::ip::device::get_ipv6_configuration(ctx, device)
}
/// Updates the IPv4 Configuration for a `device`.
pub fn update_ipv4_configuration<
NonSyncCtx: NonSyncContext,
F: FnOnce(&mut Ipv4DeviceConfiguration),
>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
update_cb: F,
) {
crate::ip::device::update_ipv4_configuration(sync_ctx, ctx, device, update_cb)
}
/// Updates the IPv6 Configuration for a `device`.
pub fn update_ipv6_configuration<
NonSyncCtx: NonSyncContext,
F: FnOnce(&mut Ipv6DeviceConfiguration),
>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
update_cb: F,
) {
crate::ip::device::update_ipv6_configuration(sync_ctx, ctx, device, update_cb)
}
/// An address that may be "tentative" in that it has not yet passed duplicate
/// address detection (DAD).
///
/// A tentative address is one for which DAD is currently being performed. An
/// address is only considered assigned to an interface once DAD has completed
/// without detecting any duplicates. See [RFC 4862] for more details.
///
/// [RFC 4862]: https://tools.ietf.org/html/rfc4862
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Tentative<T>(T, bool);
/// This implementation of `NdpPacketHandler` is consumed by ICMPv6.
impl<NonSyncCtx: NonSyncContext> NdpPacketHandler<NonSyncCtx, DeviceId> for SyncCtx<NonSyncCtx> {
fn receive_ndp_packet<B: ByteSlice>(
&mut self,
ctx: &mut NonSyncCtx,
device: DeviceId,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
packet: NdpPacket<B>,
) {
trace!("device::receive_ndp_packet");
match device.inner() {
DeviceIdInner::Ethernet(id) => {
crate::device::ndp::receive_ndp_packet(self, ctx, id, src_ip, dst_ip, packet);
}
DeviceIdInner::Loopback => {
unimplemented!("TODO(https://fxbug.dev/72378): Handle NDP on loopback")
}
}
}
}
#[cfg(test)]
pub(crate) mod testutil {
use net_types::ip::{Ipv4, Ipv6};
use super::*;
use crate::{
ip::device::state::{IpDeviceState, IpDeviceStateIpExt},
Ctx,
};
pub(crate) trait DeviceTestIpExt<Instant: crate::Instant>:
IpDeviceStateIpExt<Instant>
{
fn get_ip_device_state<NonSyncCtx: NonSyncContext<Instant = Instant>>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> &IpDeviceState<NonSyncCtx::Instant, Self>;
}
impl<Instant: crate::Instant> DeviceTestIpExt<Instant> for Ipv4 {
fn get_ip_device_state<NonSyncCtx: NonSyncContext<Instant = Instant>>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> &IpDeviceState<NonSyncCtx::Instant, Ipv4> {
crate::ip::device::get_ipv4_device_state(ctx, device)
}
}
impl<Instant: crate::Instant> DeviceTestIpExt<Instant> for Ipv6 {
fn get_ip_device_state<NonSyncCtx: NonSyncContext<Instant = Instant>>(
ctx: &SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> &IpDeviceState<NonSyncCtx::Instant, Ipv6> {
crate::ip::device::get_ipv6_device_state(ctx, device)
}
}
/// Calls [`receive_frame`], panicking on error.
pub(crate) fn receive_frame_or_panic<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>>(
Ctx { sync_ctx, non_sync_ctx }: &mut Ctx<NonSyncCtx>,
device: DeviceId,
buffer: B,
) {
crate::device::receive_frame(sync_ctx, non_sync_ctx, device, buffer).unwrap()
}
pub fn enable_device<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
) {
crate::ip::device::update_ipv4_configuration(sync_ctx, ctx, device, |config| {
config.ip_config.ip_enabled = true;
});
crate::ip::device::update_ipv6_configuration(sync_ctx, ctx, device, |config| {
config.ip_config.ip_enabled = true;
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
testutil::{
DummyEventDispatcherBuilder, DummyEventDispatcherConfig, DummySyncCtx, DUMMY_CONFIG_V4,
},
Ctx,
};
#[test]
fn test_iter_devices() {
let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build();
fn check(sync_ctx: &DummySyncCtx, expected: &[DeviceId]) {
assert_eq!(
IpDeviceContext::<Ipv4, _>::iter_devices(sync_ctx).collect::<Vec<_>>(),
expected
);
assert_eq!(
IpDeviceContext::<Ipv6, _>::iter_devices(sync_ctx).collect::<Vec<_>>(),
expected
);
}
check(&sync_ctx, &[][..]);
let loopback_device = crate::add_loopback_device(&mut sync_ctx, 55 /* mtu */)
.expect("error adding loopback device");
check(&sync_ctx, &[loopback_device][..]);
let DummyEventDispatcherConfig {
subnet: _,
local_ip: _,
local_mac,
remote_ip: _,
remote_mac: _,
} = DUMMY_CONFIG_V4;
let ethernet_device = crate::add_ethernet_device(
&mut sync_ctx,
&mut non_sync_ctx,
local_mac,
0, /* mtu */
);
check(&sync_ctx, &[ethernet_device, loopback_device][..]);
}
}