blob: 5bed708c0b3f80ad5d772bb69e6c543ae84544d6 [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 Internet Protocol, versions 4 and 6.
#[macro_use]
pub(crate) mod path_mtu;
pub(crate) mod device;
pub(crate) mod forwarding;
pub(crate) mod gmp;
pub mod icmp;
mod integration;
mod ipv6;
pub(crate) mod reassembly;
pub(crate) mod socket;
mod types;
// It's ok to `pub use` rather `pub(crate) use` here because the items in
// `types` which are themselves `pub(crate)` will still not be allowed to be
// re-exported from the root.
pub use self::types::*;
use alloc::vec::Vec;
use core::{
fmt::{self, Debug, Display, Formatter},
hash::Hash,
num::NonZeroU8,
slice::Iter,
};
use log::{debug, trace};
use net_types::{
ip::{Ip, IpAddress, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6SourceAddr, Subnet},
SpecifiedAddr, UnicastAddr, Witness,
};
use nonzero_ext::nonzero;
use packet::{Buf, BufferMut, ParseMetadata, Serializer};
use packet_formats::{
error::IpParseError,
ip::{IpPacket, IpProto, Ipv4Proto, Ipv6Proto},
ipv4::{Ipv4FragmentType, Ipv4Packet, Ipv4PacketBuilder},
ipv6::{Ipv6Packet, Ipv6PacketBuilder},
};
use crate::{
context::{
CounterContext, EventContext, InstantContext, NonTestCtxMarker, RngContext, StateContext,
TimerHandler,
},
device::{DeviceId, FrameDestination},
error::{ExistsError, NotFoundError},
ip::{
device::{
get_ipv4_device_state, get_ipv6_device_state, iter_ipv4_devices, iter_ipv6_devices,
state::{AssignedAddress as _, IpDeviceState},
IpDeviceNonSyncContext,
},
forwarding::{AddRouteError, Destination, ForwardingTable},
gmp::igmp::IgmpPacketHandler,
icmp::{
BufferIcmpHandler, IcmpHandlerIpExt, IcmpIpExt, IcmpIpTransportContext, IcmpNonSyncCtx,
IcmpState, Icmpv4Error, Icmpv4ErrorCode, Icmpv4ErrorKind, Icmpv4State,
Icmpv4StateBuilder, Icmpv6ErrorCode, Icmpv6ErrorKind, Icmpv6State, Icmpv6StateBuilder,
InnerIcmpContext,
},
ipv6::Ipv6PacketAction,
path_mtu::{PmtuCache, PmtuTimerId},
reassembly::{
FragmentCacheKey, FragmentHandler, FragmentProcessingState, IpPacketFragmentCache,
},
socket::{
BufferIpSocketHandler, IpSock, IpSockRoute, IpSockRouteError, IpSockUnroutableError,
IpSocketContext, IpSocketHandler,
},
},
BufferNonSyncContext, Instant, NonSyncContext, StackState, SyncCtx,
};
/// Default IPv4 TTL.
const DEFAULT_TTL: NonZeroU8 = nonzero!(64u8);
/// The IPv6 subnet that contains all addresses; `::/0`.
// Safe because 0 is less than the number of IPv6 address bits.
const IPV6_DEFAULT_SUBNET: Subnet<Ipv6Addr> =
unsafe { Subnet::new_unchecked(Ipv6::UNSPECIFIED_ADDRESS, 0) };
/// An error encountered when receiving a transport-layer packet.
#[derive(Debug)]
pub(crate) struct TransportReceiveError {
inner: TransportReceiveErrorInner,
}
impl TransportReceiveError {
// NOTE: We don't expose a constructor for the "protocol unsupported" case.
// This ensures that the only way that we send a "protocol unsupported"
// error is if the implementation of `IpTransportContext` provided for a
// given protocol number is `()`. That's because `()` is the only type whose
// `receive_ip_packet` function is implemented in this module, and thus it's
// the only type that is able to construct a "protocol unsupported"
// `TransportReceiveError`.
/// Constructs a new `TransportReceiveError` to indicate an unreachable
/// port.
pub(crate) fn new_port_unreachable() -> TransportReceiveError {
TransportReceiveError { inner: TransportReceiveErrorInner::PortUnreachable }
}
}
#[derive(Debug)]
enum TransportReceiveErrorInner {
ProtocolUnsupported,
PortUnreachable,
}
/// An [`Ip`] extension trait adding functionality specific to the IP layer.
pub trait IpExt: packet_formats::ip::IpExt + IcmpIpExt {
/// The type used to specify an IP packet's source address in a call to
/// [`BufferIpTransportContext::receive_ip_packet`].
///
/// For IPv4, this is `Ipv4Addr`. For IPv6, this is [`Ipv6SourceAddr`].
type RecvSrcAddr: Into<Self::Addr>;
}
impl IpExt for Ipv4 {
type RecvSrcAddr = Ipv4Addr;
}
impl IpExt for Ipv6 {
type RecvSrcAddr = Ipv6SourceAddr;
}
/// The execution context provided by a transport layer protocol to the IP
/// layer.
///
/// An implementation for `()` is provided which indicates that a particular
/// transport layer protocol is unsupported.
pub(crate) trait IpTransportContext<I: IcmpIpExt, C, SC: IpDeviceIdContext<I> + ?Sized> {
/// Receive an ICMP error message.
///
/// All arguments beginning with `original_` are fields from the IP packet
/// that triggered the error. The `original_body` is provided here so that
/// the error can be associated with a transport-layer socket. `device`
/// identifies the device that received the ICMP error message packet.
///
/// While ICMPv4 error messages are supposed to contain the first 8 bytes of
/// the body of the offending packet, and ICMPv6 error messages are supposed
/// to contain as much of the offending packet as possible without violating
/// the IPv6 minimum MTU, the caller does NOT guarantee that either of these
/// hold. It is `receive_icmp_error`'s responsibility to handle any length
/// of `original_body`, and to perform any necessary validation.
fn receive_icmp_error(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
original_src_ip: Option<SpecifiedAddr<I::Addr>>,
original_dst_ip: SpecifiedAddr<I::Addr>,
original_body: &[u8],
err: I::ErrorCode,
);
}
/// The execution context provided by a transport layer protocol to the IP layer
/// when a buffer is required.
pub(crate) trait BufferIpTransportContext<
I: IpExt,
C,
SC: IpDeviceIdContext<I> + ?Sized,
B: BufferMut,
>: IpTransportContext<I, C, SC>
{
/// Receive a transport layer packet in an IP packet.
///
/// In the event of an unreachable port, `receive_ip_packet` returns the
/// buffer in its original state (with the transport packet un-parsed) in
/// the `Err` variant.
fn receive_ip_packet(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
src_ip: I::RecvSrcAddr,
dst_ip: SpecifiedAddr<I::Addr>,
buffer: B,
) -> Result<(), (B, TransportReceiveError)>;
}
impl<I: IcmpIpExt, C, SC: IpDeviceIdContext<I> + ?Sized> IpTransportContext<I, C, SC> for () {
fn receive_icmp_error(
_sync_ctx: &mut SC,
_ctx: &mut C,
_device: SC::DeviceId,
_original_src_ip: Option<SpecifiedAddr<I::Addr>>,
_original_dst_ip: SpecifiedAddr<I::Addr>,
_original_body: &[u8],
err: I::ErrorCode,
) {
trace!("IpTransportContext::receive_icmp_error: Received ICMP error message ({:?}) for unsupported IP protocol", err);
}
}
impl<I: IpExt, C, SC: IpDeviceIdContext<I> + ?Sized, B: BufferMut>
BufferIpTransportContext<I, C, SC, B> for ()
{
fn receive_ip_packet(
_sync_ctx: &mut SC,
_ctx: &mut C,
_device: SC::DeviceId,
_src_ip: I::RecvSrcAddr,
_dst_ip: SpecifiedAddr<I::Addr>,
buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
Err((
buffer,
TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported },
))
}
}
/// The execution context provided by the IP layer to transport layer protocols.
pub trait TransportIpContext<I: IpExt, C>: IpDeviceIdContext<I> + IpSocketHandler<I, C> {
/// Is this one of our local addresses, and is it in the assigned state?
///
/// If `addr` is the address associated with a local interface and, for
/// IPv6, if it is in the "assigned" state, this method returns the
/// identifier for the device with the address assigned. Otherwise returns
/// `None`.
fn get_device_with_assigned_addr(&self, addr: SpecifiedAddr<I::Addr>)
-> Option<Self::DeviceId>;
}
/// The execution context provided by the IP layer to transport layer protocols
/// when a buffer is provided.
///
/// `BufferTransportIpContext` is like [`TransportIpContext`], except that it
/// also requires that the context be capable of receiving frames in buffers of
/// type `B`. This is used when a buffer of type `B` is provided to IP, and
/// allows any generated link-layer frames to reuse that buffer rather than
/// needing to always allocate a new one.
pub trait BufferTransportIpContext<I: IpExt, C, B: BufferMut>:
TransportIpContext<I, C> + BufferIpSocketHandler<I, C, B>
{
}
impl<I: IpExt, B: BufferMut, C, SC: TransportIpContext<I, C> + BufferIpSocketHandler<I, C, B>>
BufferTransportIpContext<I, C, B> for SC
{
}
// TODO(joshlf): With all 256 protocol numbers (minus reserved ones) given their
// own associated type in both traits, running `cargo check` on a 2018 MacBook
// Pro takes over a minute. Eventually - and before we formally publish this as
// a library - we should identify the bottleneck in the compiler and optimize
// it. For the time being, however, we only support protocol numbers that we
// actually use (TCP and UDP).
/// The execution context for IP's transport layer.
///
/// `IpTransportLayerContext` defines the [`IpTransportContext`] for each IP
/// protocol number that is common to all IP protocols.
trait IpTransportLayerContext<I: IpExt, C>: IpDeviceIdContext<I> {
type Tcp: IpTransportContext<I, C, Self>;
type Udp: IpTransportContext<I, C, Self>;
}
impl<
I: IpExt,
C: crate::transport::udp::UdpStateNonSyncContext<I>,
SC: crate::transport::udp::UdpStateContext<I, C>,
> IpTransportLayerContext<I, C> for SC
{
type Tcp = ();
type Udp = crate::transport::udp::UdpIpTransportContext;
}
/// Execution context for IP's transport layer with a buffer.
trait BufferIpTransportLayerContext<I: IpExt, C, B: BufferMut>: IpTransportLayerContext<I, C> {
type Tcp: BufferIpTransportContext<I, C, Self, B>;
type Udp: BufferIpTransportContext<I, C, Self, B>;
}
impl<I: IpExt, B: BufferMut, C: RngContext, SC: IpTransportLayerContext<I, C>>
BufferIpTransportLayerContext<I, C, B> for SC
where
SC::Tcp: BufferIpTransportContext<I, C, SC, B>,
SC::Udp: BufferIpTransportContext<I, C, SC, B>,
{
type Tcp = SC::Tcp;
type Udp = SC::Udp;
}
impl<C, SC: IpDeviceContext<Ipv4, C> + IpSocketHandler<Ipv4, C>> TransportIpContext<Ipv4, C>
for SC
{
fn get_device_with_assigned_addr(&self, addr: SpecifiedAddr<Ipv4Addr>) -> Option<SC::DeviceId> {
match self.address_status(addr) {
AddressStatus::Present((device, state)) => match state {
Ipv4PresentAddressStatus::Unicast => Some(device),
Ipv4PresentAddressStatus::LimitedBroadcast
| Ipv4PresentAddressStatus::SubnetBroadcast
| Ipv4PresentAddressStatus::Multicast => None,
},
AddressStatus::Unassigned => None,
}
}
}
impl<C, SC: IpDeviceContext<Ipv6, C> + IpSocketHandler<Ipv6, C>> TransportIpContext<Ipv6, C>
for SC
{
fn get_device_with_assigned_addr(&self, addr: SpecifiedAddr<Ipv6Addr>) -> Option<SC::DeviceId> {
match self.address_status(addr) {
AddressStatus::Present((device, status)) => match status {
Ipv6PresentAddressStatus::UnicastAssigned => Some(device),
Ipv6PresentAddressStatus::Multicast
| Ipv6PresentAddressStatus::UnicastTentative => None,
},
AddressStatus::Unassigned => None,
}
}
}
/// An IP device ID.
pub trait IpDeviceId: Copy + Display + Debug + Eq + Hash + PartialEq + Send + Sync {
/// Returns true if the device is a loopback device.
fn is_loopback(&self) -> bool;
}
/// An execution context which provides a `DeviceId` type for various IP
/// internals to share.
///
/// This trait provides the associated `DeviceId` type, and is used by
/// [`IgmpContext`], [`MldContext`], and [`InnerIcmpContext`]. It allows them to use
/// the same `DeviceId` type rather than each providing their own, which would
/// require lots of verbose type bounds when they need to be interoperable (such
/// as when ICMP delivers an MLD packet to the `mld` module for processing).
pub trait IpDeviceIdContext<I: Ip> {
/// The type of device IDs.
type DeviceId: IpDeviceId + 'static;
/// Returns the ID of the loopback interface, if one exists on the system
/// and is initialized.
fn loopback_id(&self) -> Option<Self::DeviceId>;
}
/// The status of an IP address on an interface.
pub(crate) enum AddressStatus<S> {
Present(S),
Unassigned,
}
impl<D: IpDeviceId, S> AddressStatus<(D, S)> {
fn drop_device(self) -> AddressStatus<S> {
match self {
Self::Present((_d, s)) => AddressStatus::Present(s),
Self::Unassigned => AddressStatus::Unassigned,
}
}
}
/// The status of an IPv4 address.
pub(crate) enum Ipv4PresentAddressStatus {
LimitedBroadcast,
SubnetBroadcast,
Multicast,
Unicast,
}
/// The status of an IPv6 address.
pub(crate) enum Ipv6PresentAddressStatus {
Multicast,
UnicastAssigned,
UnicastTentative,
}
/// An extension trait providing IP layer properties.
pub(crate) trait IpLayerIpExt: IpExt {
type AddressStatus;
}
impl IpLayerIpExt for Ipv4 {
type AddressStatus = Ipv4PresentAddressStatus;
}
impl IpLayerIpExt for Ipv6 {
type AddressStatus = Ipv6PresentAddressStatus;
}
/// An extension trait providing IP layer state properties.
pub(crate) trait IpLayerStateIpExt<I: Instant, DeviceId>: IpLayerIpExt {
type State: AsRef<IpStateInner<Self, I, DeviceId>> + AsMut<IpStateInner<Self, I, DeviceId>>;
}
impl<I: Instant, DeviceId> IpLayerStateIpExt<I, DeviceId> for Ipv4 {
type State = Ipv4State<I, DeviceId>;
}
impl<I: Instant, DeviceId> IpLayerStateIpExt<I, DeviceId> for Ipv6 {
type State = Ipv6State<I, DeviceId>;
}
/// The state context provided to the IP layer.
// TODO(https://fxbug.dev/48578): Do not return references to state. Instead,
// callers of methods on this trait should provide a callback that takes a state
// reference.
pub(crate) trait IpStateContext<
I: IpLayerStateIpExt<Instant, Self::DeviceId>,
Instant: crate::Instant,
>: IpDeviceIdContext<I>
{
/// Gets immutable access to the IP layer state.
fn get_ip_layer_state(&self) -> &I::State;
/// Gets mutable access to the IP layer state.
fn get_ip_layer_state_mut(&mut self) -> &mut I::State;
}
/// The IP device context provided to the IP layer.
pub(crate) trait IpDeviceContext<I: IpLayerIpExt, C>: IpDeviceIdContext<I> {
/// Is the device enabled?
fn is_ip_device_enabled(&self, device_id: Self::DeviceId) -> bool;
/// Gets the status of an address.
///
/// Returns the status of the address if it is assigned, and the device it
/// is assigned to.
fn address_status(
&self,
addr: SpecifiedAddr<I::Addr>,
) -> AddressStatus<(Self::DeviceId, I::AddressStatus)>;
/// Gets the status of an address.
///
/// Only the specified device will be checked for the address. Returns
/// [`AddressStatus::Unassigned`] if the address is not assigned to the
/// device.
fn address_status_for_device(
&self,
addr: SpecifiedAddr<I::Addr>,
device_id: Self::DeviceId,
) -> AddressStatus<I::AddressStatus>;
/// Returns true iff the device has routing enabled.
fn is_device_routing_enabled(&self, device_id: Self::DeviceId) -> bool;
/// Returns the hop limit.
fn get_hop_limit(&self, device_id: Self::DeviceId) -> NonZeroU8;
/// Returns the MTU of the device.
fn get_mtu(&self, device_id: Self::DeviceId) -> u32;
}
/// Events observed at the IP layer.
pub enum IpLayerEvent<DeviceId, I: Ip> {
/// A device route was added.
DeviceRouteAdded {
/// The resolved device.
device: DeviceId,
/// The destination subnet.
subnet: Subnet<I::Addr>,
},
/// A device route was removed.
DeviceRouteRemoved {
/// The resolved device.
device: DeviceId,
/// The destination subnet.
subnet: Subnet<I::Addr>,
},
}
/// The non-synchronized execution context for the IP layer.
pub(crate) trait IpLayerNonSyncContext<I: Ip, DeviceId>:
InstantContext + EventContext<IpLayerEvent<DeviceId, I>> + CounterContext
{
}
impl<
I: Ip,
DeviceId,
C: InstantContext + EventContext<IpLayerEvent<DeviceId, I>> + CounterContext,
> IpLayerNonSyncContext<I, DeviceId> for C
{
}
/// The execution context for the IP layer.
pub(crate) trait IpLayerContext<
I: IpLayerStateIpExt<C::Instant, Self::DeviceId>,
C: IpLayerNonSyncContext<I, <Self as IpDeviceIdContext<I>>::DeviceId>,
>: IpStateContext<I, C::Instant> + IpDeviceContext<I, C>
{
}
impl<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, <SC as IpDeviceIdContext<I>>::DeviceId>,
SC: IpStateContext<I, C::Instant> + IpDeviceContext<I, C>,
> IpLayerContext<I, C> for SC
{
}
impl<
C: IpDeviceNonSyncContext<Ipv4, SC::DeviceId> + IpLayerNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpLayerContext<Ipv4, C> + device::IpDeviceContext<Ipv4, C>,
> IpSocketContext<Ipv4, C> for SC
{
fn lookup_route(
&self,
ctx: &mut C,
device: Option<SC::DeviceId>,
local_ip: Option<SpecifiedAddr<Ipv4Addr>>,
addr: SpecifiedAddr<Ipv4Addr>,
) -> Result<IpSockRoute<Ipv4, SC::DeviceId>, IpSockRouteError> {
let get_local_addr = |dev_state: &IpDeviceState<_, Ipv4>, local_ip| {
if let Some(local_ip) = local_ip {
if dev_state.iter_addrs().any(|addr_sub| addr_sub.addr() == local_ip) {
Ok(local_ip)
} else {
Err(IpSockUnroutableError::LocalAddrNotAssigned.into())
}
} else {
dev_state
.iter_addrs()
.next()
.map_or(Err(IpSockRouteError::NoLocalAddrAvailable), |subnet| Ok(subnet.addr()))
}
};
// Check if locally destined.
//
// TODO(https://fxbug.dev/93870): Encode the delivery of locally
// destined packets to loopback in the route table.
if let Some(dev_state) =
iter_ipv4_devices::<C, SC>(self).find_map(|(_device_id, dev_state)| {
dev_state.iter_addrs().any(|addr_sub| addr_sub.addr() == addr).then(|| dev_state)
})
{
let loopback = if let Some(loopback) = self.loopback_id() {
loopback
} else {
return Err(IpSockUnroutableError::NoRouteToRemoteAddr.into());
};
return Ok(IpSockRoute {
// TODO(https://fxbug.dev/94965): Allow local IPs from any
// interface for locally-destined packets.
local_ip: get_local_addr(dev_state, local_ip)?,
destination: Destination { device: loopback, next_hop: addr },
});
}
lookup_route(self, ctx, device, addr)
.map(|destination| {
let Destination { device, next_hop: _ } = &destination;
let dev_state = get_ipv4_device_state(self, *device);
Ok(IpSockRoute { local_ip: get_local_addr(dev_state, local_ip)?, destination })
})
.unwrap_or(Err(IpSockUnroutableError::NoRouteToRemoteAddr.into()))
}
}
impl<
C: IpDeviceNonSyncContext<Ipv6, SC::DeviceId> + IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpLayerContext<Ipv6, C> + device::IpDeviceContext<Ipv6, C>,
> IpSocketContext<Ipv6, C> for SC
{
fn lookup_route(
&self,
ctx: &mut C,
device: Option<SC::DeviceId>,
local_ip: Option<SpecifiedAddr<Ipv6Addr>>,
addr: SpecifiedAddr<Ipv6Addr>,
) -> Result<IpSockRoute<Ipv6, SC::DeviceId>, IpSockRouteError> {
let get_local_addr = |device, local_ip| {
let dev_state = get_ipv6_device_state(self, device);
if let Some(local_ip) = local_ip {
// TODO(joshlf):
// - Allow the specified local IP to be the local IP of a
// different device so long as we're operating in the weak
// host model.
// - What about when the socket is bound to a device? How does
// that affect things?
//
// TODO(fxbug.dev/69196): Give `local_ip` the type
// `Option<UnicastAddr<Ipv6Addr>>` instead of doing this dynamic
// check here.
let local_ip = UnicastAddr::from_witness(local_ip)
.ok_or(IpSockUnroutableError::LocalAddrNotAssigned)?;
if dev_state.find_addr(&local_ip).map_or(true, |entry| entry.state.is_tentative()) {
Err(IpSockUnroutableError::LocalAddrNotAssigned.into())
} else {
Ok(local_ip.into_specified())
}
} else {
// TODO(joshlf):
// - If device binding is used, then we should only consider the
// addresses of the device being bound to.
// - If we are operating in the strong host model, then perhaps
// we should restrict ourselves to addresses associated with
// the device found by looking up the remote in the forwarding
// table? This I'm less sure of.
crate::ip::socket::ipv6_source_address_selection::select_ipv6_source_address(
addr,
device,
iter_ipv6_devices(self)
.map(|(device_id, ip_state)| {
ip_state.iter_addrs().map(move |a| (a, device_id))
})
.flatten(),
)
.map(|a| a.into_specified())
.ok_or(IpSockRouteError::NoLocalAddrAvailable)
}
};
// Check if locally destined.
//
// TODO(https://fxbug.dev/93870): Encode the delivery of locally
// destined packets to loopback in the route table.
if let Some(device_id) = iter_ipv6_devices(self).find_map(|(device_id, dev_state)| {
dev_state
.iter_assigned_ipv6_addrs()
.any(|addr_sub| addr_sub.addr() == addr)
.then(|| device_id)
}) {
let loopback = if let Some(loopback) = self.loopback_id() {
loopback
} else {
return Err(IpSockUnroutableError::NoRouteToRemoteAddr.into());
};
return Ok(IpSockRoute {
// TODO(https://fxbug.dev/94965): Allow local IPs from any
// interface for locally-destined packets.
local_ip: get_local_addr(device_id, local_ip)?,
destination: Destination { device: loopback, next_hop: addr },
});
}
lookup_route(self, ctx, device, addr)
.map(|destination| {
let Destination { device, next_hop: _ } = &destination;
Ok(IpSockRoute { local_ip: get_local_addr(*device, local_ip)?, destination })
})
.unwrap_or(Err(IpSockUnroutableError::NoRouteToRemoteAddr.into()))
}
}
impl<NonSyncCtx: NonSyncContext> IpStateContext<Ipv4, NonSyncCtx::Instant> for SyncCtx<NonSyncCtx> {
fn get_ip_layer_state(&self) -> &Ipv4State<NonSyncCtx::Instant, DeviceId> {
&self.state.ipv4
}
fn get_ip_layer_state_mut(&mut self) -> &mut Ipv4State<NonSyncCtx::Instant, DeviceId> {
&mut self.state.ipv4
}
}
impl<NonSyncCtx: NonSyncContext> IpStateContext<Ipv6, NonSyncCtx::Instant> for SyncCtx<NonSyncCtx> {
fn get_ip_layer_state(&self) -> &Ipv6State<NonSyncCtx::Instant, DeviceId> {
&self.state.ipv6
}
fn get_ip_layer_state_mut(&mut self) -> &mut Ipv6State<NonSyncCtx::Instant, DeviceId> {
&mut self.state.ipv6
}
}
/// The transport context provided to the IP layer requiring a buffer type.
pub(crate) trait BufferTransportContext<I: IpLayerIpExt, C, B: BufferMut>:
IpDeviceIdContext<I>
{
/// Dispatches a received incoming IP packet to the appropriate protocol.
fn dispatch_receive_ip_packet(
&mut self,
ctx: &mut C,
device: Self::DeviceId,
src_ip: I::RecvSrcAddr,
dst_ip: SpecifiedAddr<I::Addr>,
proto: I::Proto,
body: B,
) -> Result<(), (B, TransportReceiveError)>;
}
/// The IP device context provided to the IP layer requiring a buffer type.
pub(crate) trait BufferIpDeviceContext<I: IpLayerIpExt, C, B: BufferMut>:
IpDeviceContext<I, C>
{
/// Sends an IP frame to the next hop.
fn send_ip_frame<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
next_hop: SpecifiedAddr<I::Addr>,
packet: S,
) -> Result<(), S>;
}
/// The execution context for the IP layer requiring buffer.
pub(crate) trait BufferIpLayerContext<
I: IpLayerStateIpExt<C::Instant, Self::DeviceId> + IcmpHandlerIpExt,
C: IpLayerNonSyncContext<I, Self::DeviceId>,
B: BufferMut,
>:
BufferTransportContext<I, C, B>
+ BufferIpDeviceContext<I, C, B>
+ BufferIcmpHandler<I, C, B>
+ IpLayerContext<I, C>
+ FragmentHandler<I, C>
{
}
impl<
I: IpLayerStateIpExt<C::Instant, Self::DeviceId> + IcmpHandlerIpExt,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
B: BufferMut,
SC: BufferTransportContext<I, C, B>
+ BufferIpDeviceContext<I, C, B>
+ BufferIcmpHandler<I, C, B>
+ IpLayerContext<I, C>
+ FragmentHandler<I, C>,
> BufferIpLayerContext<I, C, B> for SC
{
}
impl<
C: IpLayerNonSyncContext<Ipv4, SC::DeviceId>,
SC: BufferIpTransportLayerContext<Ipv4, C, B> + IgmpPacketHandler<C, SC::DeviceId, B>,
B: BufferMut,
> BufferTransportContext<Ipv4, C, B> for SC
where
IcmpIpTransportContext: BufferIpTransportContext<Ipv4, C, SC, B>,
{
fn dispatch_receive_ip_packet(
&mut self,
ctx: &mut C,
device: SC::DeviceId,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
proto: Ipv4Proto,
body: B,
) -> Result<(), (B, TransportReceiveError)> {
// TODO(https://fxbug.dev/93955): Deliver the packet to interested raw
// sockets.
match proto {
Ipv4Proto::Icmp => <IcmpIpTransportContext as BufferIpTransportContext<
Ipv4,
_,
_,
_,
>>::receive_ip_packet(
self, ctx, device, src_ip, dst_ip, body
),
Ipv4Proto::Igmp => {
IgmpPacketHandler::receive_igmp_packet(self, ctx, device, src_ip, dst_ip, body);
Ok(())
}
Ipv4Proto::Proto(IpProto::Udp) => {
<<SC as BufferIpTransportLayerContext<_, _, _>>::Udp as BufferIpTransportContext<
Ipv4,
_,
_,
_,
>>::receive_ip_packet(self, ctx, device, src_ip, dst_ip, body)
}
Ipv4Proto::Proto(IpProto::Tcp) => {
<<SC as BufferIpTransportLayerContext<_, _, _>>::Tcp as BufferIpTransportContext<
Ipv4,
_,
_,
_,
>>::receive_ip_packet(self, ctx, device, src_ip, dst_ip, body)
}
// TODO(joshlf): Once all IP protocol numbers are covered, remove
// this default case.
_ => Err((
body,
TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported },
)),
}
}
}
impl<
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
SC: BufferIpTransportLayerContext<Ipv6, C, B>,
B: BufferMut,
> BufferTransportContext<Ipv6, C, B> for SC
where
IcmpIpTransportContext: BufferIpTransportContext<Ipv6, C, SC, B>,
{
fn dispatch_receive_ip_packet(
&mut self,
ctx: &mut C,
device: SC::DeviceId,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
proto: Ipv6Proto,
body: B,
) -> Result<(), (B, TransportReceiveError)> {
// TODO(https://fxbug.dev/93955): Deliver the packet to interested raw
// sockets.
match proto {
Ipv6Proto::Icmpv6 => <IcmpIpTransportContext as BufferIpTransportContext<
Ipv6,
_,
_,
_,
>>::receive_ip_packet(
self, ctx, device, src_ip, dst_ip, body
),
// A value of `Ipv6Proto::NoNextHeader` tells us that there is no
// header whatsoever following the last lower-level header so we stop
// processing here.
Ipv6Proto::NoNextHeader => Ok(()),
Ipv6Proto::Proto(IpProto::Tcp) => {
<<SC as BufferIpTransportLayerContext<_, _, _>>::Tcp as BufferIpTransportContext<
Ipv6,
_,
_,
_,
>>::receive_ip_packet(self, ctx, device, src_ip, dst_ip, body)
}
Ipv6Proto::Proto(IpProto::Udp) => {
<<SC as BufferIpTransportLayerContext<_, _, _>>::Udp as BufferIpTransportContext<
Ipv6,
_,
_,
_,
>>::receive_ip_packet(self, ctx, device, src_ip, dst_ip, body)
}
// TODO(joshlf): Once all IP Next Header numbers are covered, remove
// this default case.
_ => Err((
body,
TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported },
)),
}
}
}
/// A dummy device ID for use in testing.
///
/// `DummyDeviceId` is provided for use in implementing
/// `IpDeviceIdContext::DeviceId` in tests. Unlike `()`, it implements the
/// `Display` trait, which is a requirement of `IpDeviceIdContext::DeviceId`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg(test)]
pub(crate) struct DummyDeviceId;
#[cfg(test)]
impl IpDeviceId for DummyDeviceId {
fn is_loopback(&self) -> bool {
false
}
}
#[cfg(test)]
impl<I: Ip, S, Meta, D: IpDeviceId + 'static> IpDeviceIdContext<I>
for crate::context::testutil::DummySyncCtx<S, Meta, D>
{
type DeviceId = D;
fn loopback_id(&self) -> Option<Self::DeviceId> {
None
}
}
#[cfg(test)]
impl Display for DummyDeviceId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "DummyDeviceId")
}
}
/// A builder for IPv4 state.
#[derive(Copy, Clone, Default)]
pub struct Ipv4StateBuilder {
icmp: Icmpv4StateBuilder,
}
impl Ipv4StateBuilder {
/// Get the builder for the ICMPv4 state.
pub fn icmpv4_builder(&mut self) -> &mut Icmpv4StateBuilder {
&mut self.icmp
}
pub(crate) fn build<Instant: crate::Instant, D>(self) -> Ipv4State<Instant, D> {
let Ipv4StateBuilder { icmp } = self;
Ipv4State {
inner: IpStateInner {
table: ForwardingTable::default(),
fragment_cache: IpPacketFragmentCache::default(),
pmtu_cache: PmtuCache::default(),
},
icmp: icmp.build(),
next_packet_id: 0,
}
}
}
/// A builder for IPv6 state.
#[derive(Copy, Clone, Default)]
pub struct Ipv6StateBuilder {
icmp: Icmpv6StateBuilder,
}
impl Ipv6StateBuilder {
/// Get the builder for the ICMPv6 state.
pub fn icmpv6_builder(&mut self) -> &mut Icmpv6StateBuilder {
&mut self.icmp
}
pub(crate) fn build<Instant: crate::Instant, D>(self) -> Ipv6State<Instant, D> {
let Ipv6StateBuilder { icmp } = self;
Ipv6State {
inner: IpStateInner {
table: ForwardingTable::default(),
fragment_cache: IpPacketFragmentCache::default(),
pmtu_cache: PmtuCache::default(),
},
icmp: icmp.build(),
}
}
}
pub(crate) struct Ipv4State<Instant: crate::Instant, D> {
inner: IpStateInner<Ipv4, Instant, D>,
icmp: Icmpv4State<Instant, IpSock<Ipv4, D>>,
next_packet_id: u16,
}
impl<I: Instant, DeviceId> AsRef<IpStateInner<Ipv4, I, DeviceId>> for Ipv4State<I, DeviceId> {
fn as_ref(&self) -> &IpStateInner<Ipv4, I, DeviceId> {
&self.inner
}
}
impl<I: Instant, DeviceId> AsMut<IpStateInner<Ipv4, I, DeviceId>> for Ipv4State<I, DeviceId> {
fn as_mut(&mut self) -> &mut IpStateInner<Ipv4, I, DeviceId> {
&mut self.inner
}
}
fn gen_ipv4_packet_id<I: Instant, C: IpStateContext<Ipv4, I>>(sync_ctx: &mut C) -> u16 {
// TODO(https://fxbug.dev/87588): Generate IPv4 IDs unpredictably
let state = sync_ctx.get_ip_layer_state_mut();
state.next_packet_id = state.next_packet_id.wrapping_add(1);
state.next_packet_id
}
pub(crate) struct Ipv6State<Instant: crate::Instant, D> {
inner: IpStateInner<Ipv6, Instant, D>,
icmp: Icmpv6State<Instant, IpSock<Ipv6, D>>,
}
impl<I: Instant, DeviceId> AsRef<IpStateInner<Ipv6, I, DeviceId>> for Ipv6State<I, DeviceId> {
fn as_ref(&self) -> &IpStateInner<Ipv6, I, DeviceId> {
&self.inner
}
}
impl<I: Instant, DeviceId> AsMut<IpStateInner<Ipv6, I, DeviceId>> for Ipv6State<I, DeviceId> {
fn as_mut(&mut self) -> &mut IpStateInner<Ipv6, I, DeviceId> {
&mut self.inner
}
}
pub(crate) struct IpStateInner<I: Ip, Instant: crate::Instant, DeviceId> {
table: ForwardingTable<I, DeviceId>,
fragment_cache: IpPacketFragmentCache<I, Instant>,
pmtu_cache: PmtuCache<I, Instant>,
}
pub(crate) trait GetStateIpExt: Ip {
fn get_state_inner<Instant: crate::Instant>(
state: &StackState<Instant>,
) -> &IpStateInner<Self, Instant, DeviceId>;
fn get_state_inner_mut<Instant: crate::Instant>(
state: &mut StackState<Instant>,
) -> &mut IpStateInner<Self, Instant, DeviceId>;
}
impl GetStateIpExt for Ipv4 {
fn get_state_inner<Instant: crate::Instant>(
state: &StackState<Instant>,
) -> &IpStateInner<Self, Instant, DeviceId> {
return &state.ipv4.inner;
}
fn get_state_inner_mut<Instant: crate::Instant>(
state: &mut StackState<Instant>,
) -> &mut IpStateInner<Self, Instant, DeviceId> {
return &mut state.ipv4.inner;
}
}
impl GetStateIpExt for Ipv6 {
fn get_state_inner<Instant: crate::Instant>(
state: &StackState<Instant>,
) -> &IpStateInner<Self, Instant, DeviceId> {
return &state.ipv6.inner;
}
fn get_state_inner_mut<Instant: crate::Instant>(
state: &mut StackState<Instant>,
) -> &mut IpStateInner<Self, Instant, DeviceId> {
return &mut state.ipv6.inner;
}
}
/// The identifier for timer events in the IP layer.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) enum IpLayerTimerId {
/// A timer event for IPv4 packet reassembly timers.
ReassemblyTimeoutv4(FragmentCacheKey<Ipv4Addr>),
/// A timer event for IPv6 packet reassembly timers.
ReassemblyTimeoutv6(FragmentCacheKey<Ipv6Addr>),
/// A timer event for IPv4 path MTU discovery.
PmtuTimeoutv4(PmtuTimerId<Ipv4>),
/// A timer event for IPv6 path MTU discovery.
PmtuTimeoutv6(PmtuTimerId<Ipv6>),
}
impl From<FragmentCacheKey<Ipv4Addr>> for IpLayerTimerId {
fn from(timer: FragmentCacheKey<Ipv4Addr>) -> IpLayerTimerId {
IpLayerTimerId::ReassemblyTimeoutv4(timer)
}
}
impl From<FragmentCacheKey<Ipv6Addr>> for IpLayerTimerId {
fn from(timer: FragmentCacheKey<Ipv6Addr>) -> IpLayerTimerId {
IpLayerTimerId::ReassemblyTimeoutv6(timer)
}
}
impl From<PmtuTimerId<Ipv4>> for IpLayerTimerId {
fn from(timer: PmtuTimerId<Ipv4>) -> IpLayerTimerId {
IpLayerTimerId::PmtuTimeoutv4(timer)
}
}
impl From<PmtuTimerId<Ipv6>> for IpLayerTimerId {
fn from(timer: PmtuTimerId<Ipv6>) -> IpLayerTimerId {
IpLayerTimerId::PmtuTimeoutv6(timer)
}
}
impl_timer_context!(
IpLayerTimerId,
FragmentCacheKey<Ipv4Addr>,
IpLayerTimerId::ReassemblyTimeoutv4(id),
id
);
impl_timer_context!(
IpLayerTimerId,
FragmentCacheKey<Ipv6Addr>,
IpLayerTimerId::ReassemblyTimeoutv6(id),
id
);
impl_timer_context!(IpLayerTimerId, PmtuTimerId<Ipv4>, IpLayerTimerId::PmtuTimeoutv4(id), id);
impl_timer_context!(IpLayerTimerId, PmtuTimerId<Ipv6>, IpLayerTimerId::PmtuTimeoutv6(id), id);
/// Handle a timer event firing in the IP layer.
pub(crate) fn handle_timer<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
id: IpLayerTimerId,
) {
match id {
IpLayerTimerId::ReassemblyTimeoutv4(key) => TimerHandler::handle_timer(sync_ctx, ctx, key),
IpLayerTimerId::ReassemblyTimeoutv6(key) => TimerHandler::handle_timer(sync_ctx, ctx, key),
IpLayerTimerId::PmtuTimeoutv4(id) => TimerHandler::handle_timer(sync_ctx, ctx, id),
IpLayerTimerId::PmtuTimeoutv6(id) => TimerHandler::handle_timer(sync_ctx, ctx, id),
}
}
// TODO(joshlf): Once we support multiple extension headers in IPv6, we will
// need to verify that the callers of this function are still sound. In
// particular, they may accidentally pass a parse_metadata argument which
// corresponds to a single extension header rather than all of the IPv6 headers.
/// Dispatch a received IPv4 packet to the appropriate protocol.
///
/// `device` is the device the packet was received on. `parse_metadata` is the
/// parse metadata associated with parsing the IP headers. It is used to undo
/// that parsing. Both `device` and `parse_metadata` are required in order to
/// send ICMP messages in response to unrecognized protocols or ports. If either
/// of `device` or `parse_metadata` is `None`, the caller promises that the
/// protocol and port are recognized.
///
/// # Panics
///
/// `dispatch_receive_ipv4_packet` panics if the protocol is unrecognized and
/// `parse_metadata` is `None`. If an IGMP message is received but it is not
/// coming from a device, i.e., `device` given is `None`,
/// `dispatch_receive_ip_packet` will also panic.
fn dispatch_receive_ipv4_packet<
C: IpLayerNonSyncContext<Ipv4, SC::DeviceId>,
B: BufferMut,
SC: BufferIpLayerContext<Ipv4, C, B>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: <SC as IpDeviceIdContext<Ipv4>>::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
proto: Ipv4Proto,
body: B,
parse_metadata: Option<ParseMetadata>,
) {
ctx.increment_counter("dispatch_receive_ipv4_packet");
let (mut body, err) =
match sync_ctx.dispatch_receive_ip_packet(ctx, device, src_ip, dst_ip, proto, body) {
Ok(()) => return,
Err(e) => e,
};
// All branches promise to return the buffer in the same state it was in
// when they were executed. Thus, all we have to do is undo the parsing
// of the IP packet header, and the buffer will be back to containing
// the entire original IP packet.
let meta = parse_metadata.unwrap();
body.undo_parse(meta);
if let Some(src_ip) = SpecifiedAddr::new(src_ip) {
match err.inner {
TransportReceiveErrorInner::ProtocolUnsupported => {
sync_ctx.send_icmp_error_message(
ctx,
device,
frame_dst,
src_ip,
dst_ip,
body,
Icmpv4Error {
kind: Icmpv4ErrorKind::ProtocolUnreachable,
header_len: meta.header_len(),
},
);
}
TransportReceiveErrorInner::PortUnreachable => {
// TODO(joshlf): What if we're called from a loopback
// handler, and device and parse_metadata are None? In other
// words, what happens if we attempt to send to a loopback
// port which is unreachable? We will eventually need to
// restructure the control flow here to handle that case.
sync_ctx.send_icmp_error_message(
ctx,
device,
frame_dst,
src_ip,
dst_ip,
body,
Icmpv4Error {
kind: Icmpv4ErrorKind::PortUnreachable,
header_len: meta.header_len(),
},
);
}
}
} else {
trace!("dispatch_receive_ipv4_packet: Cannot send ICMP error message in response to a packet from the unspecified address");
}
}
/// Dispatch a received IPv6 packet to the appropriate protocol.
///
/// `dispatch_receive_ipv6_packet` has the same semantics as
/// `dispatch_receive_ipv4_packet`, but for IPv6.
fn dispatch_receive_ipv6_packet<
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
B: BufferMut,
SC: BufferIpLayerContext<Ipv6, C, B>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: <SC as IpDeviceIdContext<Ipv6>>::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
proto: Ipv6Proto,
body: B,
parse_metadata: Option<ParseMetadata>,
) {
// TODO(https://fxbug.dev/21227): Once we support multiple extension
// headers in IPv6, we will need to verify that the callers of this
// function are still sound. In particular, they may accidentally pass a
// parse_metadata argument which corresponds to a single extension
// header rather than all of the IPv6 headers.
ctx.increment_counter("dispatch_receive_ipv6_packet");
let (mut body, err) =
match sync_ctx.dispatch_receive_ip_packet(ctx, device, src_ip, dst_ip, proto, body) {
Ok(()) => return,
Err(e) => e,
};
// All branches promise to return the buffer in the same state it was in
// when they were executed. Thus, all we have to do is undo the parsing
// of the IP packet header, and the buffer will be back to containing
// the entire original IP packet.
let meta = parse_metadata.unwrap();
body.undo_parse(meta);
match err.inner {
TransportReceiveErrorInner::ProtocolUnsupported => {
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
sync_ctx.send_icmp_error_message(
ctx,
device,
frame_dst,
src_ip,
dst_ip,
body,
Icmpv6ErrorKind::ProtocolUnreachable { header_len: meta.header_len() },
);
}
}
TransportReceiveErrorInner::PortUnreachable => {
// TODO(joshlf): What if we're called from a loopback handler,
// and device and parse_metadata are None? In other words, what
// happens if we attempt to send to a loopback port which is
// unreachable? We will eventually need to restructure the
// control flow here to handle that case.
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
sync_ctx.send_icmp_error_message(
ctx,
device,
frame_dst,
src_ip,
dst_ip,
body,
Icmpv6ErrorKind::PortUnreachable,
);
}
}
}
}
/// Drop a packet and undo the effects of parsing it.
///
/// `drop_packet_and_undo_parse!` takes a `$packet` and a `$buffer` which the
/// packet was parsed from. It saves the results of the `src_ip()`, `dst_ip()`,
/// `proto()`, and `parse_metadata()` methods. It drops `$packet` and uses the
/// result of `parse_metadata()` to undo the effects of parsing the packet.
/// Finally, it returns the source IP, destination IP, protocol, and parse
/// metadata.
macro_rules! drop_packet_and_undo_parse {
($packet:expr, $buffer:expr) => {{
let (src_ip, dst_ip, proto, meta) = $packet.into_metadata();
$buffer.undo_parse(meta);
(src_ip, dst_ip, proto, meta)
}};
}
/// Process a fragment and reassemble if required.
///
/// Attempts to process a potential fragment packet and reassemble if we are
/// ready to do so. If the packet isn't fragmented, or a packet was reassembled,
/// attempt to dispatch the packet.
macro_rules! process_fragment {
($sync_ctx:expr, $ctx:expr, $dispatch:ident, $device:ident, $frame_dst:expr, $buffer:expr, $packet:expr, $src_ip:expr, $dst_ip:expr, $ip:ident) => {{
match FragmentHandler::<$ip, _>::process_fragment::<&mut [u8]>(
$sync_ctx,
$ctx,
$packet,
) {
// Handle the packet right away since reassembly is not needed.
FragmentProcessingState::NotNeeded(packet) => {
trace!("receive_ip_packet: not fragmented");
// TODO(joshlf):
// - Check for already-expired TTL?
let (_, _, proto, meta) = packet.into_metadata();
$dispatch(
$sync_ctx,
$ctx,
$device,
$frame_dst,
$src_ip,
$dst_ip,
proto,
$buffer,
Some(meta),
);
}
// Ready to reassemble a packet.
FragmentProcessingState::Ready { key, packet_len } => {
trace!("receive_ip_packet: fragmented, ready for reassembly");
// Allocate a buffer of `packet_len` bytes.
let mut buffer = Buf::new(alloc::vec![0; packet_len], ..);
// Attempt to reassemble the packet.
match FragmentHandler::<$ip, _>::reassemble_packet(
$sync_ctx,
$ctx,
&key,
buffer.buffer_view_mut(),
) {
// Successfully reassembled the packet, handle it.
Ok(packet) => {
trace!("receive_ip_packet: fragmented, reassembled packet: {:?}", packet);
// TODO(joshlf):
// - Check for already-expired TTL?
let (_, _, proto, meta) = packet.into_metadata();
$dispatch::<_, Buf<Vec<u8>>, _>(
$sync_ctx,
$ctx,
$device,
$frame_dst,
$src_ip,
$dst_ip,
proto,
buffer,
Some(meta),
);
}
// TODO(ghanan): Handle reassembly errors, remove
// `allow(unreachable_patterns)` when complete.
_ => return,
#[allow(unreachable_patterns)]
Err(e) => {
trace!("receive_ip_packet: fragmented, failed to reassemble: {:?}", e);
}
}
}
// Cannot proceed since we need more fragments before we
// can reassemble a packet.
FragmentProcessingState::NeedMoreFragments => {
trace!("receive_ip_packet: fragmented, need more before reassembly")
}
// TODO(ghanan): Handle invalid fragments.
FragmentProcessingState::InvalidFragment => {
trace!("receive_ip_packet: fragmented, invalid")
}
FragmentProcessingState::OutOfMemory => {
trace!("receive_ip_packet: fragmented, dropped because OOM")
}
};
}};
}
// TODO(joshlf): Can we turn `try_parse_ip_packet` into a function? So far, I've
// been unable to get the borrow checker to accept it.
/// Try to parse an IP packet from a buffer.
///
/// If parsing fails, return the buffer to its original state so that its
/// contents can be used to send an ICMP error message. When invoked, the macro
/// expands to an expression whose type is `Result<P, P::Error>`, where `P` is
/// the parsed packet type.
macro_rules! try_parse_ip_packet {
($buffer:expr) => {{
let p_len = $buffer.prefix_len();
let s_len = $buffer.suffix_len();
let result = $buffer.parse_mut();
if let Err(err) = result {
// Revert `buffer` to it's original state.
let n_p_len = $buffer.prefix_len();
let n_s_len = $buffer.suffix_len();
if p_len > n_p_len {
$buffer.grow_front(p_len - n_p_len);
}
if s_len > n_s_len {
$buffer.grow_back(s_len - n_s_len);
}
Err(err)
} else {
result
}
}};
}
/// Receive an IP packet from a device.
///
/// `receive_ip_packet` calls [`receive_ipv4_packet`] or [`receive_ipv6_packet`]
/// depending on the type parameter, `I`.
pub(crate) fn receive_ip_packet<B: BufferMut, NonSyncCtx: BufferNonSyncContext<B>, I: Ip>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
frame_dst: FrameDestination,
buffer: B,
) {
match I::VERSION {
IpVersion::V4 => receive_ipv4_packet(sync_ctx, ctx, device, frame_dst, buffer),
IpVersion::V6 => receive_ipv6_packet(sync_ctx, ctx, device, frame_dst, buffer),
}
}
/// Receive an IPv4 packet from a device.
///
/// `frame_dst` specifies whether this packet was received in a broadcast or
/// unicast link-layer frame.
pub(crate) fn receive_ipv4_packet<
C: IpLayerNonSyncContext<Ipv4, SC::DeviceId>,
B: BufferMut,
SC: BufferIpLayerContext<Ipv4, C, B> + BufferIpLayerContext<Ipv4, C, Buf<Vec<u8>>>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
frame_dst: FrameDestination,
mut buffer: B,
) {
if !sync_ctx.is_ip_device_enabled(device) {
return;
}
ctx.increment_counter("receive_ipv4_packet");
trace!("receive_ip_packet({})", device);
let mut packet: Ipv4Packet<_> = match try_parse_ip_packet!(buffer) {
Ok(packet) => packet,
// Conditionally send an ICMP response if we encountered a parameter
// problem error when parsing an IPv4 packet. Note, we do not always
// send back an ICMP response as it can be used as an attack vector for
// DDoS attacks. We only send back an ICMP response if the RFC requires
// that we MUST send one, as noted by `must_send_icmp` and `action`.
// TODO(https://fxbug.dev/77598): test this code path once
// `Ipv4Packet::parse` can return an `IpParseError::ParameterProblem`
// error.
Err(IpParseError::ParameterProblem {
src_ip,
dst_ip,
code,
pointer,
must_send_icmp,
header_len,
action,
}) if must_send_icmp && action.should_send_icmp(&dst_ip) => {
// `should_send_icmp_to_multicast` should never return `true` for IPv4.
assert!(!action.should_send_icmp_to_multicast());
let dst_ip = match SpecifiedAddr::new(dst_ip) {
Some(ip) => ip,
None => {
debug!("receive_ipv4_packet: Received packet with unspecified destination IP address; dropping");
return;
}
};
let src_ip = match SpecifiedAddr::new(src_ip) {
Some(ip) => ip,
None => {
trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address");
return;
}
};
BufferIcmpHandler::<Ipv4, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv4Error {
kind: Icmpv4ErrorKind::ParameterProblem {
code,
pointer,
// When the call to `action.should_send_icmp` returns true, it always means that
// the IPv4 packet that failed parsing is an initial fragment.
fragment_type: Ipv4FragmentType::InitialFragment,
},
header_len,
},
);
return;
}
_ => return, // TODO(joshlf): Do something with ICMP here?
};
let dst_ip = match SpecifiedAddr::new(packet.dst_ip()) {
Some(ip) => ip,
None => {
debug!("receive_ipv4_packet: Received packet with unspecified destination IP address; dropping");
return;
}
};
// TODO(ghanan): Act upon options.
match receive_ipv4_packet_action(sync_ctx, ctx, device, dst_ip) {
ReceivePacketAction::Deliver => {
trace!("receive_ipv4_packet: delivering locally");
let src_ip = packet.src_ip();
// Process a potential IPv4 fragment if the destination is this
// host.
//
// We process IPv4 packet reassembly here because, for IPv4, the
// fragment data is in the header itself so we can handle it right
// away.
//
// Note, the `process_fragment` function (which is called by the
// `process_fragment!` macro) could panic if the packet does not
// have fragment data. However, we are guaranteed that it will not
// panic because the fragment data is in the fixed header so it is
// always present (even if the fragment data has values that implies
// that the packet is not fragmented).
process_fragment!(
sync_ctx,
ctx,
dispatch_receive_ipv4_packet,
device,
frame_dst,
buffer,
packet,
src_ip,
dst_ip,
Ipv4
);
}
ReceivePacketAction::Forward { dst } => {
let ttl = packet.ttl();
if ttl > 1 {
trace!("receive_ipv4_packet: forwarding");
packet.set_ttl(ttl - 1);
let _: (Ipv4Addr, Ipv4Addr, Ipv4Proto, ParseMetadata) =
drop_packet_and_undo_parse!(packet, buffer);
match BufferIpDeviceContext::<Ipv4, _, _>::send_ip_frame(
sync_ctx,
ctx,
dst.device,
dst.next_hop,
buffer,
) {
Ok(()) => (),
Err(b) => {
let _: B = b;
// TODO(https://fxbug.dev/86247): Encode the MTU error
// more obviously in the type system.
debug!("failed to forward IPv4 packet: MTU exceeded");
}
}
} else {
// TTL is 0 or would become 0 after decrement; see "TTL"
// section, https://tools.ietf.org/html/rfc791#page-14
use packet_formats::ipv4::Ipv4Header as _;
debug!("received IPv4 packet dropped due to expired TTL");
let fragment_type = packet.fragment_type();
let (src_ip, _, proto, meta): (_, Ipv4Addr, _, _) =
drop_packet_and_undo_parse!(packet, buffer);
let src_ip = match SpecifiedAddr::new(src_ip) {
Some(ip) => ip,
None => {
trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address");
return;
}
};
BufferIcmpHandler::<Ipv4, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv4Error {
kind: Icmpv4ErrorKind::TtlExpired { proto, fragment_type },
header_len: meta.header_len(),
},
);
}
}
ReceivePacketAction::SendNoRouteToDest => {
use packet_formats::ipv4::Ipv4Header as _;
debug!("received IPv4 packet with no known route to destination {}", dst_ip);
let fragment_type = packet.fragment_type();
let (src_ip, _, proto, meta): (_, Ipv4Addr, _, _) =
drop_packet_and_undo_parse!(packet, buffer);
let src_ip = match SpecifiedAddr::new(src_ip) {
Some(ip) => ip,
None => {
trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address");
return;
}
};
BufferIcmpHandler::<Ipv4, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv4Error {
kind: Icmpv4ErrorKind::NetUnreachable { proto, fragment_type },
header_len: meta.header_len(),
},
);
}
ReceivePacketAction::Drop { reason } => {
debug!(
"receive_ipv4_packet: dropping packet from {} to {} received on {}: {}",
packet.src_ip(),
dst_ip,
device,
reason
);
}
}
}
/// Receive an IPv6 packet from a device.
///
/// `frame_dst` specifies whether this packet was received in a broadcast or
/// unicast link-layer frame.
pub(crate) fn receive_ipv6_packet<
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
B: BufferMut,
SC: BufferIpLayerContext<Ipv6, C, B> + BufferIpLayerContext<Ipv6, C, Buf<Vec<u8>>>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
frame_dst: FrameDestination,
mut buffer: B,
) {
if !sync_ctx.is_ip_device_enabled(device) {
return;
}
ctx.increment_counter("receive_ipv6_packet");
trace!("receive_ipv6_packet({})", device);
let mut packet: Ipv6Packet<_> = match try_parse_ip_packet!(buffer) {
Ok(packet) => packet,
// Conditionally send an ICMP response if we encountered a parameter
// problem error when parsing an IPv4 packet. Note, we do not always
// send back an ICMP response as it can be used as an attack vector for
// DDoS attacks. We only send back an ICMP response if the RFC requires
// that we MUST send one, as noted by `must_send_icmp` and `action`.
Err(IpParseError::ParameterProblem {
src_ip,
dst_ip,
code,
pointer,
must_send_icmp,
header_len: _,
action,
}) if must_send_icmp && action.should_send_icmp(&dst_ip) => {
let dst_ip = match SpecifiedAddr::new(dst_ip) {
Some(ip) => ip,
None => {
debug!("receive_ipv6_packet: Received packet with unspecified destination IP address; dropping");
return;
}
};
let src_ip = match UnicastAddr::new(src_ip) {
Some(ip) => ip,
None => {
trace!("receive_ipv6_packet: Cannot send ICMP error in response to packet with non unicast source IP address");
return;
}
};
BufferIcmpHandler::<Ipv6, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv6ErrorKind::ParameterProblem {
code,
pointer,
allow_dst_multicast: action.should_send_icmp_to_multicast(),
},
);
return;
}
_ => return, // TODO(joshlf): Do something with ICMP here?
};
trace!("receive_ipv6_packet: parsed packet: {:?}", packet);
// TODO(ghanan): Act upon extension headers.
let src_ip = match packet.src_ipv6() {
Some(ip) => ip,
None => {
debug!(
"receive_ipv6_packet: received packet from non-unicast source {}; dropping",
packet.src_ip()
);
ctx.increment_counter("receive_ipv6_packet: non-unicast source");
return;
}
};
let dst_ip = match SpecifiedAddr::new(packet.dst_ip()) {
Some(ip) => ip,
None => {
debug!("receive_ipv6_packet: Received packet with unspecified destination IP address; dropping");
return;
}
};
match receive_ipv6_packet_action(sync_ctx, ctx, device, dst_ip) {
ReceivePacketAction::Deliver => {
trace!("receive_ipv6_packet: delivering locally");
// Process a potential IPv6 fragment if the destination is this
// host.
//
// We need to process extension headers in the order they appear in
// the header. With some extension headers, we do not proceed to the
// next header, and do some action immediately. For example, say we
// have an IPv6 packet with two extension headers (routing extension
// header before a fragment extension header). Until we get to the
// final destination node in the routing header, we would need to
// reroute the packet to the next destination without reassembling.
// Once the packet gets to the last destination in the routing
// header, that node will process the fragment extension header and
// handle reassembly.
match ipv6::handle_extension_headers(sync_ctx, device, frame_dst, &packet, true) {
Ipv6PacketAction::_Discard => {
trace!(
"receive_ipv6_packet: handled IPv6 extension headers: discarding packet"
);
}
Ipv6PacketAction::Continue => {
trace!(
"receive_ipv6_packet: handled IPv6 extension headers: dispatching packet"
);
// TODO(joshlf):
// - Do something with ICMP if we don't have a handler for
// that protocol?
// - Check for already-expired TTL?
let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) = packet.into_metadata();
dispatch_receive_ipv6_packet(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
buffer,
Some(meta),
);
}
Ipv6PacketAction::ProcessFragment => {
trace!(
"receive_ipv6_packet: handled IPv6 extension headers: handling fragmented packet"
);
// Note, the `IpPacketFragmentCache::process_fragment`
// method (which is called by the `process_fragment!` macro)
// could panic if the packet does not have fragment data.
// However, we are guaranteed that it will not panic for an
// IPv6 packet because the fragment data is in an (optional)
// fragment extension header which we attempt to handle by
// calling `ipv6::handle_extension_headers`. We will only
// end up here if its return value is
// `Ipv6PacketAction::ProcessFragment` which is only
// possible when the packet has the fragment extension
// header (even if the fragment data has values that implies
// that the packet is not fragmented).
//
// TODO(ghanan): Handle extension headers again since there
// could be some more in a reassembled packet
// (after the fragment header).
process_fragment!(
sync_ctx,
ctx,
dispatch_receive_ipv6_packet,
device,
frame_dst,
buffer,
packet,
src_ip,
dst_ip,
Ipv6
);
}
}
}
ReceivePacketAction::Forward { dst } => {
ctx.increment_counter("receive_ipv6_packet::forward");
let ttl = packet.ttl();
if ttl > 1 {
trace!("receive_ipv6_packet: forwarding");
// Handle extension headers first.
match ipv6::handle_extension_headers(sync_ctx, device, frame_dst, &packet, false) {
Ipv6PacketAction::_Discard => {
trace!("receive_ipv6_packet: handled IPv6 extension headers: discarding packet");
return;
}
Ipv6PacketAction::Continue => {
trace!("receive_ipv6_packet: handled IPv6 extension headers: forwarding packet");
}
Ipv6PacketAction::ProcessFragment => unreachable!("When forwarding packets, we should only ever look at the hop by hop options extension header (if present)"),
}
packet.set_ttl(ttl - 1);
let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) =
drop_packet_and_undo_parse!(packet, buffer);
if let Err(buffer) = BufferIpDeviceContext::<Ipv6, _, _>::send_ip_frame(
sync_ctx,
ctx,
dst.device,
dst.next_hop,
buffer,
) {
// TODO(https://fxbug.dev/86247): Encode the MTU error more
// obviously in the type system.
debug!("failed to forward IPv6 packet: MTU exceeded");
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
trace!("receive_ipv6_packet: Sending ICMPv6 Packet Too Big");
// TODO(joshlf): Increment the TTL since we just
// decremented it. The fact that we don't do this is
// technically a violation of the ICMP spec (we're not
// encapsulating the original packet that caused the
// issue, but a slightly modified version of it), but
// it's not that big of a deal because it won't affect
// the sender's ability to figure out the minimum path
// MTU. This may break other logic, though, so we should
// still fix it eventually.
let mtu = sync_ctx.get_mtu(device);
BufferIcmpHandler::<Ipv6, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv6ErrorKind::PacketTooBig {
proto,
header_len: meta.header_len(),
mtu,
},
);
}
}
} else {
// Hop Limit is 0 or would become 0 after decrement; see RFC
// 2460 Section 3.
debug!("received IPv6 packet dropped due to expired Hop Limit");
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) =
drop_packet_and_undo_parse!(packet, buffer);
BufferIcmpHandler::<Ipv6, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv6ErrorKind::TtlExpired { proto, header_len: meta.header_len() },
);
}
}
}
ReceivePacketAction::SendNoRouteToDest => {
let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) =
drop_packet_and_undo_parse!(packet, buffer);
debug!("received IPv6 packet with no known route to destination {}", dst_ip);
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
BufferIcmpHandler::<Ipv6, _, _>::send_icmp_error_message(
sync_ctx,
ctx,
device,
frame_dst,
src_ip,
dst_ip,
buffer,
Icmpv6ErrorKind::NetUnreachable { proto, header_len: meta.header_len() },
);
}
}
ReceivePacketAction::Drop { reason } => {
ctx.increment_counter("receive_ipv6_packet::drop");
debug!(
"receive_ipv6_packet: dropping packet from {} to {} received on {}: {}",
packet.src_ip(),
dst_ip,
device,
reason
);
}
}
}
/// The action to take in order to process a received IP packet.
#[cfg_attr(test, derive(Debug, PartialEq))]
enum ReceivePacketAction<A: IpAddress, DeviceId> {
/// Deliver the packet locally.
Deliver,
/// Forward the packet to the given destination.
Forward { dst: Destination<A, DeviceId> },
/// Send a Destination Unreachable ICMP error message to the packet's sender
/// and drop the packet.
///
/// For ICMPv4, use the code "net unreachable". For ICMPv6, use the code "no
/// route to destination".
SendNoRouteToDest,
/// Silently drop the packet.
///
/// `reason` describes why the packet was dropped. Its `Display` impl
/// provides a human-readable description which is intended for use in
/// logging.
Drop { reason: DropReason },
}
#[cfg_attr(test, derive(Debug, PartialEq))]
enum DropReason {
Tentative,
ForwardingDisabledInboundIface,
}
impl Display for DropReason {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
DropReason::Tentative => "remote packet destined to tentative address",
DropReason::ForwardingDisabledInboundIface => {
"packet should be forwarded but packet's inbound interface has forwarding disabled"
}
}
)
}
}
/// Computes the action to take in order to process a received IPv4 packet.
fn receive_ipv4_packet_action<
C: IpLayerNonSyncContext<Ipv4, SC::DeviceId>,
SC: IpLayerContext<Ipv4, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
dst_ip: SpecifiedAddr<Ipv4Addr>,
) -> ReceivePacketAction<Ipv4Addr, SC::DeviceId> {
// If the packet arrived at the loopback interface, check if any local
// interface has the destination address assigned. This effectively lets
// the loopback interface operate as a weak host for incoming packets.
//
// Note that (as of writing) the stack sends all locally destined traffic to
// the loopback interface so we need this hack to allow the stack to accept
// packets that arrive at the loopback interface (after being looped back)
// but destined to an address that is assigned to another local interface.
//
// TODO(https://fxbug.dev/93870): This should instead be controlled by the
// routing table.
let address_status = if device.is_loopback() {
sync_ctx.address_status(dst_ip).drop_device()
} else {
sync_ctx.address_status_for_device(dst_ip, device)
};
match address_status {
AddressStatus::Present(state) => match state {
Ipv4PresentAddressStatus::LimitedBroadcast
| Ipv4PresentAddressStatus::SubnetBroadcast
| Ipv4PresentAddressStatus::Multicast
| Ipv4PresentAddressStatus::Unicast => {
ctx.increment_counter("receive_ipv4_packet_action::deliver");
ReceivePacketAction::Deliver
}
},
AddressStatus::Unassigned => {
receive_ip_packet_action_common::<Ipv4, _, _>(sync_ctx, ctx, dst_ip, device)
}
}
}
/// Computes the action to take in order to process a received IPv6 packet.
fn receive_ipv6_packet_action<
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
SC: IpLayerContext<Ipv6, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
device: SC::DeviceId,
dst_ip: SpecifiedAddr<Ipv6Addr>,
) -> ReceivePacketAction<Ipv6Addr, SC::DeviceId> {
// If the packet arrived at the loopback interface, check if any local
// interface has the destination address assigned. This effectively lets
// the loopback interface operate as a weak host for incoming packets.
//
// Note that (as of writing) the stack sends all locally destined traffic to
// the loopback interface so we need this hack to allow the stack to accept
// packets that arrive at the loopback interface (after being looped back)
// but destined to an address that is assigned to another local interface.
//
// TODO(https://fxbug.dev/93870): This should instead be controlled by the
// routing table.
let address_status = if device.is_loopback() {
sync_ctx.address_status(dst_ip).drop_device()
} else {
sync_ctx.address_status_for_device(dst_ip, device)
};
match address_status {
AddressStatus::Present(Ipv6PresentAddressStatus::Multicast) => {
ctx.increment_counter("receive_ipv6_packet_action::deliver_multicast");
ReceivePacketAction::Deliver
}
AddressStatus::Present(Ipv6PresentAddressStatus::UnicastAssigned) => {
ctx.increment_counter("receive_ipv6_packet_action::deliver_unicast");
ReceivePacketAction::Deliver
}
AddressStatus::Present(Ipv6PresentAddressStatus::UnicastTentative) => {
// If the destination address is tentative (which implies that
// we are still performing NDP's Duplicate Address Detection on
// it), then we don't consider the address "assigned to an
// interface", and so we drop packets instead of delivering them
// locally.
//
// As per RFC 4862 section 5.4:
//
// An address on which the Duplicate Address Detection
// procedure is applied is said to be tentative until the
// procedure has completed successfully. A tentative address
// is not considered "assigned to an interface" in the
// traditional sense. That is, the interface must accept
// Neighbor Solicitation and Advertisement messages containing
// the tentative address in the Target Address field, but
// processes such packets differently from those whose Target
// Address matches an address assigned to the interface. Other
// packets addressed to the tentative address should be
// silently discarded. Note that the "other packets" include
// Neighbor Solicitation and Advertisement messages that have
// the tentative (i.e., unicast) address as the IP destination
// address and contain the tentative address in the Target
// Address field. Such a case should not happen in normal
// operation, though, since these messages are multicasted in
// the Duplicate Address Detection procedure.
//
// That is, we accept no packets destined to a tentative
// address. NS and NA packets should be addressed to a multicast
// address that we would have joined during DAD so that we can
// receive those packets.
ctx.increment_counter("receive_ipv6_packet_action::drop_for_tentative");
ReceivePacketAction::Drop { reason: DropReason::Tentative }
}
AddressStatus::Unassigned => {
receive_ip_packet_action_common::<Ipv6, _, _>(sync_ctx, ctx, dst_ip, device)
}
}
}
/// Computes the remaining protocol-agnostic actions on behalf of
/// [`receive_ipv4_packet_action`] and [`receive_ipv6_packet_action`].
fn receive_ip_packet_action_common<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
dst_ip: SpecifiedAddr<I::Addr>,
device_id: SC::DeviceId,
) -> ReceivePacketAction<I::Addr, SC::DeviceId> {
// The packet is not destined locally, so we attempt to forward it.
if !sync_ctx.is_device_routing_enabled(device_id) {
// Forwarding is disabled; we are operating only as a host.
//
// For IPv4, per RFC 1122 Section 3.2.1.3, "A host MUST silently discard
// an incoming datagram that is not destined for the host."
//
// For IPv6, per RFC 4443 Section 3.1, the only instance in which a host
// sends an ICMPv6 Destination Unreachable message is when a packet is
// destined to that host but on an unreachable port (Code 4 - "Port
// unreachable"). Since the only sensible error message to send in this
// case is a Destination Unreachable message, we interpret the RFC text
// to mean that, consistent with IPv4's behavior, we should silently
// discard the packet in this case.
ctx.increment_counter("receive_ip_packet_action_common::routing_disabled_per_device");
ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface }
} else {
match lookup_route(sync_ctx, ctx, None, dst_ip) {
Some(dst) => {
ctx.increment_counter("receive_ip_packet_action_common::forward");
ReceivePacketAction::Forward { dst }
}
None => {
ctx.increment_counter("receive_ip_packet_action_common::no_route_to_host");
ReceivePacketAction::SendNoRouteToDest
}
}
}
}
// Look up the route to a host.
fn lookup_route<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &SC,
_ctx: &mut C,
device: Option<SC::DeviceId>,
dst_ip: SpecifiedAddr<I::Addr>,
) -> Option<Destination<I::Addr, SC::DeviceId>> {
AsRef::<IpStateInner<_, _, _>>::as_ref(sync_ctx.get_ip_layer_state())
.table
.lookup(device, dst_ip)
}
fn get_ip_layer_state_inner_mut<
'a,
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &'a mut SC,
_ctx: &mut C,
) -> &'a mut IpStateInner<I, C::Instant, SC::DeviceId> {
sync_ctx.get_ip_layer_state_mut().as_mut()
}
/// Add a route to the forwarding table, returning `Err` if the subnet
/// is already in the table.
pub(crate) fn add_route<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
subnet: Subnet<I::Addr>,
next_hop: SpecifiedAddr<I::Addr>,
) -> Result<(), AddRouteError> {
get_ip_layer_state_inner_mut(sync_ctx, ctx).table.add_route(subnet, next_hop)
}
/// Add a device route to the forwarding table, returning `Err` if the
/// subnet is already in the table.
pub(crate) fn add_device_route<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
subnet: Subnet<I::Addr>,
device: SC::DeviceId,
) -> Result<(), ExistsError> {
get_ip_layer_state_inner_mut(sync_ctx, ctx).table.add_device_route(subnet, device).map(|()| {
ctx.on_event(IpLayerEvent::DeviceRouteAdded { device, subnet });
})
}
/// Delete a route from the forwarding table, returning `Err` if no
/// route was found to be deleted.
pub(crate) fn del_route<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
SC: IpLayerContext<I, C>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
subnet: Subnet<I::Addr>,
) -> Result<(), NotFoundError> {
get_ip_layer_state_inner_mut(sync_ctx, ctx).table.del_route(subnet).map(|removed| {
removed.into_iter().for_each(|Entry { subnet, device, gateway }| match gateway {
None => ctx.on_event(IpLayerEvent::DeviceRouteRemoved { device, subnet }),
Some(SpecifiedAddr { .. }) => (),
});
})
}
pub(crate) fn del_device_routes<
I: IpLayerStateIpExt<C::Instant, SC::DeviceId>,
SC: IpLayerContext<I, C>,
C: IpLayerNonSyncContext<I, SC::DeviceId>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
to_delete: &SC::DeviceId,
) {
get_ip_layer_state_inner_mut(sync_ctx, ctx)
.table
.retain(|Entry { subnet: _, device, gateway: _ }| device != to_delete)
}
/// Returns all the routes for the provided `IpAddress` type.
pub(crate) fn iter_all_routes<NonSyncCtx: NonSyncContext, A: IpAddress>(
ctx: &SyncCtx<NonSyncCtx>,
) -> Iter<'_, Entry<A, DeviceId>>
where
A::Version: GetStateIpExt,
{
A::Version::get_state_inner(&ctx.state).table.iter_table()
}
/// The metadata associated with an outgoing IP packet.
#[cfg_attr(test, derive(Debug))]
pub(crate) struct SendIpPacketMeta<I: packet_formats::ip::IpExt, D, Src> {
/// The outgoing device.
pub(crate) device: D,
/// The source address of the packet.
pub(crate) src_ip: Src,
/// The destination address of the packet.
pub(crate) dst_ip: SpecifiedAddr<I::Addr>,
/// The next-hop node that the packet should be sent to.
pub(crate) next_hop: SpecifiedAddr<I::Addr>,
/// The upper-layer protocol held in the packet's payload.
pub(crate) proto: I::Proto,
/// The time-to-live (IPv4) or hop limit (IPv6) for the packet.
///
/// If not set, a default TTL may be used.
pub(crate) ttl: Option<NonZeroU8>,
/// An MTU to artificially impose on the whole IP packet.
///
/// Note that the device's MTU will still be imposed on the packet.
pub(crate) mtu: Option<u32>,
}
impl<I: packet_formats::ip::IpExt, D> From<SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>>
for SendIpPacketMeta<I, D, Option<SpecifiedAddr<I::Addr>>>
{
fn from(
SendIpPacketMeta { device, src_ip, dst_ip, next_hop, proto, ttl, mtu }: SendIpPacketMeta<
I,
D,
SpecifiedAddr<I::Addr>,
>,
) -> SendIpPacketMeta<I, D, Option<SpecifiedAddr<I::Addr>>> {
SendIpPacketMeta { device, src_ip: Some(src_ip), dst_ip, next_hop, proto, ttl, mtu }
}
}
trait BufferIpLayerHandler<I: Ip, C, B: BufferMut>: IpDeviceIdContext<I> {
fn send_ip_packet_from_device<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut C,
meta: SendIpPacketMeta<I, Self::DeviceId, Option<SpecifiedAddr<I::Addr>>>,
body: S,
) -> Result<(), S>;
}
impl<
B: BufferMut,
C: IpLayerNonSyncContext<Ipv6, <SC as IpDeviceIdContext<Ipv6>>::DeviceId>,
SC: BufferIpDeviceContext<Ipv6, C, B> + IpStateContext<Ipv6, C::Instant> + NonTestCtxMarker,
> BufferIpLayerHandler<Ipv6, C, B> for SC
{
fn send_ip_packet_from_device<S: Serializer<Buffer = B>>(
&mut self,
ctx: &mut C,
meta: SendIpPacketMeta<Ipv6, SC::DeviceId, Option<SpecifiedAddr<Ipv6Addr>>>,
body: S,
) -> Result<(), S> {
send_ipv6_packet_from_device(self, ctx, meta, body)
}
}
/// Sends an IPv4 packet with the specified metadata.
///
/// # Panics
///
/// Panics if either the source or destination address is the loopback address
/// and the device is a non-loopback device.
pub(crate) fn send_ipv4_packet_from_device<
B: BufferMut,
C: IpLayerNonSyncContext<Ipv4, <SC as IpDeviceIdContext<Ipv4>>::DeviceId>,
SC: BufferIpDeviceContext<Ipv4, C, B> + IpStateContext<Ipv4, C::Instant>,
S: Serializer<Buffer = B>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
SendIpPacketMeta { device, src_ip, dst_ip, next_hop, proto, ttl, mtu }: SendIpPacketMeta<
Ipv4,
<SC as IpDeviceIdContext<Ipv4>>::DeviceId,
Option<SpecifiedAddr<Ipv4Addr>>,
>,
body: S,
) -> Result<(), S> {
let src_ip = src_ip.map_or(Ipv4::UNSPECIFIED_ADDRESS, |a| a.get());
let builder = {
assert!(
(!Ipv4::LOOPBACK_SUBNET.contains(&src_ip) && !Ipv4::LOOPBACK_SUBNET.contains(&dst_ip))
|| device.is_loopback()
);
let mut builder = Ipv4PacketBuilder::new(
src_ip,
dst_ip,
ttl.unwrap_or_else(|| sync_ctx.get_hop_limit(device)).get(),
proto,
);
builder.id(gen_ipv4_packet_id(sync_ctx));
builder
};
let body = body.encapsulate(builder);
if let Some(mtu) = mtu {
let body = body.with_mtu(mtu as usize);
sync_ctx
.send_ip_frame(ctx, device, next_hop, body)
.map_err(|ser| ser.into_inner().into_inner())
} else {
sync_ctx.send_ip_frame(ctx, device, next_hop, body).map_err(|ser| ser.into_inner())
}
}
/// Sends an IPv6 packet with the specified metadata.
///
/// # Panics
///
/// Panics if either the source or destination address is the loopback address
/// and the device is a non-loopback device.
pub(crate) fn send_ipv6_packet_from_device<
B: BufferMut,
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId>,
SC: BufferIpDeviceContext<Ipv6, C, B>,
S: Serializer<Buffer = B>,
>(
sync_ctx: &mut SC,
ctx: &mut C,
SendIpPacketMeta { device, src_ip, dst_ip, next_hop, proto, ttl, mtu }: SendIpPacketMeta<
Ipv6,
SC::DeviceId,
Option<SpecifiedAddr<Ipv6Addr>>,
>,
body: S,
) -> Result<(), S> {
let src_ip = src_ip.map_or(Ipv6::UNSPECIFIED_ADDRESS, |a| a.get());
let builder = {
assert!(
(!Ipv6::LOOPBACK_SUBNET.contains(&src_ip) && !Ipv6::LOOPBACK_SUBNET.contains(&dst_ip))
|| device.is_loopback()
);
Ipv6PacketBuilder::new(
src_ip,
dst_ip,
ttl.unwrap_or_else(|| sync_ctx.get_hop_limit(device)).get(),
proto,
)
};
let body = body.encapsulate(builder);
if let Some(mtu) = mtu {
let body = body.with_mtu(mtu as usize);
sync_ctx
.send_ip_frame(ctx, device, next_hop, body)
.map_err(|ser| ser.into_inner().into_inner())
} else {
sync_ctx.send_ip_frame(ctx, device, next_hop, body).map_err(|ser| ser.into_inner())
}
}
impl<
C: IpLayerNonSyncContext<Ipv4, SC::DeviceId> + IcmpNonSyncCtx<Ipv4>,
SC: IpTransportLayerContext<Ipv4, C>
+ StateContext<C, IcmpState<Ipv4Addr, C::Instant, IpSock<Ipv4, SC::DeviceId>>>
+ IpSocketHandler<Ipv4, C>,
> InnerIcmpContext<Ipv4, C> for SC
{
fn receive_icmp_error(
&mut self,
ctx: &mut C,
device: SC::DeviceId,
original_src_ip: Option<SpecifiedAddr<Ipv4Addr>>,
original_dst_ip: SpecifiedAddr<Ipv4Addr>,
original_proto: Ipv4Proto,
original_body: &[u8],
err: Icmpv4ErrorCode,
) {
ctx.increment_counter("InnerIcmpContext<Ipv4>::receive_icmp_error");
trace!("InnerIcmpContext<Ipv4>::receive_icmp_error({:?})", err);
macro_rules! mtch {
($($cond:pat => $ty:ident),*) => {
match original_proto {
Ipv4Proto::Icmp => <IcmpIpTransportContext as IpTransportContext<Ipv4, _, _>>
::receive_icmp_error(self,ctx, device, original_src_ip, original_dst_ip, original_body, err),
$($cond => <<SC as IpTransportLayerContext<_, _>>::$ty as IpTransportContext<Ipv4, _, _>>
::receive_icmp_error(self,ctx, device, original_src_ip, original_dst_ip, original_body, err),)*
// TODO(joshlf): Once all IP protocol numbers are covered,
// remove this default case.
_ => <() as IpTransportContext<Ipv4, _, _>>::receive_icmp_error(self,ctx, device, original_src_ip, original_dst_ip, original_body, err),
}
};
}
#[rustfmt::skip]
mtch!(
Ipv4Proto::Proto(IpProto::Tcp) => Tcp,
Ipv4Proto::Proto(IpProto::Udp) => Udp
);
}
}
impl<
C: IpLayerNonSyncContext<Ipv6, SC::DeviceId> + IcmpNonSyncCtx<Ipv6>,
SC: IpTransportLayerContext<Ipv6, C>
+ StateContext<C, IcmpState<Ipv6Addr, C::Instant, IpSock<Ipv6, SC::DeviceId>>>
+ IpSocketHandler<Ipv6, C>,
> InnerIcmpContext<Ipv6, C> for SC
{
fn receive_icmp_error(
&mut self,
ctx: &mut C,
device: SC::DeviceId,
original_src_ip: Option<SpecifiedAddr<Ipv6Addr>>,
original_dst_ip: SpecifiedAddr<Ipv6Addr>,
original_next_header: Ipv6Proto,
original_body: &[u8],
err: Icmpv6ErrorCode,
) {
ctx.increment_counter("InnerIcmpContext<Ipv6>::receive_icmp_error");
trace!("InnerIcmpContext<Ipv6>::receive_icmp_error({:?})", err);
macro_rules! mtch {
($($cond:pat => $ty:ident),*) => {
match original_next_header {
Ipv6Proto::Icmpv6 => <IcmpIpTransportContext as IpTransportContext<Ipv6, _, _>>
::receive_icmp_error(self, ctx, device, original_src_ip, original_dst_ip, original_body, err),
$($cond => <<SC as IpTransportLayerContext<_, _>>::$ty as IpTransportContext<Ipv6, _, _>>
::receive_icmp_error(self, ctx, device, original_src_ip, original_dst_ip, original_body, err),)*
// TODO(joshlf): Once all IP protocol numbers are covered,
// remove this default case.
_ => <() as IpTransportContext<Ipv6, _, _>>::receive_icmp_error(self, ctx, device, original_src_ip, original_dst_ip, original_body, err),
}
};
}
#[rustfmt::skip]
mtch!(
Ipv6Proto::Proto(IpProto::Tcp) => Tcp,
Ipv6Proto::Proto(IpProto::Udp) => Udp
);
}
}
// Used in testing in other modules.
#[cfg(test)]
pub(crate) fn dispatch_receive_ip_packet_name<I: Ip>() -> &'static str {
match I::VERSION {
IpVersion::V4 => "dispatch_receive_ipv4_packet",
IpVersion::V6 => "dispatch_receive_ipv6_packet",
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use core::{convert::TryFrom, num::NonZeroU16, time::Duration};
use net_types::{
ethernet::Mac,
ip::{AddrSubnet, IpAddr, Ipv4Addr, Ipv6Addr},
MulticastAddr, UnicastAddr,
};
use packet::{Buf, ParseBuffer};
use packet_formats::{
ethernet::{EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt},
icmp::{
IcmpDestUnreachable, IcmpEchoRequest, IcmpPacketBuilder, IcmpParseArgs, IcmpUnusedCode,
Icmpv4DestUnreachableCode, Icmpv6Packet, Icmpv6PacketTooBig,
Icmpv6ParameterProblemCode, MessageBody,
},
ip::{IpExtByteSlice, IpPacketBuilder, Ipv6ExtHdrType},
ipv4::Ipv4PacketBuilder,
ipv6::{ext_hdrs::ExtensionHeaderOptionAction, Ipv6PacketBuilder},
testutil::parse_icmp_packet_in_ip_packet_in_ethernet_frame,
};
use rand::Rng;
use specialize_ip_macro::ip_test;
use super::*;
use crate::{
context::testutil::{DummyInstant, DummyTimerCtxExt as _},
device::{receive_frame, testutil::receive_frame_or_panic, FrameDestination},
ip::device::set_routing_enabled,
testutil::{
assert_empty, get_counter_val, handle_timer, new_rng, DummyCtx,
DummyEventDispatcherBuilder, DummyNonSyncCtx, TestIpExt, DUMMY_CONFIG_V4,
DUMMY_CONFIG_V6,
},
Ctx, DeviceId,
};
// Some helper functions
/// Verify that an ICMP Parameter Problem packet was actually sent in
/// response to a packet with an unrecognized IPv6 extension header option.
///
/// `verify_icmp_for_unrecognized_ext_hdr_option` verifies that the next
/// frame in `net` is an ICMP packet with code set to `code`, and pointer
/// set to `pointer`.
fn verify_icmp_for_unrecognized_ext_hdr_option(
ctx: &mut DummyNonSyncCtx,
code: Icmpv6ParameterProblemCode,
pointer: u32,
offset: usize,
) {
// Check the ICMP that bob attempted to send to alice
let device_frames = ctx.frames_sent();
assert!(!device_frames.is_empty());
let mut buffer = Buf::new(device_frames[offset].1.as_slice(), ..);
let _frame =
buffer.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap();
let packet = buffer.parse::<<Ipv6 as IpExtByteSlice<&[u8]>>::Packet>().unwrap();
let (src_ip, dst_ip, proto, _): (_, _, _, ParseMetadata) = packet.into_metadata();
assert_eq!(dst_ip, DUMMY_CONFIG_V6.remote_ip.get());
assert_eq!(src_ip, DUMMY_CONFIG_V6.local_ip.get());
assert_eq!(proto, Ipv6Proto::Icmpv6);
let icmp =
buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)).unwrap();
if let Icmpv6Packet::ParameterProblem(icmp) = icmp {
assert_eq!(icmp.code(), code);
assert_eq!(icmp.message().pointer(), pointer);
} else {
panic!("Expected ICMPv6 Parameter Problem: {:?}", icmp);
}
}
/// Populate a buffer `bytes` with data required to test unrecognized
/// options.
///
/// The unrecognized option type will be located at index 48. `bytes` must
/// be at least 64 bytes long. If `to_multicast` is `true`, the destination
/// address of the packet will be a multicast address.
fn buf_for_unrecognized_ext_hdr_option_test(
bytes: &mut [u8],
action: ExtensionHeaderOptionAction,
to_multicast: bool,
) -> Buf<&mut [u8]> {
assert!(bytes.len() >= 64);
let action: u8 = action.into();
// Unrecognized Option type.
let oty = 63 | (action << 6);
#[rustfmt::skip]
bytes[40..64].copy_from_slice(&[
// Destination Options Extension Header
IpProto::Udp.into(), // Next Header
1, // Hdr Ext Len (In 8-octet units, not including first 8 octets)
0, // Pad1
1, 0, // Pad2
1, 1, 0, // Pad3
oty, 6, 0, 0, 0, 0, 0, 0, // Unrecognized type w/ action = discard
// Body
1, 2, 3, 4, 5, 6, 7, 8
][..]);
bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]);
let payload_len = u16::try_from(bytes.len() - 40).unwrap();
bytes[4..6].copy_from_slice(&payload_len.to_be_bytes());
bytes[6] = Ipv6ExtHdrType::DestinationOptions.into();
bytes[7] = 64;
bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes());
if to_multicast {
bytes[24..40].copy_from_slice(
&[255, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32][..],
);
} else {
bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes());
}
Buf::new(bytes, ..)
}
/// Create an IPv4 packet builder.
fn get_ipv4_builder() -> Ipv4PacketBuilder {
Ipv4PacketBuilder::new(
DUMMY_CONFIG_V4.remote_ip,
DUMMY_CONFIG_V4.local_ip,
10,
IpProto::Udp.into(),
)
}
/// Process an IP fragment depending on the `Ip` `process_ip_fragment` is
/// specialized with.
fn process_ip_fragment<I: Ip, NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
fragment_id: u16,
fragment_offset: u8,
fragment_count: u8,
) {
match I::VERSION {
IpVersion::V4 => process_ipv4_fragment(
sync_ctx,
ctx,
device,
fragment_id,
fragment_offset,
fragment_count,
),
IpVersion::V6 => process_ipv6_fragment(
sync_ctx,
ctx,
device,
fragment_id,
fragment_offset,
fragment_count,
),
}
}
/// Generate and 'receive' an IPv4 fragment packet.
///
/// `fragment_offset` is the fragment offset. `fragment_count` is the number
/// of fragments for a packet. The generated packet will have a body of size
/// 8 bytes.
fn process_ipv4_fragment<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
fragment_id: u16,
fragment_offset: u8,
fragment_count: u8,
) {
assert!(fragment_offset < fragment_count);
let m_flag = fragment_offset < (fragment_count - 1);
let mut builder = get_ipv4_builder();
builder.id(fragment_id);
builder.fragment_offset(fragment_offset as u16);
builder.mf_flag(m_flag);
let mut body: Vec<u8> = Vec::new();
body.extend(fragment_offset * 8..fragment_offset * 8 + 8);
let buffer =
Buf::new(body, ..).encapsulate(builder).serialize_vec_outer().unwrap().into_inner();
receive_ipv4_packet(sync_ctx, ctx, device, FrameDestination::Unicast, buffer);
}
/// Generate and 'receive' an IPv6 fragment packet.
///
/// `fragment_offset` is the fragment offset. `fragment_count` is the number
/// of fragments for a packet. The generated packet will have a body of size
/// 8 bytes.
fn process_ipv6_fragment<NonSyncCtx: NonSyncContext>(
sync_ctx: &mut SyncCtx<NonSyncCtx>,
ctx: &mut NonSyncCtx,
device: DeviceId,
fragment_id: u16,
fragment_offset: u8,
fragment_count: u8,
) {
assert!(fragment_offset < fragment_count);
let m_flag = fragment_offset < (fragment_count - 1);
let mut bytes = vec![0; 48];
bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]);
bytes[6] = Ipv6ExtHdrType::Fragment.into(); // Next Header
bytes[7] = 64;
bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes());
bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes());
bytes[40] = IpProto::Udp.into();
bytes[42] = fragment_offset >> 5;
bytes[43] = ((fragment_offset & 0x1F) << 3) | if m_flag { 1 } else { 0 };
bytes[44..48].copy_from_slice(&(u32::try_from(fragment_id).unwrap().to_be_bytes()));
bytes.extend(fragment_offset * 8..fragment_offset * 8 + 8);
let payload_len = u16::try_from(bytes.len() - 40).unwrap();
bytes[4..6].copy_from_slice(&payload_len.to_be_bytes());
let buffer = Buf::new(bytes, ..);
receive_ipv6_packet(sync_ctx, ctx, device, FrameDestination::Unicast, buffer);
}
#[test]
fn test_ipv6_icmp_parameter_problem_non_must() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6).build();
let device = DeviceId::new_ethernet(0);
// Test parsing an IPv6 packet with invalid next header value which
// we SHOULD send an ICMP response for (but we don't since its not a
// MUST).
#[rustfmt::skip]
let bytes: &mut [u8] = &mut [
// FixedHeader (will be replaced later)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// Body
1, 2, 3, 4, 5,
][..];
bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]);
let payload_len = u16::try_from(bytes.len() - 40).unwrap();
bytes[4..6].copy_from_slice(&payload_len.to_be_bytes());
bytes[6] = 255; // Invalid Next Header
bytes[7] = 64;
bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes());
bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes());
let buf = Buf::new(bytes, ..);
receive_ipv6_packet(
&mut sync_ctx,
&mut non_sync_ctx,
device,
FrameDestination::Unicast,
buf,
);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv4_parameter_problem"), 0);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), 0);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 0);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 0);
}
#[test]
fn test_ipv6_icmp_parameter_problem_must() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6).build();
let device = DeviceId::new_ethernet(0);
// Test parsing an IPv6 packet where we MUST send an ICMP parameter problem
// response (invalid routing type for a routing extension header).
#[rustfmt::skip]
let bytes: &mut [u8] = &mut [
// FixedHeader (will be replaced later)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// Routing Extension Header
IpProto::Udp.into(), // Next Header
4, // Hdr Ext Len (In 8-octet units, not including first 8 octets)
255, // Routing Type (Invalid)
1, // Segments Left
0, 0, 0, 0, // Reserved
// Addresses for Routing Header w/ Type 0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// Body
1, 2, 3, 4, 5,
][..];
bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]);
let payload_len = u16::try_from(bytes.len() - 40).unwrap();
bytes[4..6].copy_from_slice(&payload_len.to_be_bytes());
bytes[6] = Ipv6ExtHdrType::Routing.into();
bytes[7] = 64;
bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes());
bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes());
let buf = Buf::new(bytes, ..);
receive_ipv6_packet(
&mut sync_ctx,
&mut non_sync_ctx,
device,
FrameDestination::Unicast,
buf,
);
assert_eq!(non_sync_ctx.frames_sent().len(), 1);
verify_icmp_for_unrecognized_ext_hdr_option(
&mut non_sync_ctx,
Icmpv6ParameterProblemCode::ErroneousHeaderField,
42,
0,
);
}
#[test]
fn test_ipv6_unrecognized_ext_hdr_option() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6).build();
let device = DeviceId::new_ethernet(0);
let mut expected_icmps = 0;
let mut bytes = [0; 64];
let frame_dst = FrameDestination::Unicast;
// Test parsing an IPv6 packet where we MUST send an ICMP parameter
// problem due to an unrecognized extension header option.
// Test with unrecognized option type set with action = skip & continue.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::SkipAndContinue,
false,
);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 1);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
// Test with unrecognized option type set with
// action = discard.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::DiscardPacket,
false,
);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
// Test with unrecognized option type set with
// action = discard & send icmp
// where dest addr is a unicast addr.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::DiscardPacketSendIcmp,
false,
);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
expected_icmps += 1;
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
verify_icmp_for_unrecognized_ext_hdr_option(
&mut non_sync_ctx,
Icmpv6ParameterProblemCode::UnrecognizedIpv6Option,
48,
expected_icmps - 1,
);
// Test with unrecognized option type set with
// action = discard & send icmp
// where dest addr is a multicast addr.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::DiscardPacketSendIcmp,
true,
);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
expected_icmps += 1;
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
verify_icmp_for_unrecognized_ext_hdr_option(
&mut non_sync_ctx,
Icmpv6ParameterProblemCode::UnrecognizedIpv6Option,
48,
expected_icmps - 1,
);
// Test with unrecognized option type set with
// action = discard & send icmp if not multicast addr
// where dest addr is a unicast addr.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast,
false,
);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
expected_icmps += 1;
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
verify_icmp_for_unrecognized_ext_hdr_option(
&mut non_sync_ctx,
Icmpv6ParameterProblemCode::UnrecognizedIpv6Option,
48,
expected_icmps - 1,
);
// Test with unrecognized option type set with
// action = discard & send icmp if not multicast addr
// but dest addr is a multicast addr.
let buf = buf_for_unrecognized_ext_hdr_option_test(
&mut bytes,
ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast,
true,
);
// Do not expect an ICMP response for this packet
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_parameter_problem"), expected_icmps);
assert_eq!(non_sync_ctx.frames_sent().len(), expected_icmps);
// None of our tests should have sent an icmpv4 packet, or dispatched an
// IP packet after the first.
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv4_parameter_problem"), 0);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 1);
}
#[ip_test]
fn test_ip_packet_reassembly_not_needed<I: Ip + TestIpExt>() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(I::DUMMY_CONFIG).build();
let device = DeviceId::new_ethernet(0);
let fragment_id = 5;
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 0);
// Test that a non fragmented packet gets dispatched right away.
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 0, 1);
// Make sure the packet got dispatched.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
}
#[ip_test]
fn test_ip_packet_reassembly<I: Ip + TestIpExt>() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(I::DUMMY_CONFIG).build();
let device = DeviceId::new_ethernet(0);
let fragment_id = 5;
// Test that the received packet gets dispatched only after receiving
// all the fragments.
// Process fragment #0
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 0, 3);
// Process fragment #1
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 1, 3);
// Make sure no packets got dispatched yet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 0);
// Process fragment #2
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 2, 3);
// Make sure the packet finally got dispatched now that the final
// fragment has been 'received'.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
}
#[ip_test]
fn test_ip_packet_reassembly_with_packets_arriving_out_of_order<I: Ip + TestIpExt>() {
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(I::DUMMY_CONFIG).build();
let device = DeviceId::new_ethernet(0);
let fragment_id_0 = 5;
let fragment_id_1 = 10;
let fragment_id_2 = 15;
// Test that received packets gets dispatched only after receiving all
// the fragments with out of order arrival of fragments.
// Process packet #0, fragment #1
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_0, 1, 3);
// Process packet #1, fragment #2
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_1, 2, 3);
// Process packet #1, fragment #0
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_1, 0, 3);
// Make sure no packets got dispatched yet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 0);
// Process a packet that does not require reassembly (packet #2, fragment #0).
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_2, 0, 1);
// Make packet #1 got dispatched since it didn't need reassembly.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
// Process packet #0, fragment #2
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_0, 2, 3);
// Make sure no other packets got dispatched yet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
// Process packet #0, fragment #0
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_0, 0, 3);
// Make sure that packet #0 finally got dispatched now that the final
// fragment has been 'received'.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 2);
// Process packet #1, fragment #1
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id_1, 1, 3);
// Make sure the packet finally got dispatched now that the final
// fragment has been 'received'.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 3);
}
#[ip_test]
fn test_ip_packet_reassembly_timer<I: Ip + TestIpExt>()
where
IpLayerTimerId: From<FragmentCacheKey<I::Addr>>,
{
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(I::DUMMY_CONFIG).build();
let device = DeviceId::new_ethernet(0);
let fragment_id = 5;
// Test to make sure that packets must arrive within the reassembly
// timer.
// Process fragment #0
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 0, 3);
// Make sure a timer got added.
non_sync_ctx.timer_ctx().assert_timers_installed([(
IpLayerTimerId::from(FragmentCacheKey::new(
I::DUMMY_CONFIG.remote_ip.get(),
I::DUMMY_CONFIG.local_ip.get(),
fragment_id.into(),
))
.into(),
..,
)]);
// Process fragment #1
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 1, 3);
// Trigger the timer (simulate a timer for the fragmented packet)
let key = FragmentCacheKey::new(
I::DUMMY_CONFIG.remote_ip.get(),
I::DUMMY_CONFIG.local_ip.get(),
u32::from(fragment_id),
);
assert_eq!(
non_sync_ctx.trigger_next_timer(&mut sync_ctx, crate::handle_timer).unwrap(),
IpLayerTimerId::from(key.into()).into(),
);
// Make sure no other timers exist.
non_sync_ctx.timer_ctx().assert_no_timers_installed();
// Process fragment #2
process_ip_fragment::<I, _>(&mut sync_ctx, &mut non_sync_ctx, device, fragment_id, 2, 3);
// Make sure no packets got dispatched yet since even though we
// technically received all the fragments, this fragment (#2) arrived
// too late and the reassembly timer was triggered, causing the prior
// fragment data to be discarded.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 0);
}
#[ip_test]
fn test_ip_reassembly_only_at_destination_host<I: Ip + TestIpExt>() {
// Create a new network with two parties (alice & bob) and enable IP
// packet routing for alice.
let a = "alice";
let b = "bob";
let dummy_config = I::DUMMY_CONFIG;
let device = DeviceId::new_ethernet(0);
let mut alice = DummyEventDispatcherBuilder::from_config(dummy_config.swap()).build();
{
let Ctx { sync_ctx, non_sync_ctx } = &mut alice;
set_routing_enabled::<_, _, I>(sync_ctx, non_sync_ctx, device, true)
.expect("qerror setting routing enabled");
}
let bob = DummyEventDispatcherBuilder::from_config(dummy_config).build();
let mut net = crate::context::testutil::new_legacy_simple_dummy_network(a, alice, b, bob);
let fragment_id = 5;
// Test that packets only get reassembled and dispatched at the
// destination. In this test, Alice is receiving packets from some
// source that is actually destined for Bob. Alice should simply forward
// the packets without attempting to process or reassemble the
// fragments.
// Process fragment #0
net.with_context("alice", |Ctx { sync_ctx, non_sync_ctx }| {
process_ip_fragment::<I, _>(sync_ctx, non_sync_ctx, device, fragment_id, 0, 3);
});
// Make sure the packet got sent from alice to bob
assert!(!net.step(receive_frame_or_panic, handle_timer).is_idle());
// Process fragment #1
net.with_context("alice", |Ctx { sync_ctx, non_sync_ctx }| {
process_ip_fragment::<I, _>(sync_ctx, non_sync_ctx, device, fragment_id, 1, 3);
});
assert!(!net.step(receive_frame_or_panic, handle_timer).is_idle());
// Make sure no packets got dispatched yet.
assert_eq!(
get_counter_val(net.non_sync_ctx("alice"), dispatch_receive_ip_packet_name::<I>()),
0
);
assert_eq!(
get_counter_val(net.non_sync_ctx("bob"), dispatch_receive_ip_packet_name::<I>()),
0
);
// Process fragment #2
net.with_context("alice", |Ctx { sync_ctx, non_sync_ctx }| {
process_ip_fragment::<I, _>(sync_ctx, non_sync_ctx, device, fragment_id, 2, 3);
});
assert!(!net.step(receive_frame_or_panic, handle_timer).is_idle());
// Make sure the packet finally got dispatched now that the final
// fragment has been received by bob.
assert_eq!(
get_counter_val(net.non_sync_ctx("alice"), dispatch_receive_ip_packet_name::<I>()),
0
);
assert_eq!(
get_counter_val(net.non_sync_ctx("bob"), dispatch_receive_ip_packet_name::<I>()),
1
);
// Make sure there are no more events.
assert!(net.step(receive_frame_or_panic, handle_timer).is_idle());
}
#[test]
fn test_ipv6_packet_too_big() {
// Test sending an IPv6 Packet Too Big Error when receiving a packet
// that is too big to be forwarded when it isn't destined for the node
// it arrived at.
let dummy_config = Ipv6::DUMMY_CONFIG;
let mut dispatcher_builder = DummyEventDispatcherBuilder::from_config(dummy_config.clone());
let extra_ip = UnicastAddr::new(Ipv6Addr::from_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 100,
]))
.unwrap();
let extra_mac = UnicastAddr::new(Mac::new([12, 13, 14, 15, 16, 17])).unwrap();
dispatcher_builder.add_ndp_table_entry(0, extra_ip, extra_mac);
dispatcher_builder.add_ndp_table_entry(
0,
extra_mac.to_ipv6_link_local().addr().get(),
extra_mac,
);
let Ctx { mut sync_ctx, mut non_sync_ctx } = dispatcher_builder.build();
let device = DeviceId::new_ethernet(0);
set_routing_enabled::<_, _, Ipv6>(&mut sync_ctx, &mut non_sync_ctx, device, true)
.expect("error setting routing enabled");
let frame_dst = FrameDestination::Unicast;
// Construct an IPv6 packet that is too big for our MTU (MTU = 1280;
// body itself is 5000). Note, the final packet will be larger because
// of IP header data.
let mut rng = new_rng(70812476915813);
let body: Vec<u8> = core::iter::repeat_with(|| rng.gen()).take(5000).collect();
// Ip packet from some node destined to a remote on this network,
// arriving locally.
let mut ipv6_packet_buf = Buf::new(body.clone(), ..)
.encapsulate(Ipv6PacketBuilder::new(
extra_ip,
dummy_config.remote_ip,
64,
IpProto::Udp.into(),
))
.serialize_vec_outer()
.unwrap();
// Receive the IP packet.
receive_ipv6_packet(
&mut sync_ctx,
&mut non_sync_ctx,
device,
frame_dst,
ipv6_packet_buf.clone(),
);
// Should not have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 0);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv6_packet_too_big"), 1);
// Should have sent out one frame though.
assert_eq!(non_sync_ctx.frames_sent().len(), 1);
// Received packet should be a Packet Too Big ICMP error message.
let buf = &non_sync_ctx.frames_sent()[0].1[..];
// The original packet's TTL gets decremented so we decrement here
// to validate the rest of the icmp message body.
let ipv6_packet_buf_mut: &mut [u8] = ipv6_packet_buf.as_mut();
ipv6_packet_buf_mut[7] -= 1;
let (_, _, _, _, _, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, Icmpv6PacketTooBig, _>(
buf,
move |packet| {
// Size of the ICMP message body should be size of the
// MTU without IP and ICMP headers.
let expected_len = 1280 - 48;
let actual_body: &[u8] = ipv6_packet_buf.as_ref();
let actual_body = &actual_body[..expected_len];
assert_eq!(packet.body().len(), expected_len);
assert_eq!(packet.body().bytes(), actual_body);
},
)
.unwrap();
assert_eq!(code, IcmpUnusedCode);
// MTU should match the MTU for the link.
assert_eq!(message, Icmpv6PacketTooBig::new(1280));
}
fn create_packet_too_big_buf<A: IpAddress>(
src_ip: A,
dst_ip: A,
mtu: u16,
body: Option<Buf<Vec<u8>>>,
) -> Buf<Vec<u8>> {
let body = body.unwrap_or_else(|| Buf::new(Vec::new(), ..));
match [src_ip, dst_ip].into() {
IpAddr::V4([src_ip, dst_ip]) => body
.encapsulate(IcmpPacketBuilder::<Ipv4, &mut [u8], IcmpDestUnreachable>::new(
dst_ip,
src_ip,
Icmpv4DestUnreachableCode::FragmentationRequired,
NonZeroU16::new(mtu)
.map(IcmpDestUnreachable::new_for_frag_req)
.unwrap_or_else(Default::default),
))
.encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 64, Ipv4Proto::Icmp))
.serialize_vec_outer()
.unwrap(),
IpAddr::V6([src_ip, dst_ip]) => body
.encapsulate(IcmpPacketBuilder::<Ipv6, &mut [u8], Icmpv6PacketTooBig>::new(
dst_ip,
src_ip,
IcmpUnusedCode,
Icmpv6PacketTooBig::new(u32::from(mtu)),
))
.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 64, Ipv6Proto::Icmpv6))
.serialize_vec_outer()
.unwrap(),
}
.into_inner()
}
#[ip_test]
fn test_ip_update_pmtu<I: Ip + TestIpExt + GetStateIpExt>() {
// Test receiving a Packet Too Big (IPv6) or Dest Unreachable
// Fragmentation Required (IPv4) which should update the PMTU if it is
// less than the current value.
let dummy_config = I::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(dummy_config.clone()).build();
let device = DeviceId::new_ethernet(0);
let frame_dst = FrameDestination::Unicast;
// Update PMTU from None.
let new_mtu1 = u32::from(I::MINIMUM_LINK_MTU) + 100;
// Create ICMP IP buf
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
u16::try_from(new_mtu1).unwrap(),
None,
);
// Receive the IP packet.
receive_ip_packet::<_, _, I>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
frame_dst,
packet_buf,
);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
assert_eq!(
I::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
new_mtu1
);
// Don't update PMTU when current PMTU is less than reported MTU.
let new_mtu2 = u32::from(I::MINIMUM_LINK_MTU) + 200;
// Create IPv6 ICMPv6 packet too big packet with MTU larger than current
// PMTU.
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
u16::try_from(new_mtu2).unwrap(),
None,
);
// Receive the IP packet.
receive_ip_packet::<_, _, I>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
frame_dst,
packet_buf,
);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 2);
// The PMTU should not have updated to `new_mtu2`
assert_eq!(
I::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
new_mtu1
);
// Update PMTU when current PMTU is greater than the reported MTU.
let new_mtu3 = u32::from(I::MINIMUM_LINK_MTU) + 50;
// Create IPv6 ICMPv6 packet too big packet with MTU smaller than
// current PMTU.
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
u16::try_from(new_mtu3).unwrap(),
None,
);
// Receive the IP packet.
receive_ip_packet::<_, _, I>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
frame_dst,
packet_buf,
);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 3);
// The PMTU should have updated to 1900.
assert_eq!(
I::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
new_mtu3
);
}
#[ip_test]
fn test_ip_update_pmtu_too_low<I: Ip + TestIpExt + GetStateIpExt>() {
// Test receiving a Packet Too Big (IPv6) or Dest Unreachable
// Fragmentation Required (IPv4) which should not update the PMTU if it
// is less than the min MTU.
let dummy_config = I::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(dummy_config.clone()).build();
let device = DeviceId::new_ethernet(0);
let frame_dst = FrameDestination::Unicast;
// Update PMTU from None but with an MTU too low.
let new_mtu1 = u32::from(I::MINIMUM_LINK_MTU) - 1;
// Create ICMP IP buf
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
u16::try_from(new_mtu1).unwrap(),
None,
);
// Receive the IP packet.
receive_ip_packet::<_, _, I>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
frame_dst,
packet_buf,
);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
assert_eq!(
I::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get()),
None
);
}
/// Create buffer to be used as the ICMPv4 message body
/// where the original packet's body length is `body_len`.
fn create_orig_packet_buf(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, body_len: usize) -> Buf<Vec<u8>> {
Buf::new(vec![0; body_len], ..)
.encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 64, IpProto::Udp.into()))
.serialize_vec_outer()
.unwrap()
.into_inner()
}
#[test]
fn test_ipv4_remote_no_rfc1191() {
// Test receiving an IPv4 Dest Unreachable Fragmentation
// Required from a node that does not implement RFC 1191.
let dummy_config = Ipv4::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(dummy_config.clone()).build();
let device = DeviceId::new_ethernet(0);
let frame_dst = FrameDestination::Unicast;
// Update from None.
// Create ICMP IP buf w/ orig packet body len = 500; orig packet len =
// 520
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
0, // A 0 value indicates that the source of the
// ICMP message does not implement RFC 1191.
create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 500)
.into(),
);
// Receive the IP packet.
receive_ipv4_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, packet_buf);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 1);
// Should have decreased PMTU value to the next lower PMTU
// plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`.
assert_eq!(
Ipv4::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
508
);
// Don't Update when packet size is too small.
// Create ICMP IP buf w/ orig packet body len = 1; orig packet len = 21
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
0,
create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 1)
.into(),
);
// Receive the IP packet.
receive_ipv4_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, packet_buf);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 2);
// Should not have updated PMTU as there is no other valid
// lower PMTU value.
assert_eq!(
Ipv4::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
508
);
// Update to lower PMTU estimate based on original packet size.
// Create ICMP IP buf w/ orig packet body len = 60; orig packet len = 80
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
0,
create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 60)
.into(),
);
// Receive the IP packet.
receive_ipv4_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, packet_buf);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 3);
// Should have decreased PMTU value to the next lower PMTU
// plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`.
assert_eq!(
Ipv4::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
68
);
// Should not update PMTU because the next low PMTU from this original
// packet size is higher than current PMTU.
// Create ICMP IP buf w/ orig packet body len = 290; orig packet len =
// 310
let packet_buf = create_packet_too_big_buf(
dummy_config.remote_ip.get(),
dummy_config.local_ip.get(),
0, // A 0 value indicates that the source of the
// ICMP message does not implement RFC 1191.
create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 290)
.into(),
);
// Receive the IP packet.
receive_ipv4_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, packet_buf);
// Should have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 4);
// Should not have updated the PMTU as the current PMTU is lower.
assert_eq!(
Ipv4::get_state_inner(&sync_ctx.state)
.pmtu_cache
.get_pmtu(dummy_config.local_ip.get(), dummy_config.remote_ip.get())
.unwrap(),
68
);
}
#[test]
fn test_invalid_icmpv4_in_ipv6() {
let ip_config = Ipv6::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(ip_config.clone()).build();
let device = DeviceId::new_ethernet(0);
let frame_dst = FrameDestination::Unicast;
let ic_config = Ipv4::DUMMY_CONFIG;
let icmp_builder = IcmpPacketBuilder::<Ipv4, &[u8], _>::new(
ic_config.remote_ip,
ic_config.local_ip,
IcmpUnusedCode,
IcmpEchoRequest::new(0, 0),
);
let ip_builder = Ipv6PacketBuilder::new(
ip_config.remote_ip,
ip_config.local_ip,
64,
Ipv6Proto::Other(Ipv4Proto::Icmp.into()),
);
let buf = Buf::new(Vec::new(), ..)
.encapsulate(icmp_builder)
.encapsulate(ip_builder)
.serialize_vec_outer()
.unwrap();
crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device);
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
// Should not have dispatched the packet.
assert_eq!(get_counter_val(&non_sync_ctx, "receive_ipv6_packet"), 1);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 0);
// In IPv6, the next header value (ICMP(v4)) would have been considered
// unrecognized so an ICMP parameter problem response SHOULD be sent,
// but the netstack chooses to just drop the packet since we are not
// required to send the ICMP response.
assert_empty(non_sync_ctx.frames_sent().iter());
}
#[test]
fn test_invalid_icmpv6_in_ipv4() {
let ip_config = Ipv4::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(ip_config.clone()).build();
// First possible device id.
let device = DeviceId::new_ethernet(0);
let frame_dst = FrameDestination::Unicast;
let ic_config = Ipv6::DUMMY_CONFIG;
let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
ic_config.remote_ip,
ic_config.local_ip,
IcmpUnusedCode,
IcmpEchoRequest::new(0, 0),
);
let ip_builder = Ipv4PacketBuilder::new(
ip_config.remote_ip,
ip_config.local_ip,
64,
Ipv4Proto::Other(Ipv6Proto::Icmpv6.into()),
);
let buf = Buf::new(Vec::new(), ..)
.encapsulate(icmp_builder)
.encapsulate(ip_builder)
.serialize_vec_outer()
.unwrap();
receive_ipv4_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
// Should have dispatched the packet but resulted in an ICMP error.
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv4_packet"), 1);
assert_eq!(get_counter_val(&non_sync_ctx, "send_icmpv4_dest_unreachable"), 1);
assert_eq!(non_sync_ctx.frames_sent().len(), 1);
let buf = &non_sync_ctx.frames_sent()[0].1[..];
let (_, _, _, _, _, _, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpDestUnreachable, _>(
buf,
|_| {},
)
.unwrap();
assert_eq!(code, Icmpv4DestUnreachableCode::DestProtocolUnreachable);
}
#[ip_test]
fn test_joining_leaving_ip_multicast_group<
I: Ip
+ TestIpExt
+ packet_formats::ip::IpExt
+ crate::device::testutil::DeviceTestIpExt<DummyInstant>,
>() {
// Test receiving a packet destined to a multicast IP (and corresponding
// multicast MAC).
let config = I::DUMMY_CONFIG;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(config.clone()).build();
let device = DeviceId::new_ethernet(0);
let multi_addr = I::get_multicast_addr(3).get();
let dst_mac = Mac::from(&MulticastAddr::new(multi_addr).unwrap());
let buf = Buf::new(vec![0; 10], ..)
.encapsulate(I::PacketBuilder::new(
config.remote_ip.get(),
multi_addr,
64,
IpProto::Udp.into(),
))
.encapsulate(EthernetFrameBuilder::new(config.remote_mac.get(), dst_mac, I::ETHER_TYPE))
.serialize_vec_outer()
.ok()
.unwrap()
.into_inner();
let multi_addr = MulticastAddr::new(multi_addr).unwrap();
// Should not have dispatched the packet since we are not in the
// multicast group `multi_addr`.
assert!(!I::get_ip_device_state(&sync_ctx, device).multicast_groups.contains(&multi_addr));
receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone())
.expect("error receiving frame");
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 0);
// Join the multicast group and receive the packet, we should dispatch
// it.
match multi_addr.into() {
IpAddr::V4(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv4, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
multicast_addr,
),
IpAddr::V6(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv6, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
multicast_addr,
),
}
assert!(I::get_ip_device_state(&sync_ctx, device).multicast_groups.contains(&multi_addr));
receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone())
.expect("error receiving frame");
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
// Leave the multicast group and receive the packet, we should not
// dispatch it.
match multi_addr.into() {
IpAddr::V4(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv4, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
multicast_addr,
),
IpAddr::V6(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv6, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
device,
multicast_addr,
),
}
assert!(!I::get_ip_device_state(&sync_ctx, device).multicast_groups.contains(&multi_addr));
receive_frame(&mut sync_ctx, &mut non_sync_ctx, device, buf.clone())
.expect("error receiving frame");
assert_eq!(get_counter_val(&non_sync_ctx, dispatch_receive_ip_packet_name::<I>()), 1);
}
#[test]
fn test_no_dispatch_non_ndp_packets_during_ndp_dad() {
// Here we make sure we are not dispatching packets destined to a
// tentative address (that is performing NDP's Duplicate Address
// Detection (DAD)) -- IPv6 only.
let config = Ipv6::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::ip::device::update_ipv6_configuration(
&mut sync_ctx,
&mut non_sync_ctx,
device,
|config| {
config.ip_config.ip_enabled = true;
// Doesn't matter as long as DAD is enabled.
config.dad_transmits = NonZeroU8::new(1);
},
);
let frame_dst = FrameDestination::Unicast;
let ip: Ipv6Addr = config.local_mac.to_ipv6_link_local().addr().get();
let buf = Buf::new(vec![0; 10], ..)
.encapsulate(Ipv6PacketBuilder::new(config.remote_ip, ip, 64, IpProto::Udp.into()))
.serialize_vec_outer()
.unwrap()
.into_inner();
// Received packet should not have been dispatched.
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf.clone());
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 0);
// Wait until DAD is complete. Arbitrarily choose a year in the future
// as a time after which we're confident DAD will be complete. We can't
// run until there are no timers because some timers will always exist
// for background tasks.
//
// TODO(https://fxbug.dev/48578): Once this test is contextified, use a
// more precise condition to ensure that DAD is complete.
let now = non_sync_ctx.now();
let _: Vec<_> = non_sync_ctx.trigger_timers_until_instant(
&mut sync_ctx,
now + Duration::from_secs(60 * 60 * 24 * 365),
crate::handle_timer,
);
// Received packet should have been dispatched.
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 1);
// Set the new IP (this should trigger DAD).
let ip = config.local_ip.get();
crate::device::add_ip_addr_subnet(
&mut sync_ctx,
&mut non_sync_ctx,
device,
AddrSubnet::new(ip, 128).unwrap(),
)
.unwrap();
let buf = Buf::new(vec![0; 10], ..)
.encapsulate(Ipv6PacketBuilder::new(config.remote_ip, ip, 64, IpProto::Udp.into()))
.serialize_vec_outer()
.unwrap()
.into_inner();
// Received packet should not have been dispatched.
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf.clone());
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 1);
// Make sure all timers are done (DAD to complete on the interface due
// to new IP).
//
// TODO(https://fxbug.dev/48578): Once this test is contextified, use a
// more precise condition to ensure that DAD is complete.
let _: Vec<_> = non_sync_ctx.trigger_timers_until_instant(
&mut sync_ctx,
DummyInstant::LATEST,
crate::handle_timer,
);
// Received packet should have been dispatched.
receive_ipv6_packet(&mut sync_ctx, &mut non_sync_ctx, device, frame_dst, buf);
assert_eq!(get_counter_val(&non_sync_ctx, "dispatch_receive_ipv6_packet"), 2);
}
#[test]
fn test_drop_non_unicast_ipv6_source() {
// Test that an inbound IPv6 packet with a non-unicast source address is
// dropped.
let cfg = DUMMY_CONFIG_V6;
let Ctx { mut sync_ctx, mut non_sync_ctx } =
DummyEventDispatcherBuilder::from_config(cfg.clone()).build();
let device = crate::add_ethernet_device(
&mut sync_ctx,
&mut non_sync_ctx,
cfg.local_mac,
Ipv6::MINIMUM_LINK_MTU.into(),
);
crate::device::testutil::enable_device(&mut sync_ctx, &mut non_sync_ctx, device);
let ip: Ipv6Addr = cfg.local_mac.to_ipv6_link_local().addr().get();
let buf = Buf::new(vec![0; 10], ..)
.encapsulate(Ipv6PacketBuilder::new(
Ipv6::MULTICAST_SUBNET.network(),
ip,
64,
IpProto::Udp.into(),
))
.serialize_vec_outer()
.unwrap()
.into_inner();
receive_ipv6_packet(
&mut sync_ctx,
&mut non_sync_ctx,
device,
FrameDestination::Unicast,
buf.clone(),
);
assert_eq!(get_counter_val(&non_sync_ctx, "receive_ipv6_packet: non-unicast source"), 1);
}
#[test]
fn test_receive_ip_packet_action() {
let v4_config = Ipv4::DUMMY_CONFIG;
let v6_config = Ipv6::DUMMY_CONFIG;
let mut builder = DummyEventDispatcherBuilder::default();
// Both devices have the same MAC address, which is a bit weird, but not
// a problem for this test.
let v4_subnet = AddrSubnet::from_witness(v4_config.local_ip, 16).unwrap().subnet();
builder.add_device_with_ip(v4_config.local_mac, v4_config.local_ip.get(), v4_subnet);
builder.add_device_with_ip(
v6_config.local_mac,
v6_config.local_ip.get(),
AddrSubnet::from_witness(v6_config.local_ip, 64).unwrap().subnet(),
);
let v4_dev = DeviceId::new_ethernet(0);
let v6_dev = DeviceId::new_ethernet(1);
let Ctx { mut sync_ctx, mut non_sync_ctx } = builder.clone().build();
// Receive packet addressed to us.
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
v4_config.local_ip
),
ReceivePacketAction::Deliver
);
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
v6_config.local_ip
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to the IPv4 subnet broadcast address.
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
SpecifiedAddr::new(v4_subnet.broadcast()).unwrap()
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to the IPv4 limited broadcast address.
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
Ipv4::LIMITED_BROADCAST_ADDRESS
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to a multicast address we're subscribed to.
crate::ip::device::join_ip_multicast::<Ipv4, _, _>(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS,
);
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS.into_specified()
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to the all-nodes multicast address.
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.into_specified()
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to a multicast address we're subscribed to.
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
v6_config.local_ip.to_solicited_node_address().into_specified(),
),
ReceivePacketAction::Deliver
);
// Receive packet addressed to a tentative address.
{
// Construct a one-off context that has DAD enabled. The context
// built above has DAD disabled, and so addresses start off in the
// assigned state rather than the tentative state.
let Ctx { mut sync_ctx, mut non_sync_ctx } = DummyCtx::default();
let local_mac = v6_config.local_mac;
let device = crate::add_ethernet_device(
&mut sync_ctx,
&mut non_sync_ctx,
local_mac,
Ipv6::MINIMUM_LINK_MTU.into(),
);
crate::ip::device::update_ipv6_configuration(
&mut sync_ctx,
&mut non_sync_ctx,
device,
|config| {
config.ip_config.ip_enabled = true;
// Doesn't matter as long as DAD is enabled.
config.dad_transmits = NonZeroU8::new(1);
},
);
let tentative: UnicastAddr<Ipv6Addr> = local_mac.to_ipv6_link_local().addr().get();
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
device,
tentative.into_specified()
),
ReceivePacketAction::Drop { reason: DropReason::Tentative }
);
}
// Receive packet destined to a remote address when forwarding is
// disabled on the inbound interface.
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
v4_config.remote_ip
),
ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface }
);
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
v6_config.remote_ip
),
ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface }
);
// Receive packet destined to a remote address when forwarding is
// enabled both globally and on the inbound device.
set_routing_enabled::<_, _, Ipv4>(&mut sync_ctx, &mut non_sync_ctx, v4_dev, true)
.expect("error setting routing enabled");
set_routing_enabled::<_, _, Ipv6>(&mut sync_ctx, &mut non_sync_ctx, v6_dev, true)
.expect("error setting routing enabled");
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
v4_config.remote_ip
),
ReceivePacketAction::Forward {
dst: Destination { next_hop: v4_config.remote_ip, device: v4_dev }
}
);
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
v6_config.remote_ip
),
ReceivePacketAction::Forward {
dst: Destination { next_hop: v6_config.remote_ip, device: v6_dev }
}
);
// Receive packet destined to a host with no route when forwarding is
// enabled both globally and on the inbound device.
sync_ctx.state.ipv4.inner.table = Default::default();
sync_ctx.state.ipv6.inner.table = Default::default();
assert_eq!(
receive_ipv4_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v4_dev,
v4_config.remote_ip
),
ReceivePacketAction::SendNoRouteToDest
);
assert_eq!(
receive_ipv6_packet_action(
&mut sync_ctx,
&mut non_sync_ctx,
v6_dev,
v6_config.remote_ip
),
ReceivePacketAction::SendNoRouteToDest
);
}
}