blob: 5667f7ecfa867029be9fdab4cb251e3bb271ccb9 [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 ndp;
use std::fmt::{self, Debug, Display, Formatter};
use std::num::NonZeroU8;
use log::{debug, trace};
use net_types::ethernet::Mac;
use net_types::ip::{AddrSubnet, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
use net_types::{LinkLocalAddr, MulticastAddr, SpecifiedAddr, Witness};
use packet::{BufferMut, Serializer};
use specialize_ip_macro::specialize_ip;
use crate::data_structures::{IdMap, IdMapCollectionKey};
use crate::device::ethernet::{EthernetDeviceState, EthernetDeviceStateBuilder};
use crate::{BufferDispatcher, Context, EventDispatcher, StackState};
/// An ID identifying a device.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct DeviceId {
id: usize,
protocol: DeviceProtocol,
}
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, 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, 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<D: EventDispatcher>(self) -> DeviceLayerState<D> {
DeviceLayerState { ethernet: IdMap::new(), default_ndp_configs: self.default_ndp_configs }
}
}
/// The state associated with the device layer.
pub(crate) struct DeviceLayerState<D: EventDispatcher> {
ethernet: IdMap<DeviceState<EthernetDeviceState<D>>>,
default_ndp_configs: ndp::NdpConfigurations,
}
impl<D: EventDispatcher> DeviceLayerState<D> {
/// 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 mut ethernet_state = DeviceState::new(builder.build());
let id = self.ethernet.push(ethernet_state);
debug!("adding Ethernet device with ID {} and MTU {}", id, mtu);
DeviceId::new_ethernet(id)
}
}
/// Common state across devices.
#[derive(Default)]
pub(crate) struct CommonDeviceState {
/// Is the device initialized?
is_initialized: bool,
}
/// Device state.
///
/// `D` is the device-specific state.
pub(crate) 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
}
}
/// The identifier for timer events in the device layer.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub(crate) enum DeviceLayerTimerId {
/// A timer event in the ARP layer with a protocol type of IPv4
ArpIpv4(arp::ArpTimerId<usize, Ipv4Addr>),
Ndp(ndp::NdpTimerId),
}
impl From<arp::ArpTimerId<usize, Ipv4Addr>> for DeviceLayerTimerId {
fn from(id: arp::ArpTimerId<usize, Ipv4Addr>) -> DeviceLayerTimerId {
DeviceLayerTimerId::ArpIpv4(id)
}
}
/// 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,
}
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
}
}
/// Data associated with an IP addressess on an interface.
pub struct AddressEntry<A: IpAddress> {
addr_sub: AddrSubnet<A>,
state: AddressState,
}
impl<A: IpAddress> AddressEntry<A> {
pub(crate) fn new(addr_sub: AddrSubnet<A>, state: AddressState) -> Self {
Self { addr_sub, state }
}
pub(crate) fn addr_sub(&self) -> &AddrSubnet<A> {
&self.addr_sub
}
pub(crate) fn state(&self) -> AddressState {
self.state
}
pub(crate) fn mark_permanent(&mut self) {
self.state = AddressState::Assigned;
}
}
/// Possible return values during an erroneous interface address change operation.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AddressError {
AlreadyExists,
NotFound,
}
/// Handle a timer event firing in the device layer.
pub(crate) fn handle_timeout<D: EventDispatcher>(ctx: &mut Context<D>, id: DeviceLayerTimerId) {
match id {
DeviceLayerTimerId::ArpIpv4(inner_id) => arp::handle_timer(ctx, inner_id),
DeviceLayerTimerId::Ndp(inner_id) => ndp::handle_timeout(ctx, inner_id),
}
}
/// 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` 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 not already be initialized.
assert!(!state.is_initialized);
state.is_initialized = true;
// All nodes should join the all-nodes multicast group.
join_ip_multicast(ctx, device, MulticastAddr::new(Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS).unwrap());
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 => ndp::start_advertising_interface::<
_,
ethernet::EthernetNdpDevice,
>(ctx, device.id),
}
}
} else {
// RFC 4861 section 6.3.7, it implies only a host sends router solicitation messages.
match device.protocol {
DeviceProtocol::Ethernet => {
ndp::start_soliciting_routers::<_, ethernet::EthernetNdpDevice>(ctx, device.id)
}
}
}
}
/// 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);
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
}
}
}
/// 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 be initialized.
assert!(is_device_initialized(ctx.state(), device));
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::send_ip_frame(ctx, device.id, 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, buffer),
}
}
/// Set the promiscuous mode flag on `device`.
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, 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`.
pub(crate) fn get_ip_addr_subnet<D: EventDispatcher, A: IpAddress>(
state: &StackState<D>,
device: DeviceId,
) -> Option<AddrSubnet<A>> {
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_subnet(state, device.id),
}
}
/// Get the IP addresses and associated subnets 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_subnets`.
///
/// Returns an [`Iterator`] of `AddrSubnet<A>`.
///
/// See [`Tentative`] and [`AddrSubnet`] for more information.
pub fn get_ip_addr_subnets<'a, D: EventDispatcher, A: IpAddress>(
state: &'a StackState<D>,
device: DeviceId,
) -> impl 'a + Iterator<Item = AddrSubnet<A>> {
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_subnets(state, device.id),
}
}
/// Get the IP addresses and associated subnets for a device, including tentative
/// address.
///
/// Returns an [`Iterator`] of `Tentative<AddrSubnet<A>>`.
///
/// See [`Tentative`] and [`AddrSubnet`] for more information.
pub fn get_ip_addr_subnets_with_tentative<'a, D: EventDispatcher, A: IpAddress>(
state: &'a StackState<D>,
device: DeviceId,
) -> impl 'a + Iterator<Item = Tentative<AddrSubnet<A>>> {
match device.protocol {
DeviceProtocol::Ethernet => {
self::ethernet::get_ip_addr_subnets_with_tentative(state, device.id)
}
}
}
/// Get the state of an address on device.
///
/// Returns `None` if `addr` is not associated with `device`.
pub fn get_ip_addr_state<D: EventDispatcher, A: IpAddress>(
state: &StackState<D>,
device: DeviceId,
addr: &SpecifiedAddr<A>,
) -> Option<AddressState> {
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::get_ip_addr_state(state, device.id, addr),
}
}
/// Adds an IP address and associated subnet to this device.
///
/// # Panics
///
/// Panics if `device` is not initialized.
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);
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::add_ip_addr_subnet(ctx, device.id, addr_sub),
}
}
/// Removes an IP address and associated subnet to this device.
///
/// # Panics
///
/// Panics if `device` is not initialized.
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);
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::del_ip_addr(ctx, device.id, addr),
}
}
/// Add `device` to a multicast group `multicast_addr`.
///
/// If `device` is already in the multicast group `multicast_addr`,
/// `join_ip_multicast` does nothing.
///
/// # 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 be initialized.
assert!(is_device_initialized(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, multicast_addr)
}
}
}
/// Remove `device` from a multicast group `multicast_addr`.
///
/// If `device` is not in the multicast group `multicast_addr`,
/// `leave_ip_multicast` does nothing.
///
/// # Panics
///
/// Panics if `device` is not initialized.
pub(crate) fn leave_ip_multicast<D: EventDispatcher, A: IpAddress>(
ctx: &mut Context<D>,
device: DeviceId,
multicast_addr: MulticastAddr<A>,
) {
// `device` must be initialized.
assert!(is_device_initialized(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, 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, multicast_addr)
}
}
}
/// Get the MTU associated with this device.
pub(crate) fn get_mtu<D: EventDispatcher>(state: &StackState<D>, device: DeviceId) -> u32 {
match device.protocol {
DeviceProtocol::Ethernet => self::ethernet::get_mtu(state, device.id),
}
}
/// 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),
}
}
/// 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),
}
}
/// 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>(
state: &StackState<D>,
addr: SpecifiedAddr<A>,
device: DeviceId,
) -> bool {
get_ip_addr_subnets_with_tentative::<_, A>(state, device)
.any(|x| (x.inner().addr() == addr) && 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),
}
}
/// 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.
#[specialize_ip]
pub(crate) fn set_routing_enabled<D: EventDispatcher, I: Ip>(
ctx: &mut Context<D>,
device: DeviceId,
enabled: bool,
) {
// TODO(ghanan): We cannot directly do `I::VERSION` in the `trace!` calls because of a bug in
// specialize_ip_macro where it does not properly replace `I` with `Self`. Once
// this is fixed, change this.
let version = I::VERSION;
if crate::device::is_routing_enabled::<_, I>(ctx, device) == enabled {
trace!(
"set_routing_enabled: {:?} routing status unchanged for device {:?}",
version,
device
);
return;
}
#[ipv4]
set_ipv4_routing_enabled(ctx, device, enabled);
#[ipv6]
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 => {
ndp::stop_soliciting_routers::<_, ethernet::EthernetNdpDevice>(ctx, device.id)
}
}
}
// 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,
MulticastAddr::new(Ipv6::ALL_ROUTERS_LINK_LOCAL_ADDRESS).unwrap(),
);
if get_ndp_configurations(ctx, device)
.get_router_configurations()
.get_should_send_advertisements()
{
match device.protocol {
DeviceProtocol::Ethernet => ndp::start_advertising_interface::<
_,
ethernet::EthernetNdpDevice,
>(ctx, device.id),
}
}
}
} 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_ndp_configurations(ctx, device)
.get_router_configurations()
.get_should_send_advertisements()
{
match device.protocol {
DeviceProtocol::Ethernet => ndp::stop_advertising_interface::<
_,
ethernet::EthernetNdpDevice,
>(ctx, device.id),
}
}
// Now that `device` is a host, leave the all-routers multicast group.
leave_ip_multicast(
ctx,
device,
MulticastAddr::new(Ipv6::ALL_ROUTERS_LINK_LOCAL_ADDRESS).unwrap(),
);
}
// 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 => {
ndp::start_soliciting_routers::<_, ethernet::EthernetNdpDevice>(ctx, device.id)
}
}
}
}
}
/// 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, 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))
}
/// 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.
pub fn set_ndp_configurations<D: EventDispatcher>(
ctx: &mut Context<D>,
device: DeviceId,
configs: ndp::NdpConfigurations,
) {
match device.protocol {
DeviceProtocol::Ethernet => {
ndp::set_ndp_configurations::<_, ethernet::EthernetNdpDevice>(ctx, device.id, 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 => {
ndp::get_ndp_configurations::<_, ethernet::EthernetNdpDevice>(ctx, device.id)
}
}
}
/// 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())
}
}
/// Borrow the content which is stored inside.
pub(crate) fn inner(&self) -> &T {
&self.0
}
/// Similar to `Option::map`.
pub(crate) fn map<U, F>(self, f: F) -> Tentative<U>
where
F: FnOnce(T) -> U,
{
Tentative(f(self.0), self.1)
}
/// Make the tentative value to be permanent.
pub(crate) fn mark_permanent(&mut self) {
self.1 = false
}
}