| // 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 ndp; |
| |
| use alloc::vec::Vec; |
| use core::fmt::{self, Debug, Display, Formatter}; |
| use core::marker::PhantomData; |
| use core::num::NonZeroU8; |
| |
| use log::{debug, trace}; |
| use net_types::ethernet::Mac; |
| use net_types::ip::{AddrSubnet, Ip, IpAddress, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use net_types::{LinkLocalAddr, MulticastAddr, SpecifiedAddr, Witness}; |
| use packet::{Buf, BufferMut, EmptyBuf, Serializer}; |
| use specialize_ip_macro::specialize_ip_address; |
| use zerocopy::ByteSlice; |
| |
| use crate::context::{ |
| CounterContext, DualStateContext, FrameContext, InstantContext, RecvFrameContext, RngContext, |
| TimerContext, |
| }; |
| use crate::data_structures::{IdMap, IdMapCollectionKey}; |
| use crate::device::ethernet::{ |
| EthernetDeviceState, EthernetDeviceStateBuilder, EthernetLinkDevice, EthernetTimerId, |
| }; |
| use crate::device::link::LinkDevice; |
| use crate::device::ndp::{NdpHandler, NdpPacketHandler}; |
| use crate::ip::gmp::igmp::{IgmpGroupState, IgmpPacketHandler}; |
| use crate::ip::gmp::mld::{MldGroupState, MldPacketHandler}; |
| use crate::ip::gmp::MulticastGroupSet; |
| use crate::ip::socket::IpSockUpdate; |
| use crate::wire::icmp::{mld::MldPacket, ndp::NdpPacket}; |
| use crate::{BufferDispatcher, Context, EventDispatcher, Instant, StackState}; |
| |
| /// 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 context provided by the device layer to a particular IP device |
| /// implementation. |
| pub(crate) trait IpDeviceContext<D: LinkDevice, TimerId, State>: |
| DeviceIdContext<D> |
| + NdpHandler<D> |
| + CounterContext |
| + RngContext |
| + DualStateContext< |
| IpLinkDeviceState<<Self as InstantContext>::Instant, State>, |
| <Self as RngContext>::Rng, |
| <Self as DeviceIdContext<D>>::DeviceId, |
| > + TimerContext<TimerId> |
| + FrameContext<EmptyBuf, <Self as DeviceIdContext<D>>::DeviceId> |
| + FrameContext<Buf<Vec<u8>>, <Self as DeviceIdContext<D>>::DeviceId> |
| { |
| /// Is `device` currently operating as a router? |
| /// |
| /// Returns `true` if both the `device` has routing enabled AND the netstack |
| /// is configured to route packets not destined for it; returns `false` |
| /// otherwise. |
| fn is_router_device<I: Ip>(&self, device: <Self as DeviceIdContext<D>>::DeviceId) -> bool; |
| |
| /// Is `device` usable? |
| /// |
| /// That is, is it either initializing or initialized? |
| fn is_device_usable(&self, device: <Self as DeviceIdContext<D>>::DeviceId) -> bool; |
| } |
| |
| impl<D: EventDispatcher> |
| IpDeviceContext< |
| EthernetLinkDevice, |
| EthernetTimerId<EthernetDeviceId>, |
| EthernetDeviceState<D::Instant>, |
| > for Context<D> |
| { |
| fn is_router_device<I: Ip>(&self, device: EthernetDeviceId) -> bool { |
| is_router_device::<_, I>(self, device.into()) |
| } |
| |
| fn is_device_usable(&self, device: EthernetDeviceId) -> bool { |
| is_device_usable(self.state(), device.into()) |
| } |
| } |
| |
| /// `IpDeviceContext` with an extra `B: BufferMut` parameter. |
| /// |
| /// `BufferIpDeviceContext` is used when sending a frame is required. |
| trait BufferIpDeviceContext<D: LinkDevice, TimerId, State, B: BufferMut>: |
| IpDeviceContext<D, TimerId, State> |
| + FrameContext<B, <Self as DeviceIdContext<D>>::DeviceId> |
| + RecvFrameContext<B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv4>> |
| + RecvFrameContext<B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv6>> |
| { |
| } |
| |
| impl< |
| D: LinkDevice, |
| TimerId, |
| State, |
| B: BufferMut, |
| C: IpDeviceContext<D, TimerId, State> |
| + FrameContext<B, <Self as DeviceIdContext<D>>::DeviceId> |
| + RecvFrameContext<B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv4>> |
| + RecvFrameContext<B, RecvIpFrameMeta<<Self as DeviceIdContext<D>>::DeviceId, Ipv6>>, |
| > BufferIpDeviceContext<D, TimerId, State, B> for C |
| { |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> |
| RecvFrameContext<B, RecvIpFrameMeta<EthernetDeviceId, Ipv4>> for Context<D> |
| { |
| fn receive_frame(&mut self, metadata: RecvIpFrameMeta<EthernetDeviceId, Ipv4>, frame: B) { |
| crate::ip::receive_ipv4_packet(self, metadata.device.into(), metadata.frame_dst, frame); |
| } |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> |
| RecvFrameContext<B, RecvIpFrameMeta<EthernetDeviceId, Ipv6>> for Context<D> |
| { |
| fn receive_frame(&mut self, metadata: RecvIpFrameMeta<EthernetDeviceId, Ipv6>, frame: B) { |
| crate::ip::receive_ipv6_packet(self, metadata.device.into(), metadata.frame_dst, frame); |
| } |
| } |
| |
| impl<D: EventDispatcher> |
| DualStateContext< |
| IpLinkDeviceState<D::Instant, EthernetDeviceState<D::Instant>>, |
| D::Rng, |
| EthernetDeviceId, |
| > for Context<D> |
| { |
| fn get_states_with( |
| &self, |
| id0: EthernetDeviceId, |
| _id1: (), |
| ) -> (&IpLinkDeviceState<D::Instant, EthernetDeviceState<D::Instant>>, &D::Rng) { |
| (self.state().device.ethernet.get(id0.0).unwrap().device(), self.dispatcher().rng()) |
| } |
| |
| fn get_states_mut_with( |
| &mut self, |
| id0: EthernetDeviceId, |
| _id1: (), |
| ) -> (&mut IpLinkDeviceState<D::Instant, EthernetDeviceState<D::Instant>>, &mut D::Rng) { |
| let (state, dispatcher) = self.state_and_dispatcher(); |
| (state.device.ethernet.get_mut(id0.0).unwrap().device_mut(), dispatcher.rng_mut()) |
| } |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> FrameContext<B, EthernetDeviceId> for Context<D> { |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| device: EthernetDeviceId, |
| frame: S, |
| ) -> Result<(), S> { |
| self.dispatcher_mut().send_frame(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 From<EthernetDeviceId> for DeviceId { |
| fn from(id: EthernetDeviceId) -> DeviceId { |
| DeviceId::new_ethernet(id.0) |
| } |
| } |
| |
| impl<D: EventDispatcher> DeviceIdContext<EthernetLinkDevice> for Context<D> { |
| 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_timeout<D: EventDispatcher>(ctx: &mut Context<D>, id: DeviceLayerTimerId) { |
| match id.0 { |
| DeviceLayerTimerIdInner::Ethernet(id) => ethernet::handle_timer(ctx, id), |
| } |
| } |
| |
| /// An ID identifying a device. |
| #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
| pub struct DeviceId { |
| id: usize, |
| protocol: DeviceProtocol, |
| } |
| |
| impl From<usize> for DeviceId { |
| fn from(id: usize) -> DeviceId { |
| DeviceId::new_ethernet(id) |
| } |
| } |
| |
| impl From<DeviceId> for usize { |
| fn from(id: DeviceId) -> usize { |
| id.id |
| } |
| } |
| |
| impl DeviceId { |
| /// Construct a new `DeviceId` for an Ethernet device. |
| pub(crate) fn new_ethernet(id: usize) -> DeviceId { |
| DeviceId { id, protocol: DeviceProtocol::Ethernet } |
| } |
| |
| /// Get the protocol-specific ID for this `DeviceId`. |
| pub fn id(self) -> usize { |
| self.id |
| } |
| |
| /// Get the protocol for this `DeviceId`. |
| pub fn protocol(self) -> DeviceProtocol { |
| self.protocol |
| } |
| } |
| |
| impl Display for DeviceId { |
| fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { |
| write!(f, "{}:{}", self.protocol, self.id) |
| } |
| } |
| |
| 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 = 1; |
| |
| fn get_variant(&self) -> usize { |
| match self.protocol { |
| DeviceProtocol::Ethernet => 0, |
| } |
| } |
| |
| fn get_id(&self) -> usize { |
| self.id as usize |
| } |
| } |
| |
| /// Type of device protocol. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub enum DeviceProtocol { |
| Ethernet, |
| } |
| |
| impl Display for DeviceProtocol { |
| fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { |
| write!( |
| f, |
| "{}", |
| match self { |
| DeviceProtocol::Ethernet => "Ethernet", |
| } |
| ) |
| } |
| } |
| |
| // 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 |
| } |
| } |
| |
| /// Builder for a [`DeviceLayerState`]. |
| #[derive(Clone)] |
| pub struct DeviceStateBuilder { |
| /// Default values for NDP's configurations for new interfaces. |
| /// |
| /// See [`ndp::NdpConfigurations`]. |
| default_ndp_configs: ndp::NdpConfigurations, |
| } |
| |
| impl Default for DeviceStateBuilder { |
| fn default() -> Self { |
| Self { default_ndp_configs: ndp::NdpConfigurations::default() } |
| } |
| } |
| |
| impl DeviceStateBuilder { |
| /// Set the default values for NDP's configurations for new interfaces. |
| /// |
| /// See [`ndp::NdpConfigurations`] for more details. |
| pub fn set_default_ndp_configs(&mut self, v: ndp::NdpConfigurations) { |
| self.default_ndp_configs = v; |
| } |
| |
| /// Build the [`DeviceLayerState`]. |
| pub(crate) fn build<I: Instant>(self) -> DeviceLayerState<I> { |
| DeviceLayerState { ethernet: IdMap::new(), default_ndp_configs: self.default_ndp_configs } |
| } |
| } |
| |
| /// The state associated with the device layer. |
| pub(crate) struct DeviceLayerState<I: Instant> { |
| ethernet: IdMap<DeviceState<IpLinkDeviceState<I, EthernetDeviceState<I>>>>, |
| default_ndp_configs: ndp::NdpConfigurations, |
| } |
| |
| 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: Mac, mtu: u32) -> DeviceId { |
| let mut builder = EthernetDeviceStateBuilder::new(mac, mtu); |
| builder.set_ndp_configs(self.default_ndp_configs.clone()); |
| let ethernet_state = DeviceState::new(IpLinkDeviceState::new(builder.build())); |
| let id = self.ethernet.push(ethernet_state); |
| debug!("adding Ethernet device with ID {} and MTU {}", id, mtu); |
| DeviceId::new_ethernet(id) |
| } |
| } |
| |
| /// Initialization status of a device. |
| #[derive(Debug, PartialEq, Eq)] |
| enum InitializationStatus { |
| /// The device is not yet initialized and MUST NOT be used. |
| Uninitialized, |
| |
| /// The device is currently being initialized and must only be used by |
| /// the initialization methods. |
| Initializing, |
| |
| /// The device is initialized and can operate as normal. |
| Initialized, |
| } |
| |
| impl Default for InitializationStatus { |
| fn default() -> InitializationStatus { |
| InitializationStatus::Uninitialized |
| } |
| } |
| |
| /// Common state across devices. |
| #[derive(Default)] |
| struct CommonDeviceState { |
| /// The device's initialization status. |
| initialization_status: InitializationStatus, |
| } |
| |
| impl CommonDeviceState { |
| fn is_initialized(&self) -> bool { |
| self.initialization_status == InitializationStatus::Initialized |
| } |
| |
| fn is_uninitialized(&self) -> bool { |
| self.initialization_status == InitializationStatus::Uninitialized |
| } |
| } |
| |
| /// Device state. |
| /// |
| /// `D` is the device-specific state. |
| struct DeviceState<D> { |
| /// Device-independant state. |
| common: CommonDeviceState, |
| |
| /// Device-specific state. |
| device: D, |
| } |
| |
| impl<D> DeviceState<D> { |
| /// Create a new `DeviceState` with a device-specific state `device`. |
| pub(crate) fn new(device: D) -> Self { |
| Self { common: CommonDeviceState::default(), device } |
| } |
| |
| /// Get a reference to the common (device-independant) state. |
| pub(crate) fn common(&self) -> &CommonDeviceState { |
| &self.common |
| } |
| |
| /// Get a mutable reference to the common (device-independant) state. |
| pub(crate) fn common_mut(&mut self) -> &mut CommonDeviceState { |
| &mut self.common |
| } |
| |
| /// Get a reference to the inner (device-specific) state. |
| pub(crate) fn device(&self) -> &D { |
| &self.device |
| } |
| |
| /// Get a mutable reference to the inner (device-specific) state. |
| pub(crate) fn device_mut(&mut self) -> &mut D { |
| &mut self.device |
| } |
| } |
| |
| /// Generic IP-Device state. |
| // TODO(ghanan): Split this up into IPv4 and IPv6 specific device states. |
| pub(crate) struct IpDeviceState<I: Instant> { |
| /// Assigned IPv4 addresses. |
| // TODO(ghanan): Use `AddrSubnet` instead of `AddressEntry` as IPv4 addresses do not |
| // need the extra fields in `AddressEntry`. |
| ipv4_addr_sub: Vec<AddressEntry<Ipv4Addr, I>>, |
| |
| /// Assigned IPv6 addresses. |
| /// |
| /// May be tentative (performing NDP's Duplicate Address Detection). |
| ipv6_addr_sub: Vec<AddressEntry<Ipv6Addr, I>>, |
| |
| /// IPv4 multicast groups this device has joined. |
| ipv4_multicast_groups: MulticastGroupSet<Ipv4Addr, IgmpGroupState<I>>, |
| |
| /// Is IGMP enabled for this device? |
| /// |
| /// If `igmp_enabled` is false, multicast groups will still be added to |
| /// `ipv4_multicast_groups`, but we will not inform the network of our |
| /// membership in those groups using IGMP. |
| igmp_enabled: bool, |
| |
| /// IPv6 multicast groups this device has joined. |
| ipv6_multicast_groups: MulticastGroupSet<Ipv6Addr, MldGroupState<I>>, |
| |
| /// Is MLD enabled for this device? |
| /// |
| /// If `mld_enabled` is false, multicast groups will still be added to |
| /// `ipv6_multicast_groups`, but we will not inform the network of our |
| /// membership in those groups using MLD. |
| mld_enabled: bool, |
| |
| /// Default hop limit for new IPv6 packets sent from this device. |
| // TODO(ghanan): Once we separate out device-IP state from device-specific |
| // state, move this to some IPv6-device state. |
| ipv6_hop_limit: NonZeroU8, |
| |
| /// A flag indicating whether routing of IPv4 packets not destined for this device is |
| /// enabled. |
| /// |
| /// This flag controls whether or not packets can be routed from this device. That is, when a |
| /// packet arrives at a device it is not destined for, the packet can only be routed if the |
| /// device it arrived at has routing enabled and there exists another device that has a path |
| /// to the packet's destination, regardless the other device's routing ability. |
| /// |
| /// Default: `false`. |
| route_ipv4: bool, |
| |
| /// A flag indicating whether routing of IPv6 packets not destined for this device is |
| /// enabled. |
| /// |
| /// This flag controls whether or not packets can be routed from this device. That is, when a |
| /// packet arrives at a device it is not destined for, the packet can only be routed if the |
| /// device it arrived at has routing enabled and there exists another device that has a path |
| /// to the packet's destination, regardless the other device's routing ability. |
| /// |
| /// Default: `false`. |
| route_ipv6: bool, |
| } |
| |
| impl<I: Instant> Default for IpDeviceState<I> { |
| fn default() -> IpDeviceState<I> { |
| IpDeviceState { |
| ipv4_addr_sub: Vec::new(), |
| ipv6_addr_sub: Vec::new(), |
| ipv4_multicast_groups: MulticastGroupSet::default(), |
| igmp_enabled: false, |
| ipv6_multicast_groups: MulticastGroupSet::default(), |
| mld_enabled: false, |
| ipv6_hop_limit: ndp::HOP_LIMIT_DEFAULT, |
| route_ipv4: false, |
| route_ipv6: false, |
| } |
| } |
| } |
| |
| /// State for a link-device that is also an IP device. |
| /// |
| /// `D` is the link-specific state. |
| pub(crate) struct IpLinkDeviceState<I: Instant, D> { |
| ip: IpDeviceState<I>, |
| link: D, |
| } |
| |
| impl<I: Instant, D> IpLinkDeviceState<I, D> { |
| /// Create a new `IpLinkDeviceState` with a link-specific state `link`. |
| pub(crate) fn new(link: D) -> Self { |
| Self { ip: IpDeviceState::default(), link } |
| } |
| |
| /// Get a reference to the ip (link-independant) state. |
| pub(crate) fn ip(&self) -> &IpDeviceState<I> { |
| &self.ip |
| } |
| |
| /// Get a mutable reference to the ip (link-independant) state. |
| pub(crate) fn ip_mut(&mut self) -> &mut IpDeviceState<I> { |
| &mut self.ip |
| } |
| |
| /// Get a reference to the inner (link-specific) state. |
| pub(crate) fn link(&self) -> &D { |
| &self.link |
| } |
| |
| /// Get a mutable reference to the inner (link-specific) state. |
| pub(crate) fn link_mut(&mut self) -> &mut D { |
| &mut self.link |
| } |
| } |
| |
| /// The various states an IP address can be on an interface. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum AddressState { |
| /// The address is assigned to an interface and can be considered |
| /// bound to it (all packets destined to the address will be |
| /// accepted). |
| Assigned, |
| |
| /// The address is considered unassigned to an interface for normal |
| /// operations, but has the intention of being assigned in the future |
| /// (e.g. once NDP's Duplicate Address Detection is completed). |
| Tentative, |
| |
| /// The address is considered deprecated on an interface. Existing |
| /// connections using the address will be fine, however new connections |
| /// should not use the deprecated address. |
| Deprecated, |
| } |
| |
| impl AddressState { |
| /// Is this address assigned? |
| pub(crate) fn is_assigned(self) -> bool { |
| self == AddressState::Assigned |
| } |
| |
| /// Is this address tentative? |
| pub(crate) fn is_tentative(self) -> bool { |
| self == AddressState::Tentative |
| } |
| |
| /// Is this address deprecated? |
| pub(crate) fn is_deprecated(self) -> bool { |
| self == AddressState::Deprecated |
| } |
| } |
| |
| /// The type of address configuraion. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) enum AddressConfigurationType { |
| /// Configured by stateless address autoconfiguration. |
| Slaac, |
| |
| /// Manually configured. |
| Manual, |
| } |
| |
| /// Data associated with an IP addressess on an interface. |
| #[derive(Clone)] |
| #[cfg_attr(test, derive(Debug, Eq, PartialEq))] |
| pub struct AddressEntry<S: IpAddress, Instant, A: Witness<S> = SpecifiedAddr<S>> { |
| addr_sub: AddrSubnet<S, A>, |
| state: AddressState, |
| configuration_type: AddressConfigurationType, |
| valid_until: Option<Instant>, |
| } |
| |
| impl<S: IpAddress, Instant, A: Witness<S>> AddressEntry<S, Instant, A> { |
| pub(crate) fn new( |
| addr_sub: AddrSubnet<S, A>, |
| state: AddressState, |
| configuration_type: AddressConfigurationType, |
| valid_until: Option<Instant>, |
| ) -> Self { |
| Self { addr_sub, state, configuration_type, valid_until } |
| } |
| |
| pub(crate) fn addr_sub(&self) -> &AddrSubnet<S, A> { |
| &self.addr_sub |
| } |
| |
| pub(crate) fn state(&self) -> AddressState { |
| self.state |
| } |
| |
| pub(crate) fn configuration_type(&self) -> AddressConfigurationType { |
| self.configuration_type |
| } |
| |
| pub(crate) fn mark_permanent(&mut self) { |
| self.state = AddressState::Assigned; |
| } |
| } |
| |
| impl<S: IpAddress, Instant: Copy, A: Witness<S>> AddressEntry<S, Instant, A> { |
| pub(crate) fn valid_until(&self) -> Option<Instant> { |
| self.valid_until |
| } |
| } |
| |
| /// Possible return values during an erroneous interface address change operation. |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| pub enum AddressError { |
| AlreadyExists, |
| NotFound, |
| } |
| |
| /// 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. |
| /// |
| /// Note, until `device` has been initialized, the netstack promises to not |
| /// send any outbound traffic to it. See [`initialize_device`] for more |
| /// information. |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| device: DeviceId, |
| frame: S, |
| ) -> Result<(), S>; |
| } |
| |
| /// Is `device` usable? |
| /// |
| /// That is, is it either initializing or initialized? |
| pub(crate) fn is_device_usable<D: EventDispatcher>( |
| state: &StackState<D>, |
| device: DeviceId, |
| ) -> bool { |
| !get_common_device_state(state, device).is_uninitialized() |
| } |
| |
| /// Is `device` initialized? |
| pub(crate) fn is_device_initialized<D: EventDispatcher>( |
| state: &StackState<D>, |
| device: DeviceId, |
| ) -> bool { |
| get_common_device_state(state, device).is_initialized() |
| } |
| |
| /// Initialize a device. |
| /// |
| /// `initialize_device` will start soliciting IPv6 routers on the link if `device` is configured to |
| /// be a host. If it is configured to be an advertising interface, it will start sending periodic |
| /// router advertisements. |
| /// |
| /// `initialize_device` MUST be called after adding the device to the netstack. A device MUST NOT |
| /// be used until it has been initialized. |
| /// |
| /// This initialize step is kept separated from the device creation/allocation step so that |
| /// implementations have a chance to do some work (such as updating implementation specific IDs or |
| /// state, configure the device or driver, etc.) before the device is actually initialized and used |
| /// by this netstack. |
| /// |
| /// See [`StackState::add_ethernet_device`] for information about adding ethernet devices. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is already initialized. |
| pub fn initialize_device<D: EventDispatcher>(ctx: &mut Context<D>, device: DeviceId) { |
| let state = get_common_device_state_mut(ctx.state_mut(), device); |
| |
| // `device` must currently be uninitialized. |
| assert!(state.is_uninitialized()); |
| |
| state.initialization_status = InitializationStatus::Initializing; |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => ethernet::initialize_device(ctx, device.id.into()), |
| } |
| |
| // All nodes should join the all-nodes multicast group. |
| join_ip_multicast(ctx, device, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS); |
| |
| if self::is_router_device::<_, Ipv6>(ctx, device) { |
| // If the device is operating as a router, and it is configured to be an advertising |
| // interface, start sending periodic router advertisements. |
| if get_ndp_configurations(ctx, device) |
| .get_router_configurations() |
| .get_should_send_advertisements() |
| { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::start_advertising_interface( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| } else { |
| // RFC 4861 section 6.3.7, it implies only a host sends router solicitation messages. |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::start_soliciting_routers( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| |
| get_common_device_state_mut(ctx.state_mut(), device).initialization_status = |
| InitializationStatus::Initialized; |
| } |
| |
| /// Remove a device from the device layer. |
| /// |
| /// This function returns frames for the bindings to send if the shutdown is graceful - they can be |
| /// safely ignored otherwise. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` does not refer to an existing device. |
| pub fn remove_device<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| ) -> Option<Vec<usize>> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| // TODO(rheacock): Generate any final frames to send here. |
| crate::device::ethernet::deinitialize(ctx, device.id.into()); |
| ctx.state_mut() |
| .device |
| .ethernet |
| .remove(device.id) |
| .unwrap_or_else(|| panic!("no such Ethernet device: {}", device.id)); |
| debug!("removing Ethernet device with ID {}", device.id); |
| None |
| } |
| } |
| } |
| |
| /// List the device IDs of all devices that exist and are initialized. |
| pub(crate) fn list_devices<'a, D: EventDispatcher>( |
| ctx: &'a Context<D>, |
| ) -> impl 'a + Iterator<Item = DeviceId> { |
| // NOTE(joshlf): This unused closure exists so that any modifications to the |
| // `DeviceProtocol` enum will cause this function to stop compiling. This is |
| // to call out the fact that, when the `DeviceProtocol` enum is updated, |
| // this function needs to be updated too! |
| let _ = |proto: DeviceProtocol| match proto { |
| DeviceProtocol::Ethernet => (), |
| }; |
| |
| // UPDATE ME when `DeviceProtocol` enum changes! |
| ctx.state().device.ethernet.iter().filter_map(|(id, state)| { |
| if state.common.is_initialized() { |
| Some(DeviceId::new_ethernet(id)) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| /// Send an IP packet in a device layer 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 device layer frame and sends it. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized. |
| pub(crate) fn send_ip_frame<B: BufferMut, D: BufferDispatcher<B>, A, S>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| local_addr: SpecifiedAddr<A>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| A: IpAddress, |
| S: Serializer<Buffer = B>, |
| { |
| // `device` must not be uninitialized. |
| assert!(is_device_usable(ctx.state(), device)); |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::send_ip_frame(ctx, device.id.into(), local_addr, body) |
| } |
| } |
| } |
| |
| /// Receive a device layer frame from the network. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized. |
| pub fn receive_frame<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| buffer: B, |
| ) { |
| // `device` must be initialized. |
| assert!(is_device_initialized(ctx.state(), device)); |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::receive_frame(ctx, device.id.into(), buffer), |
| } |
| } |
| |
| /// 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| enabled: bool, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::set_promiscuous_mode(ctx, device.id.into(), enabled) |
| } |
| } |
| } |
| |
| /// Get a single IP address and subnet 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. |
| pub(crate) fn get_ip_addr_subnet<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> Option<AddrSubnet<A>> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_subnet(ctx, device.id.into()), |
| } |
| } |
| |
| /// Get the IP address and subnet pairs 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. |
| pub fn get_assigned_ip_addr_subnets<'a, D: EventDispatcher, A: IpAddress>( |
| ctx: &'a Context<D>, |
| device: DeviceId, |
| ) -> impl 'a + Iterator<Item = AddrSubnet<A>> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::get_assigned_ip_addr_subnets(ctx, device.id.into()) |
| } |
| } |
| } |
| |
| /// Get the IP address/subnet pairs associated with this device, including |
| /// tentative and deprecated addresses. |
| /// |
| /// Returns an [`Iterator`] of `AddressEntry<A, D::Instant>`. |
| pub fn get_ip_addr_subnets<'a, D: EventDispatcher, A: IpAddress>( |
| ctx: &'a Context<D>, |
| device: DeviceId, |
| ) -> impl 'a + Iterator<Item = &'a AddressEntry<A, D::Instant>> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_subnets(ctx, device.id.into()), |
| } |
| } |
| |
| /// Get the state of an address on device. |
| /// |
| /// Returns `None` if `addr` is not associated with `device`. |
| pub(crate) fn get_ip_addr_state<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| addr: &SpecifiedAddr<A>, |
| ) -> Option<AddressState> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_state(ctx, device.id.into(), addr), |
| } |
| } |
| |
| /// Checks if `addr` is a local address |
| pub(crate) fn is_local_addr<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| addr: &SpecifiedAddr<A>, |
| ) -> bool { |
| // TODO(brunodalbo) this is a total hack just to enable UDP sockets in |
| // bindings. |
| let device_ids: Vec<_> = ctx.state.device.ethernet.iter().map(|(k, _)| k).collect(); |
| device_ids.into_iter().any(|id| { |
| self::ethernet::get_ip_addr_state(ctx, id.into(), addr) |
| .map(|s| s.is_assigned()) |
| .unwrap_or(false) |
| }) |
| } |
| |
| /// 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). |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized. |
| #[specialize_ip_address] |
| pub fn add_ip_addr_subnet<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| addr_sub: AddrSubnet<A>, |
| ) -> Result<(), AddressError> { |
| // `device` must be initialized. |
| assert!(is_device_initialized(ctx.state(), device)); |
| |
| trace!("add_ip_addr_subnet: adding addr {:?} to device {:?}", addr_sub, device); |
| |
| let res = match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::add_ip_addr_subnet(ctx, device.id.into(), addr_sub) |
| } |
| }; |
| |
| if res.is_ok() { |
| #[ipv4addr] |
| crate::ip::socket::apply_ipv4_socket_update(ctx, IpSockUpdate::new()); |
| #[ipv6addr] |
| crate::ip::socket::apply_ipv6_socket_update(ctx, IpSockUpdate::new()); |
| } |
| |
| res |
| } |
| |
| /// Removes an IP address and associated subnet to this device. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized. |
| #[specialize_ip_address] |
| pub fn del_ip_addr<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| addr: &SpecifiedAddr<A>, |
| ) -> Result<(), AddressError> { |
| // `device` must be initialized. |
| assert!(is_device_initialized(ctx.state(), device)); |
| |
| trace!("del_ip_addr: removing addr {:?} from device {:?}", addr, device); |
| |
| let res = match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::del_ip_addr(ctx, device.id.into(), addr), |
| }; |
| |
| if res.is_ok() { |
| #[ipv4addr] |
| crate::ip::socket::apply_ipv4_socket_update(ctx, IpSockUpdate::new()); |
| #[ipv6addr] |
| crate::ip::socket::apply_ipv6_socket_update(ctx, IpSockUpdate::new()); |
| } |
| |
| res |
| } |
| |
| /// Add `device` to a multicast group `multicast_addr`. |
| /// |
| /// Calling `join_ip_multicast` with the same `device` 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` 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` 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. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized. |
| pub(crate) fn join_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| // `device` must not be uninitialized. |
| assert!(is_device_usable(ctx.state(), device)); |
| |
| trace!("join_ip_multicast: device {:?} joining multicast {:?}", device, multicast_addr); |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::join_ip_multicast(ctx, device.id.into(), multicast_addr) |
| } |
| } |
| } |
| |
| /// Attempt to remove `device` from a multicast group `multicast_addr`. |
| /// |
| /// `leave_ip_multicast` will attempt to remove `device` from a multicast group `multicast_addr`. |
| /// `device` may have "joined" the same multicast address multiple times, so `device` 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` |
| /// will still be in the multicast group until the next (final) call to `leave_ip_multicast`. |
| /// |
| /// # Panics |
| /// |
| /// Panics if `device` is not initialized or `device` is not currently in the multicast group. |
| // TODO(joshlf): remove `allow(dead_code)` when this is used. |
| #[cfg_attr(not(test), allow(dead_code))] |
| pub(crate) fn leave_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| multicast_addr: MulticastAddr<A>, |
| ) { |
| // `device` must not be uninitialized. |
| assert!(is_device_usable(ctx.state(), device)); |
| |
| trace!("join_ip_multicast: device {:?} leaving multicast {:?}", device, multicast_addr); |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::leave_ip_multicast(ctx, device.id.into(), multicast_addr) |
| } |
| } |
| } |
| |
| /// Is `device` part of the IP multicast group `multicast_addr`. |
| pub(crate) fn is_in_ip_multicast<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| multicast_addr: MulticastAddr<A>, |
| ) -> bool { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::is_in_ip_multicast(ctx, device.id.into(), multicast_addr) |
| } |
| } |
| } |
| |
| /// Get the MTU associated with this device. |
| pub(crate) fn get_mtu<D: EventDispatcher>(ctx: &Context<D>, device: DeviceId) -> u32 { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_mtu(ctx, device.id.into()), |
| } |
| } |
| |
| /// Get the hop limit for new IPv6 packets that will be sent out from `device`. |
| pub(crate) fn get_ipv6_hop_limit<D: EventDispatcher>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> NonZeroU8 { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_ipv6_hop_limit(ctx, device.id.into()), |
| } |
| } |
| |
| /// Gets the IPv6 link-local address associated with this device. |
| // TODO(brunodalbo) when our device model allows for multiple IPs we can have |
| // a single function go get all the IP addresses associated with a device, which |
| // would be cleaner and remove the need for this function. |
| pub fn get_ipv6_link_local_addr<D: EventDispatcher>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> Option<LinkLocalAddr<Ipv6Addr>> { |
| match device.protocol { |
| DeviceProtocol::Ethernet => self::ethernet::get_ipv6_link_local_addr(ctx, device.id.into()), |
| } |
| } |
| |
| /// Determine if an IP Address is considered tentative on a device. |
| /// |
| /// Returns `true` if the address is tentative on a device; `false` otherwise. |
| /// Note, if the `addr` is not assigned to `device` but is considered tentative |
| /// on another device, `is_addr_tentative_on_device` will return `false`. |
| pub(crate) fn is_addr_tentative_on_device<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| addr: &SpecifiedAddr<A>, |
| device: DeviceId, |
| ) -> bool { |
| get_ip_addr_state::<_, A>(ctx, device, addr).map_or(false, |x| x.is_tentative()) |
| } |
| |
| /// Get a reference to the common device state for a `device`. |
| fn get_common_device_state<D: EventDispatcher>( |
| state: &StackState<D>, |
| device: DeviceId, |
| ) -> &CommonDeviceState { |
| match device.protocol { |
| DeviceProtocol::Ethernet => state |
| .device |
| .ethernet |
| .get(device.id) |
| .unwrap_or_else(|| panic!("no such Ethernet device: {}", device.id)) |
| .common(), |
| } |
| } |
| |
| /// Get a mutable reference to the common device state for a `device`. |
| fn get_common_device_state_mut<D: EventDispatcher>( |
| state: &mut StackState<D>, |
| device: DeviceId, |
| ) -> &mut CommonDeviceState { |
| match device.protocol { |
| DeviceProtocol::Ethernet => state |
| .device |
| .ethernet |
| .get_mut(device.id) |
| .unwrap_or_else(|| panic!("no such Ethernet device: {}", device.id)) |
| .common_mut(), |
| } |
| } |
| |
| /// Is IP packet routing enabled on `device`? |
| /// |
| /// 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(crate) fn is_routing_enabled<D: EventDispatcher, I: Ip>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> bool { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::is_routing_enabled::<_, I>(ctx, device.id.into()) |
| } |
| } |
| } |
| |
| /// Enables or disables IP packet routing on `device`. |
| /// |
| /// `set_routing_enabled` does nothing if the new routing status, `enabled`, is the same as |
| /// the current routing status. |
| /// |
| /// Note, enabling routing does not mean that `device` will immediately start 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. |
| // TODO(joshlf): remove `allow(dead_code)` when this is used. |
| #[cfg_attr(not(test), allow(dead_code))] |
| pub(crate) fn set_routing_enabled<D: EventDispatcher, I: Ip>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| enabled: bool, |
| ) { |
| if crate::device::is_routing_enabled::<_, I>(ctx, device) == enabled { |
| trace!( |
| "set_routing_enabled: {:?} routing status unchanged for device {:?}", |
| I::VERSION, |
| device |
| ); |
| return; |
| } |
| |
| match I::VERSION { |
| IpVersion::V4 => set_ipv4_routing_enabled(ctx, device, enabled), |
| IpVersion::V6 => set_ipv6_routing_enabled(ctx, device, enabled), |
| } |
| } |
| |
| /// Sets IPv4 routing on `device`. |
| fn set_ipv4_routing_enabled<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| enabled: bool, |
| ) { |
| if enabled { |
| trace!("set_ipv4_routing_enabled: enabling IPv4 routing for device {:?}", device); |
| } else { |
| trace!("set_ipv4_routing_enabled: disabling IPv4 routing for device {:?}", device); |
| } |
| |
| set_routing_enabled_inner::<_, Ipv4>(ctx, device, enabled); |
| } |
| |
| /// Sets IPv6 routing on `device`. |
| /// |
| /// If the `device` transitions from a router -> host or host -> router, periodic router |
| /// advertisements will be stopped or started, and router solicitations will be started or stopped, |
| /// depending on `device`'s current and new router state. |
| fn set_ipv6_routing_enabled<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| enabled: bool, |
| ) { |
| let ip_routing = crate::ip::is_routing_enabled::<_, Ipv6>(ctx); |
| |
| if enabled { |
| trace!("set_ipv6_routing_enabled: enabling IPv6 routing for device {:?}", device); |
| |
| // Make sure that the netstack is configured to route packets before considering this |
| // device a router and stopping router solicitations. If the netstack was not configured |
| // to route packets before, then we would still be considered a host, so we shouldn't |
| // stop soliciting routers. |
| if ip_routing { |
| // TODO(ghanan): Handle transition from disabled to enabled: |
| // - start periodic router advertisements (if configured to do so) |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::stop_soliciting_routers( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| |
| // Actually update the routing flag. |
| set_routing_enabled_inner::<_, Ipv6>(ctx, device, true); |
| |
| // Make sure that the netstack is configured to route packets before considering this |
| // device a router and starting periodic router advertisements. |
| if ip_routing { |
| // Now that `device` is a router, join the all-routers multicast group. |
| join_ip_multicast(ctx, device, Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS); |
| |
| // If `device` has a link-local address, and is configured to be an advertising |
| // interface, start advertising. |
| if get_ipv6_link_local_addr(ctx, device).is_some() |
| && get_ndp_configurations(ctx, device) |
| .get_router_configurations() |
| .get_should_send_advertisements() |
| { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::start_advertising_interface( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| } |
| } else { |
| trace!("set_ipv6_routing_enabled: disabling IPv6 routing for device {:?}", device); |
| |
| // Make sure that the netstack is configured to route packets before considering this |
| // device a router and stopping periodic router advertisements. If the netstack was not |
| // configured to route packets before, then we would still be considered a host, so we |
| // wouldn't have any periodic router advertisements to stop. |
| if ip_routing { |
| // Make sure that the device was configured to send advertisements before stopping it. |
| // If it was never configured to stop advertisements, there should be nothing to stop. |
| if get_ipv6_link_local_addr(ctx, device).is_some() |
| && get_ndp_configurations(ctx, device) |
| .get_router_configurations() |
| .get_should_send_advertisements() |
| { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::stop_advertising_interface( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| |
| // Now that `device` is a host, leave the all-routers multicast group. |
| leave_ip_multicast(ctx, device, Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS); |
| } |
| |
| // Actually update the routing flag. |
| set_routing_enabled_inner::<_, Ipv6>(ctx, device, false); |
| |
| // We only need to start soliciting routers if we were not soliciting them before. We |
| // would only reach this point if there was a change in routing status for `device`. |
| // However, if the nestatck does not currently have routing enabled, the device would |
| // not have been considered a router before this routing change on the device, so it |
| // would have already solicited routers. |
| if ip_routing { |
| // On transition from router -> host, start soliciting router information. |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::start_soliciting_routers( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| } |
| } |
| |
| /// Sets the IP packet routing flag on `device`. |
| fn set_routing_enabled_inner<D: EventDispatcher, I: Ip>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| enabled: bool, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::set_routing_enabled_inner::<_, I>(ctx, device.id.into(), enabled) |
| } |
| } |
| } |
| |
| /// Is `device` currently operating as a router? |
| /// |
| /// Returns `true` if both the `device` has routing enabled AND the netstack is configured to |
| /// route packets not destined for it; returns `false` otherwise. |
| pub(crate) fn is_router_device<D: EventDispatcher, I: Ip>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> bool { |
| crate::ip::is_routing_enabled::<_, I>(ctx) |
| && crate::device::is_routing_enabled::<_, I>(ctx, device) |
| } |
| |
| /// 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| addr: Ipv4Addr, |
| mac: Mac, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::insert_static_arp_table_entry(ctx, device.id.into(), 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(crate) fn insert_ndp_table_entry<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| addr: Ipv6Addr, |
| mac: Mac, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| self::ethernet::insert_ndp_table_entry(ctx, device.id.into(), addr, mac) |
| } |
| } |
| } |
| |
| /// Updates the NDP Configurations for a `device`. |
| /// |
| /// Note, some values may not take effect immediately, and may only take effect the next time they |
| /// are used. These scenarios documented below: |
| /// |
| /// - Updates to [`NdpConfiguration::dup_addr_detect_transmits`] will only take effect the next |
| /// time Duplicate Address Detection (DAD) is done. Any DAD processes that have already started |
| /// will continue using the old value. |
| /// |
| /// - Updates to [`NdpConfiguration::max_router_solicitations`] will only take effect the next |
| /// time routers are explicitly solicited. Current router solicitation will continue using the |
| /// old value. |
| // TODO(rheacock): remove `allow(dead_code)` when this is used. |
| #[allow(dead_code)] |
| pub fn set_ndp_configurations<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| configs: ndp::NdpConfigurations, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::set_configurations( |
| ctx, |
| device.id.into(), |
| configs, |
| ) |
| } |
| } |
| } |
| |
| /// Gets the NDP Configurations for a `device`. |
| pub fn get_ndp_configurations<D: EventDispatcher>( |
| ctx: &Context<D>, |
| device: DeviceId, |
| ) -> &ndp::NdpConfigurations { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| <Context<_> as NdpHandler<EthernetLinkDevice>>::get_configurations( |
| ctx, |
| device.id.into(), |
| ) |
| } |
| } |
| } |
| |
| /// 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); |
| |
| impl<T> Tentative<T> { |
| /// Create a new address that is marked as tentative. |
| pub(crate) fn new_tentative(t: T) -> Self { |
| Self(t, true) |
| } |
| |
| /// Create a new address that is marked as permanent/assigned. |
| pub(crate) fn new_permanent(t: T) -> Self { |
| Self(t, false) |
| } |
| |
| /// Returns whether the value is tentative. |
| pub(crate) fn is_tentative(&self) -> bool { |
| self.1 |
| } |
| |
| /// Gets the value that is stored inside. |
| pub(crate) fn into_inner(self) -> T { |
| self.0 |
| } |
| |
| /// Converts a `Tentative<T>` into a `Option<T>` in the way that |
| /// a tentative value corresponds to a `None`. |
| pub(crate) fn try_into_permanent(self) -> Option<T> { |
| if self.is_tentative() { |
| None |
| } else { |
| Some(self.into_inner()) |
| } |
| } |
| } |
| |
| /// This implementation of `NdpPacketHandler` is consumed by ICMPv6. |
| impl<D: EventDispatcher> NdpPacketHandler<DeviceId> for Context<D> { |
| fn receive_ndp_packet<B: ByteSlice>( |
| &mut self, |
| device: DeviceId, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| packet: NdpPacket<B>, |
| ) { |
| trace!("device::receive_ndp_packet"); |
| |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| crate::device::ndp::receive_ndp_packet( |
| self, |
| device.id.into(), |
| src_ip, |
| dst_ip, |
| packet, |
| ); |
| } |
| } |
| } |
| } |
| |
| /// This implementation of `IgmpPacketHandler` is consumed by IPv4. |
| impl<B: BufferMut, D: BufferDispatcher<B>> IgmpPacketHandler<(), DeviceId, B> for Context<D> { |
| fn receive_igmp_packet( |
| &mut self, |
| device: DeviceId, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| buffer: B, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| IgmpPacketHandler::<EthernetLinkDevice, _, _>::receive_igmp_packet( |
| self, |
| device.id.into(), |
| src_ip, |
| dst_ip, |
| buffer, |
| ); |
| } |
| } |
| } |
| } |
| |
| /// This implementation of `MldPacketHandler` is consumed by ICMPv6. |
| impl<D: EventDispatcher> MldPacketHandler<(), DeviceId> for Context<D> { |
| fn receive_mld_packet<B: ByteSlice>( |
| &mut self, |
| device: DeviceId, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| packet: MldPacket<B>, |
| ) { |
| match device.protocol { |
| DeviceProtocol::Ethernet => { |
| MldPacketHandler::<EthernetLinkDevice, _>::receive_mld_packet( |
| self, |
| device.id.into(), |
| src_ip, |
| dst_ip, |
| packet, |
| ); |
| } |
| } |
| } |
| } |