blob: 4b3aab7db10c0f84c64af9a566eb9fded351eaea [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.
//! A networking stack.
#![cfg_attr(not(fuzz), no_std)]
// In case we roll the toolchain and something we're using as a feature has been
// stabilized.
#![allow(stable_features)]
// Tracking: https://github.com/rust-lang/rust/issues/31844.
#![feature(min_specialization)]
#![deny(missing_docs, unreachable_patterns)]
// Turn off checks for dead code, but only when building for fuzzing or
// benchmarking. This allows fuzzers and benchmarks to be written as part of
// the crate, with access to test utilities, without a bunch of build errors
// due to unused code. These checks are turned back on in the 'fuzz' and
// 'benchmark' modules.
#![cfg_attr(any(fuzz, benchmark), allow(dead_code, unused_imports, unused_macros))]
// TODO(https://github.com/rust-lang-nursery/portability-wg/issues/11): remove
// this module.
extern crate fakealloc as alloc;
// TODO(https://github.com/dtolnay/thiserror/pull/64): remove this module.
#[cfg(not(fuzz))]
extern crate fakestd as std;
#[macro_use]
mod macros;
mod algorithm;
#[cfg(test)]
pub mod benchmarks;
pub mod context;
mod data_structures;
mod device;
pub mod error;
#[cfg(fuzz)]
mod fuzz;
mod ip;
mod socket;
#[cfg(test)]
mod testutil;
mod transport;
use log::trace;
pub use crate::{
data_structures::{Entry, IdMap, IdMapCollection, IdMapCollectionKey},
device::{
add_ethernet_device, add_loopback_device, get_ipv4_configuration, get_ipv6_configuration,
receive_frame, remove_device, update_ipv4_configuration, update_ipv6_configuration,
DeviceId, DeviceLayerEventDispatcher,
},
error::{LocalAddressError, NetstackError, RemoteAddressError, SocketError},
ip::{
device::{
dad::DadEvent,
route_discovery::Ipv6RouteDiscoveryEvent,
slaac::SlaacConfiguration,
state::{IpDeviceConfiguration, Ipv4DeviceConfiguration, Ipv6DeviceConfiguration},
IpAddressState, IpDeviceEvent,
},
forwarding::AddRouteError,
icmp,
socket::{IpSockCreationError, IpSockRouteError, IpSockSendError, IpSockUnroutableError},
AddableEntry, AddableEntryEither, EntryEither, IpDeviceIdContext, IpExt, IpLayerEvent,
Ipv4StateBuilder, Ipv6StateBuilder, TransportIpContext,
},
transport::{
udp::{
connect_udp, create_udp_unbound, get_udp_bound_device, get_udp_conn_info,
get_udp_listener_info, get_udp_posix_reuse_port, listen_udp, remove_udp_conn,
remove_udp_listener, remove_udp_unbound, send_udp, send_udp_conn, send_udp_listener,
set_bound_udp_device, set_udp_multicast_membership, set_udp_posix_reuse_port,
set_unbound_udp_device, BufferUdpContext, BufferUdpStateContext,
BufferUdpStateNonSyncContext, UdpBoundId, UdpConnId, UdpConnInfo, UdpContext,
UdpListenerId, UdpListenerInfo, UdpSendError, UdpSendListenerError,
UdpSockCreationError, UdpSocketId, UdpStateContext, UdpStateNonSyncContext,
UdpUnboundId,
},
TransportStateBuilder,
},
};
use alloc::vec::Vec;
use core::{fmt::Debug, marker::PhantomData, time};
use net_types::{
ip::{AddrSubnetEither, IpAddr, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, SubnetEither},
SpecifiedAddr,
};
use packet::{Buf, BufferMut, EmptyBuf};
use crate::{
context::{CounterContext, EventContext, RngContext, TimerContext},
device::{DeviceLayerState, DeviceLayerTimerId},
ip::{
device::{Ipv4DeviceTimerId, Ipv6DeviceTimerId},
icmp::{BufferIcmpContext, IcmpContext},
IpLayerTimerId, Ipv4State, Ipv6State,
},
transport::{TransportLayerState, TransportLayerTimerId},
};
/// Map an expression over either version of one or more addresses.
///
/// `map_addr_version!` when given a value of a type which is an enum with two
/// variants - `V4` and `V6` - matches on the variants, and for both variants,
/// invokes an expression on the inner contents. `$addr` is both the name of the
/// variable to match on, and the name that the address will be bound to for the
/// scope of the expression.
///
/// `map_addr_version!` when given a list of values and their types (all enums
/// with variants `V4` and `V6`), matches on the tuple of values and invokes the
/// `$match` expression when all values are of the same variant. Otherwise the
/// `$mismatch` expression is invoked.
///
/// To make it concrete, the expression `map_addr_version!(bar: Foo; blah(bar))`
/// desugars to:
///
/// ```rust,ignore
/// match bar {
/// Foo::V4(bar) => blah(bar),
/// Foo::V6(bar) => blah(bar),
/// }
/// ```
///
/// Also,
/// `map_addr_version!((foo: Foo, bar: Bar); blah(foo, bar), unreachable!())`
/// desugars to:
///
/// ```rust,ignore
/// match (foo, bar) {
/// (Foo::V4(foo), Bar::V4(bar)) => blah(foo, bar),
/// (Foo::V6(foo), Bar::V6(bar)) => blah(foo, bar),
/// _ => unreachable!(),
/// }
/// ```
#[macro_export]
macro_rules! map_addr_version {
($addr:ident: $ty:tt; $expr:expr) => {
match $addr {
$ty::V4($addr) => $expr,
$ty::V6($addr) => $expr,
}
};
($addr:ident: $ty:tt; $expr_v4:expr, $expr_v6:expr) => {
match $addr {
$ty::V4($addr) => $expr_v4,
$ty::V6($addr) => $expr_v6,
}
};
(( $( $addr:ident : $ty:tt ),+ ); $match:expr, $mismatch:expr) => {
match ( $( $addr ),+ ) {
( $( $ty::V4( $addr ) ),+ ) => $match,
( $( $ty::V6( $addr ) ),+ ) => $match,
_ => $mismatch,
}
};
(( $( $addr:ident : $ty:tt ),+ ); $match_v4:expr, $match_v6:expr, $mismatch:expr) => {
match ( $( $addr ),+ ) {
( $( $ty::V4( $addr ) ),+ ) => $match_v4,
( $( $ty::V6( $addr ) ),+ ) => $match_v6,
_ => $mismatch,
}
};
(( $( $addr:ident : $ty:tt ),+ ); $match:expr, $mismatch:expr,) => {
map_addr_version!(($( $addr: $ty ),+); $match, $mismatch)
};
}
/// A builder for [`StackState`].
#[derive(Default, Clone)]
pub struct StackStateBuilder {
transport: TransportStateBuilder,
ipv4: Ipv4StateBuilder,
ipv6: Ipv6StateBuilder,
}
impl StackStateBuilder {
/// Get the builder for the transport layer state.
pub fn transport_builder(&mut self) -> &mut TransportStateBuilder {
&mut self.transport
}
/// Get the builder for the IPv4 state.
pub fn ipv4_builder(&mut self) -> &mut Ipv4StateBuilder {
&mut self.ipv4
}
/// Get the builder for the IPv6 state.
pub fn ipv6_builder(&mut self) -> &mut Ipv6StateBuilder {
&mut self.ipv6
}
/// Consume this builder and produce a `StackState`.
pub fn build<I: Instant>(self) -> StackState<I> {
StackState {
transport: self.transport.build(),
ipv4: self.ipv4.build(),
ipv6: self.ipv6.build(),
device: Default::default(),
}
}
}
/// The state associated with the network stack.
pub struct StackState<I: Instant> {
transport: TransportLayerState,
ipv4: Ipv4State<I, DeviceId>,
ipv6: Ipv6State<I, DeviceId>,
device: DeviceLayerState<I>,
}
impl<I: Instant> Default for StackState<I> {
fn default() -> StackState<I> {
StackStateBuilder::default().build()
}
}
/// The non synchronized context for the stack with a buffer.
pub trait BufferNonSyncContextInner<B: BufferMut>:
DeviceLayerEventDispatcher<B>
+ BufferUdpContext<Ipv4, B>
+ BufferUdpContext<Ipv6, B>
+ BufferIcmpContext<Ipv4, B>
+ BufferIcmpContext<Ipv6, B>
{
}
impl<
B: BufferMut,
C: DeviceLayerEventDispatcher<B>
+ BufferUdpContext<Ipv4, B>
+ BufferUdpContext<Ipv6, B>
+ BufferIcmpContext<Ipv4, B>
+ BufferIcmpContext<Ipv6, B>,
> BufferNonSyncContextInner<B> for C
{
}
/// The non synchronized context for the stack with a buffer.
pub trait BufferNonSyncContext<B: BufferMut>:
NonSyncContext + BufferNonSyncContextInner<B>
{
}
impl<B: BufferMut, C: NonSyncContext + BufferNonSyncContextInner<B>> BufferNonSyncContext<B> for C {}
/// The non-synchronized context for the stack.
pub trait NonSyncContext:
CounterContext
+ BufferNonSyncContextInner<Buf<Vec<u8>>>
+ BufferNonSyncContextInner<EmptyBuf>
+ RngContext
+ TimerContext<TimerId>
+ EventContext<IpDeviceEvent<DeviceId, Ipv4>>
+ EventContext<IpDeviceEvent<DeviceId, Ipv6>>
+ EventContext<IpLayerEvent<DeviceId, Ipv4>>
+ EventContext<IpLayerEvent<DeviceId, Ipv6>>
+ EventContext<DadEvent<DeviceId>>
+ EventContext<Ipv6RouteDiscoveryEvent<DeviceId>>
+ UdpContext<Ipv4>
+ UdpContext<Ipv6>
+ IcmpContext<Ipv4>
+ IcmpContext<Ipv6>
{
}
impl<
C: CounterContext
+ BufferNonSyncContextInner<Buf<Vec<u8>>>
+ BufferNonSyncContextInner<EmptyBuf>
+ RngContext
+ TimerContext<TimerId>
+ EventContext<IpDeviceEvent<DeviceId, Ipv4>>
+ EventContext<IpDeviceEvent<DeviceId, Ipv6>>
+ EventContext<IpLayerEvent<DeviceId, Ipv4>>
+ EventContext<IpLayerEvent<DeviceId, Ipv6>>
+ EventContext<DadEvent<DeviceId>>
+ EventContext<Ipv6RouteDiscoveryEvent<DeviceId>>
+ UdpContext<Ipv4>
+ UdpContext<Ipv6>
+ IcmpContext<Ipv4>
+ IcmpContext<Ipv6>,
> NonSyncContext for C
{
}
/// The synchronized context.
pub struct SyncCtx<NonSyncCtx: NonSyncContext> {
/// Contains the state of the stack.
pub state: StackState<NonSyncCtx::Instant>,
/// A marker for the non-synchronized context type.
pub non_sync_ctx_marker: PhantomData<NonSyncCtx>,
}
/// Context available during the execution of the netstack.
///
/// `Ctx` provides access to the state of the netstack and to an event
/// dispatcher which can be used to emit events and schedule timers. A mutable
/// reference to a `Ctx` is passed to every function in the netstack.
pub struct Ctx<NonSyncCtx: NonSyncContext> {
/// The synchronized context.
pub sync_ctx: SyncCtx<NonSyncCtx>,
/// The non-synchronized context.
pub non_sync_ctx: NonSyncCtx,
}
impl<NonSyncCtx: NonSyncContext + Default> Default for Ctx<NonSyncCtx>
where
StackState<NonSyncCtx::Instant>: Default,
{
fn default() -> Ctx<NonSyncCtx> {
Ctx {
sync_ctx: SyncCtx { state: StackState::default(), non_sync_ctx_marker: PhantomData },
non_sync_ctx: Default::default(),
}
}
}
impl<NonSyncCtx: NonSyncContext + Default> Ctx<NonSyncCtx> {
/// Constructs a new `Ctx`.
pub fn new(state: StackState<NonSyncCtx::Instant>) -> Ctx<NonSyncCtx> {
Ctx {
sync_ctx: SyncCtx { state, non_sync_ctx_marker: PhantomData },
non_sync_ctx: Default::default(),
}
}
}
/// The identifier for any timer event.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct TimerId(TimerIdInner);
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
enum TimerIdInner {
/// A timer event in the device layer.
DeviceLayer(DeviceLayerTimerId),
/// A timer event in the transport layer.
_TransportLayer(TransportLayerTimerId),
/// A timer event in the IP layer.
IpLayer(IpLayerTimerId),
/// A timer event for an IPv4 device.
Ipv4Device(Ipv4DeviceTimerId<DeviceId>),
/// A timer event for an IPv6 device.
Ipv6Device(Ipv6DeviceTimerId<DeviceId>),
/// A no-op timer event (used for tests)
#[cfg(test)]
Nop(usize),
}
impl From<DeviceLayerTimerId> for TimerId {
fn from(id: DeviceLayerTimerId) -> TimerId {
TimerId(TimerIdInner::DeviceLayer(id))
}
}
impl From<Ipv4DeviceTimerId<DeviceId>> for TimerId {
fn from(id: Ipv4DeviceTimerId<DeviceId>) -> TimerId {
TimerId(TimerIdInner::Ipv4Device(id))
}
}
impl From<Ipv6DeviceTimerId<DeviceId>> for TimerId {
fn from(id: Ipv6DeviceTimerId<DeviceId>) -> TimerId {
TimerId(TimerIdInner::Ipv6Device(id))
}
}
impl From<IpLayerTimerId> for TimerId {
fn from(id: IpLayerTimerId) -> TimerId {
TimerId(TimerIdInner::IpLayer(id))
}
}
impl_timer_context!(TimerId, DeviceLayerTimerId, TimerId(TimerIdInner::DeviceLayer(id)), id);
impl_timer_context!(TimerId, IpLayerTimerId, TimerId(TimerIdInner::IpLayer(id)), id);
impl_timer_context!(
TimerId,
Ipv4DeviceTimerId<DeviceId>,
TimerId(TimerIdInner::Ipv4Device(id)),
id
);
impl_timer_context!(
TimerId,
Ipv6DeviceTimerId<DeviceId>,
TimerId(TimerIdInner::Ipv6Device(id)),
id
);
/// Handles a generic timer event.
pub fn handle_timer<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
id: TimerId,
) {
trace!("handle_timer: dispatching timerid: {:?}", id);
match id {
TimerId(TimerIdInner::DeviceLayer(x)) => {
device::handle_timer(sync_ctx, ctx, x);
}
TimerId(TimerIdInner::_TransportLayer(x)) => {
transport::handle_timer(sync_ctx, x);
}
TimerId(TimerIdInner::IpLayer(x)) => {
ip::handle_timer(sync_ctx, ctx, x);
}
TimerId(TimerIdInner::Ipv4Device(x)) => {
ip::device::handle_ipv4_timer(sync_ctx, ctx, x);
}
TimerId(TimerIdInner::Ipv6Device(x)) => {
ip::device::handle_ipv6_timer(sync_ctx, ctx, x);
}
#[cfg(test)]
TimerId(TimerIdInner::Nop(_)) => {
ctx.increment_counter("timer::nop");
}
}
}
/// A type representing an instant in time.
///
/// `Instant` can be implemented by any type which represents an instant in
/// time. This can include any sort of real-world clock time (e.g.,
/// [`std::time::Instant`]) or fake time such as in testing.
pub trait Instant: Sized + Ord + Copy + Clone + Debug + Send + Sync {
/// Returns the amount of time elapsed from another instant to this one.
///
/// # Panics
///
/// This function will panic if `earlier` is later than `self`.
fn duration_since(&self, earlier: Self) -> time::Duration;
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be
/// represented as `Instant` (which means it's inside the bounds of the
/// underlying data structure), `None` otherwise.
fn checked_add(&self, duration: time::Duration) -> Option<Self>;
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be
/// represented as `Instant` (which means it's inside the bounds of the
/// underlying data structure), `None` otherwise.
fn checked_sub(&self, duration: time::Duration) -> Option<Self>;
}
/// Get all IPv4 and IPv6 address/subnet pairs configured on a device
pub fn get_all_ip_addr_subnets<'a, NonSyncCtx: NonSyncContext>(
ctx: &'a SyncCtx<NonSyncCtx>,
device: DeviceId,
) -> impl 'a + Iterator<Item = AddrSubnetEither> {
let addr_v4 = crate::ip::device::get_assigned_ipv4_addr_subnets(ctx, device)
.map(|a| AddrSubnetEither::V4(a));
let addr_v6 = crate::ip::device::get_assigned_ipv6_addr_subnets(ctx, device)
.map(|a| AddrSubnetEither::V6(a));
addr_v4.chain(addr_v6)
}
/// Set the IP address and subnet for a device.
pub fn add_ip_addr_subnet<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr_sub: AddrSubnetEither,
) -> error::Result<()> {
map_addr_version!(
addr_sub: AddrSubnetEither;
crate::device::add_ip_addr_subnet(sync_ctx, ctx, device, addr_sub)
)
.map_err(From::from)
}
/// Delete an IP address on a device.
pub fn del_ip_addr<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
addr: IpAddr<SpecifiedAddr<Ipv4Addr>, SpecifiedAddr<Ipv6Addr>>,
) -> error::Result<()> {
map_addr_version!(
addr: IpAddr;
crate::device::del_ip_addr(sync_ctx, ctx, device, &addr)
)
.map_err(From::from)
}
/// Adds a route to the forwarding table.
pub fn add_route<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
entry: AddableEntryEither<DeviceId>,
) -> Result<(), AddRouteError> {
let (subnet, device, gateway) = entry.into_subnet_device_gateway();
match (device, gateway) {
(Some(device), None) => map_addr_version!(
subnet: SubnetEither;
crate::ip::add_device_route::<Ipv4, _, _>(sync_ctx, ctx, subnet, device),
crate::ip::add_device_route::<Ipv6, _, _>(sync_ctx, ctx, subnet, device)
)
.map_err(From::from),
(None, Some(next_hop)) => {
let next_hop = next_hop.into();
map_addr_version!(
(subnet: SubnetEither, next_hop: IpAddr);
crate::ip::add_route::<Ipv4, _, _>(sync_ctx, ctx, subnet, next_hop),
crate::ip::add_route::<Ipv6, _, _>(sync_ctx, ctx, subnet, next_hop),
unreachable!()
)
}
x => todo!("TODO(https://fxbug.dev/96680): support setting gateway route with device; (device, gateway) = {:?}", x),
}
}
/// Delete a route from the forwarding table, returning `Err` if no
/// route was found to be deleted.
pub fn del_route<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
subnet: SubnetEither,
) -> error::Result<()> {
map_addr_version!(
subnet: SubnetEither;
crate::ip::del_route::<Ipv4, _, _>(sync_ctx, ctx, subnet),
crate::ip::del_route::<Ipv6, _, _>(sync_ctx, ctx, subnet)
)
.map_err(From::from)
}
/// Get all the routes.
pub fn get_all_routes<'a, NonSyncCtx: NonSyncContext>(
ctx: &'a SyncCtx<NonSyncCtx>,
) -> impl 'a + Iterator<Item = EntryEither<DeviceId>> {
let v4_routes = ip::iter_all_routes::<_, Ipv4Addr>(ctx);
let v6_routes = ip::iter_all_routes::<_, Ipv6Addr>(ctx);
v4_routes.cloned().map(From::from).chain(v6_routes.cloned().map(From::from))
}
#[cfg(test)]
mod tests {
use net_types::{
ip::{Ip, Ipv4, Ipv6},
Witness,
};
use super::*;
use crate::testutil::{DummyEventDispatcherBuilder, TestIpExt};
fn test_add_remove_ip_addresses<I: Ip + TestIpExt>() {
let config = I::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyEventDispatcherBuilder::default().build();
let device = crate::add_ethernet_device(
&mut sync_ctx,
&mut non_sync_ctx,
config.local_mac,
Ipv6::MINIMUM_LINK_MTU.into(),
);
crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device);
let ip: IpAddr = I::get_other_ip_address(1).get().into();
let prefix = config.subnet.prefix();
let addr_subnet = AddrSubnetEither::new(ip, prefix).unwrap();
// IP doesn't exist initially.
assert_eq!(get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet), None);
// Add IP (OK).
let () = add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, addr_subnet).unwrap();
assert_eq!(
get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet),
Some(addr_subnet)
);
// Add IP again (already exists).
assert_eq!(
add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, addr_subnet).unwrap_err(),
NetstackError::Exists
);
assert_eq!(
get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet),
Some(addr_subnet)
);
// Add IP with different subnet (already exists).
let wrong_addr_subnet = AddrSubnetEither::new(ip, prefix - 1).unwrap();
assert_eq!(
add_ip_addr_subnet(&mut sync_ctx, &mut non_sync_ctx, device, wrong_addr_subnet)
.unwrap_err(),
NetstackError::Exists
);
assert_eq!(
get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet),
Some(addr_subnet)
);
let ip = SpecifiedAddr::new(ip).unwrap();
// Del IP (ok).
let () = del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, ip.into()).unwrap();
assert_eq!(get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet), None);
// Del IP again (not found).
assert_eq!(
del_ip_addr(&mut sync_ctx, &mut non_sync_ctx, device, ip.into()).unwrap_err(),
NetstackError::NotFound
);
assert_eq!(get_all_ip_addr_subnets(&sync_ctx, device).find(|&a| a == addr_subnet), None);
}
#[test]
fn test_add_remove_ipv4_addresses() {
test_add_remove_ip_addresses::<Ipv4>();
}
#[test]
fn test_add_remove_ipv6_addresses() {
test_add_remove_ip_addresses::<Ipv6>();
}
}