blob: 8388551eba22cd3135097fdab4a940eafb5ae3d1 [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 Control Message Protocol (ICMP).
pub(crate) mod socket;
use core::{
convert::TryInto as _,
fmt::Debug,
num::{NonZeroU16, NonZeroU8},
ops::ControlFlow,
};
use lock_order::{lock::UnlockedAccess, relation::LockBefore, wrap::prelude::*};
use net_types::{
ip::{
GenericOverIp, Ip, IpAddress, IpMarked, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6SourceAddr,
Mtu, SubnetError,
},
LinkLocalAddress, LinkLocalUnicastAddr, MulticastAddress, SpecifiedAddr, UnicastAddr, Witness,
};
use packet::{
BufferMut, InnerPacketBuilder as _, ParsablePacket as _, ParseBuffer, Serializer,
TruncateDirection, TruncatingSerializer,
};
use packet_formats::{
icmp::{
ndp::{
options::{NdpOption, NdpOptionBuilder},
NdpPacket, NeighborAdvertisement, NonZeroNdpLifetime, OptionSequenceBuilder,
},
peek_message_type, IcmpDestUnreachable, IcmpEchoRequest, IcmpMessage, IcmpMessageType,
IcmpPacket, IcmpPacketBuilder, IcmpPacketRaw, IcmpParseArgs, IcmpTimeExceeded,
IcmpUnusedCode, Icmpv4DestUnreachableCode, Icmpv4Packet, Icmpv4ParameterProblem,
Icmpv4ParameterProblemCode, Icmpv4RedirectCode, Icmpv4TimeExceededCode,
Icmpv6DestUnreachableCode, Icmpv6Packet, Icmpv6PacketTooBig, Icmpv6ParameterProblem,
Icmpv6ParameterProblemCode, Icmpv6TimeExceededCode, MessageBody, OriginalPacket,
},
ip::{Ipv4Proto, Ipv6Proto},
ipv4::{Ipv4FragmentType, Ipv4Header},
ipv6::{ExtHdrParseError, Ipv6Header},
};
use tracing::{debug, error, trace};
use zerocopy::ByteSlice;
use crate::{
context::{
CounterContext, InstantBindingsTypes, InstantContext, ReferenceNotifiers, RngContext,
},
counters::Counter,
data_structures::token_bucket::TokenBucket,
device::{self, AnyDevice, DeviceIdContext, FrameDestination, StrongId as _, WeakId as _},
filter::{MaybeTransportPacket, TransportPacketSerializer},
ip::{
device::{
nud::{ConfirmationFlags, NudIpHandler},
route_discovery::Ipv6DiscoveredRoute,
IpAddressState, IpDeviceHandler, Ipv6DeviceHandler,
},
icmp::socket::{
BoundSockets, IcmpAddrSpec, IcmpEchoBindingsContext, IcmpEchoBindingsTypes, IcmpSockets,
},
path_mtu::PmtuHandler,
socket::{DefaultSendOptions, IpSocketHandler},
AddressStatus, EitherDeviceId, IpDeviceStateContext, IpExt, IpLayerHandler,
IpTransportContext, Ipv6PresentAddressStatus, MulticastMembershipHandler, SendIpPacketMeta,
TransportIpContext, TransportReceiveError, IPV6_DEFAULT_SUBNET,
},
socket::{
address::{AddrIsMappedError, AddrVecIter, ConnAddr, ConnIpAddr, SocketIpAddr},
datagram::{self},
AddrVec,
},
sync::Mutex,
BindingsContext, CoreCtx, StackState,
};
/// The IP packet hop limit for all NDP packets.
///
/// See [RFC 4861 section 4.1], [RFC 4861 section 4.2], [RFC 4861 section 4.2],
/// [RFC 4861 section 4.3], [RFC 4861 section 4.4], and [RFC 4861 section 4.5]
/// for more information.
///
/// [RFC 4861 section 4.1]: https://tools.ietf.org/html/rfc4861#section-4.1
/// [RFC 4861 section 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
/// [RFC 4861 section 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
/// [RFC 4861 section 4.4]: https://tools.ietf.org/html/rfc4861#section-4.4
/// [RFC 4861 section 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
pub(crate) const REQUIRED_NDP_IP_PACKET_HOP_LIMIT: u8 = 255;
/// The default number of ICMP error messages to send per second.
///
/// Beyond this rate, error messages will be silently dropped.
pub const DEFAULT_ERRORS_PER_SECOND: u64 = 2 << 16;
/// An ICMPv4 error type and code.
///
/// Each enum variant corresponds to a particular error type, and contains the
/// possible codes for that error type.
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum Icmpv4ErrorCode {
DestUnreachable(Icmpv4DestUnreachableCode),
Redirect(Icmpv4RedirectCode),
TimeExceeded(Icmpv4TimeExceededCode),
ParameterProblem(Icmpv4ParameterProblemCode),
}
impl<I: IcmpIpExt> GenericOverIp<I> for Icmpv4ErrorCode {
type Type = I::ErrorCode;
}
/// An ICMPv6 error type and code.
///
/// Each enum variant corresponds to a particular error type, and contains the
/// possible codes for that error type.
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum Icmpv6ErrorCode {
DestUnreachable(Icmpv6DestUnreachableCode),
PacketTooBig,
TimeExceeded(Icmpv6TimeExceededCode),
ParameterProblem(Icmpv6ParameterProblemCode),
}
impl<I: IcmpIpExt> GenericOverIp<I> for Icmpv6ErrorCode {
type Type = I::ErrorCode;
}
/// An ICMP error of either IPv4 or IPv6.
#[derive(Debug, Clone, Copy)]
pub enum IcmpErrorCode {
/// ICMPv4 error.
V4(Icmpv4ErrorCode),
/// ICMPv6 error.
V6(Icmpv6ErrorCode),
}
impl From<Icmpv4ErrorCode> for IcmpErrorCode {
fn from(v4_err: Icmpv4ErrorCode) -> Self {
IcmpErrorCode::V4(v4_err)
}
}
impl From<Icmpv6ErrorCode> for IcmpErrorCode {
fn from(v6_err: Icmpv6ErrorCode) -> Self {
IcmpErrorCode::V6(v6_err)
}
}
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
pub(crate) struct IcmpState<
I: IpExt + datagram::DualStackIpExt,
D: device::WeakId,
BT: IcmpBindingsTypes,
> {
pub(crate) sockets: IcmpSockets<I, D, BT>,
pub(crate) error_send_bucket: Mutex<TokenBucket<BT::Instant>>,
pub(crate) tx_counters: IcmpTxCounters<I>,
pub(crate) rx_counters: IcmpRxCounters<I>,
}
/// ICMP tx path counters.
pub type IcmpTxCounters<I> = IpMarked<I, IcmpTxCountersInner>;
/// ICMP tx path counters.
#[derive(Default)]
pub struct IcmpTxCountersInner {
/// Count of reply messages sent.
pub reply: Counter,
/// Count of protocol unreachable messages sent.
pub protocol_unreachable: Counter,
/// Count of host/address unreachable messages sent.
pub address_unreachable: Counter,
/// Count of port unreachable messages sent.
pub port_unreachable: Counter,
/// Count of net unreachable messages sent.
pub net_unreachable: Counter,
/// Count of ttl expired messages sent.
pub ttl_expired: Counter,
/// Count of packet too big messages sent.
pub packet_too_big: Counter,
/// Count of parameter problem messages sent.
pub parameter_problem: Counter,
/// Count of destination unreachable messages sent.
pub dest_unreachable: Counter,
/// Count of error messages sent.
pub error: Counter,
}
/// ICMP rx path counters.
pub type IcmpRxCounters<I> = IpMarked<I, IcmpRxCountersInner>;
/// ICMP rx path counters.
#[derive(Default)]
pub struct IcmpRxCountersInner {
/// Count of error messages received.
pub error: Counter,
/// Count of error messages delivered to the transport layer.
pub error_delivered_to_transport_layer: Counter,
/// Count of error messages delivered to a socket.
pub error_delivered_to_socket: Counter,
/// Count of echo request messages received.
pub echo_request: Counter,
/// Count of echo reply messages received.
pub echo_reply: Counter,
/// Count of timestamp request messages received.
pub timestamp_request: Counter,
/// Count of destination unreachable messages received.
pub dest_unreachable: Counter,
/// Count of time exceeded messages received.
pub time_exceeded: Counter,
/// Count of parameter problem messages received.
pub parameter_problem: Counter,
/// Count of packet too big messages received.
pub packet_too_big: Counter,
}
/// Receive NDP counters.
#[derive(Default)]
pub struct NdpRxCounters {
/// Count of neighbor solicitation messages received.
pub neighbor_solicitation: Counter,
/// Count of neighbor advertisement messages received.
pub neighbor_advertisement: Counter,
/// Count of router advertisement messages received.
pub router_advertisement: Counter,
/// Count of router solicitation messages received.
pub router_solicitation: Counter,
}
/// Transmit NDP counters.
#[derive(Default)]
pub struct NdpTxCounters {
/// Count of neighbor advertisement messages sent.
pub neighbor_advertisement: Counter,
/// Count of neighbor solicitation messages sent.
pub neighbor_solicitation: Counter,
}
/// Counters for NDP messages.
#[derive(Default)]
pub struct NdpCounters {
/// Receive counters.
pub rx: NdpRxCounters,
/// Transmit counters.
pub tx: NdpTxCounters,
}
impl<BC: BindingsContext, I: datagram::DualStackIpExt>
UnlockedAccess<crate::lock_ordering::IcmpTxCounters<I>> for StackState<BC>
{
type Data = IcmpTxCounters<I>;
type Guard<'l> = &'l IcmpTxCounters<I> where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
&self.inner_icmp_state().tx_counters
}
}
impl<BC: BindingsContext, I: datagram::DualStackIpExt, L> CounterContext<IcmpTxCounters<I>>
for CoreCtx<'_, BC, L>
{
fn with_counters<O, F: FnOnce(&IcmpTxCounters<I>) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::IcmpTxCounters<I>>())
}
}
impl<BC: BindingsContext, I: datagram::DualStackIpExt>
UnlockedAccess<crate::lock_ordering::IcmpRxCounters<I>> for StackState<BC>
{
type Data = IcmpRxCounters<I>;
type Guard<'l> = &'l IcmpRxCounters<I> where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
&self.inner_icmp_state().rx_counters
}
}
impl<BC: BindingsContext, I: datagram::DualStackIpExt, L> CounterContext<IcmpRxCounters<I>>
for CoreCtx<'_, BC, L>
{
fn with_counters<O, F: FnOnce(&IcmpRxCounters<I>) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::IcmpRxCounters<I>>())
}
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::NdpCounters> for StackState<BC> {
type Data = NdpCounters;
type Guard<'l> = &'l NdpCounters where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
self.ndp_counters()
}
}
impl<BC: BindingsContext, L> CounterContext<NdpCounters> for CoreCtx<'_, BC, L> {
fn with_counters<O, F: FnOnce(&NdpCounters) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::NdpCounters>())
}
}
/// A builder for ICMPv4 state.
#[derive(Copy, Clone)]
pub(crate) struct Icmpv4StateBuilder {
send_timestamp_reply: bool,
errors_per_second: u64,
}
impl Default for Icmpv4StateBuilder {
fn default() -> Icmpv4StateBuilder {
Icmpv4StateBuilder {
send_timestamp_reply: false,
errors_per_second: DEFAULT_ERRORS_PER_SECOND,
}
}
}
impl Icmpv4StateBuilder {
/// Enable or disable replying to ICMPv4 Timestamp Request messages with
/// Timestamp Reply messages (default: disabled).
///
/// Enabling this can introduce a very minor vulnerability in which an
/// attacker can learn the system clock's time, which in turn can aid in
/// attacks against time-based authentication systems.
#[cfg(test)]
pub(crate) fn send_timestamp_reply(&mut self, send_timestamp_reply: bool) -> &mut Self {
self.send_timestamp_reply = send_timestamp_reply;
self
}
pub(crate) fn build<D: device::WeakId, BT: IcmpBindingsTypes>(self) -> Icmpv4State<D, BT> {
Icmpv4State {
inner: IcmpState {
sockets: Default::default(),
error_send_bucket: Mutex::new(TokenBucket::new(self.errors_per_second)),
tx_counters: Default::default(),
rx_counters: Default::default(),
},
send_timestamp_reply: self.send_timestamp_reply,
}
}
}
/// The state associated with the ICMPv4 protocol.
pub(crate) struct Icmpv4State<D: device::WeakId, BT: IcmpBindingsTypes> {
pub(crate) inner: IcmpState<Ipv4, D, BT>,
send_timestamp_reply: bool,
}
// Used by `receive_icmp_echo_reply`.
impl<D: device::WeakId, BT: IcmpBindingsTypes> AsRef<IcmpState<Ipv4, D, BT>>
for Icmpv4State<D, BT>
{
fn as_ref(&self) -> &IcmpState<Ipv4, D, BT> {
&self.inner
}
}
// Used by `send_icmpv4_echo_request_inner`.
impl<D: device::WeakId, BT: IcmpBindingsTypes> AsMut<IcmpState<Ipv4, D, BT>>
for Icmpv4State<D, BT>
{
fn as_mut(&mut self) -> &mut IcmpState<Ipv4, D, BT> {
&mut self.inner
}
}
/// A builder for ICMPv6 state.
#[derive(Copy, Clone)]
pub(crate) struct Icmpv6StateBuilder {
errors_per_second: u64,
}
impl Default for Icmpv6StateBuilder {
fn default() -> Icmpv6StateBuilder {
Icmpv6StateBuilder { errors_per_second: DEFAULT_ERRORS_PER_SECOND }
}
}
impl Icmpv6StateBuilder {
pub(crate) fn build<D: device::WeakId, BT: IcmpBindingsTypes>(self) -> Icmpv6State<D, BT> {
Icmpv6State {
inner: IcmpState {
sockets: Default::default(),
error_send_bucket: Mutex::new(TokenBucket::new(self.errors_per_second)),
tx_counters: Default::default(),
rx_counters: Default::default(),
},
ndp_counters: Default::default(),
}
}
}
/// The state associated with the ICMPv6 protocol.
pub(crate) struct Icmpv6State<D: device::WeakId, BT: IcmpBindingsTypes> {
pub(crate) inner: IcmpState<Ipv6, D, BT>,
pub(crate) ndp_counters: NdpCounters,
}
// Used by `receive_icmp_echo_reply`.
impl<D: device::WeakId, BT: IcmpBindingsTypes> AsRef<IcmpState<Ipv6, D, BT>>
for Icmpv6State<D, BT>
{
fn as_ref(&self) -> &IcmpState<Ipv6, D, BT> {
&self.inner
}
}
// Used by `send_icmpv6_echo_request_inner`.
impl<D: device::WeakId, BT: IcmpBindingsTypes> AsMut<IcmpState<Ipv6, D, BT>>
for Icmpv6State<D, BT>
{
fn as_mut(&mut self) -> &mut IcmpState<Ipv6, D, BT> {
&mut self.inner
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) struct IcmpAddr<A: IpAddress> {
local_addr: SocketIpAddr<A>,
remote_addr: SocketIpAddr<A>,
icmp_id: u16,
}
/// An extension trait adding extra ICMP-related functionality to IP versions.
pub trait IcmpIpExt: packet_formats::ip::IpExt + packet_formats::icmp::IcmpIpExt {
/// The type of error code for this version of ICMP - [`Icmpv4ErrorCode`] or
/// [`Icmpv6ErrorCode`].
type ErrorCode: Debug
+ Copy
+ PartialEq
+ GenericOverIp<Self, Type = Self::ErrorCode>
+ GenericOverIp<Ipv4, Type = Icmpv4ErrorCode>
+ GenericOverIp<Ipv6, Type = Icmpv6ErrorCode>
+ Into<IcmpErrorCode>;
}
impl IcmpIpExt for Ipv4 {
type ErrorCode = Icmpv4ErrorCode;
}
impl IcmpIpExt for Ipv6 {
type ErrorCode = Icmpv6ErrorCode;
}
/// An extension trait providing ICMP handler properties.
pub(crate) trait IcmpHandlerIpExt: Ip {
type SourceAddress: Witness<Self::Addr>;
type IcmpError;
}
impl IcmpHandlerIpExt for Ipv4 {
type SourceAddress = SpecifiedAddr<Ipv4Addr>;
type IcmpError = Icmpv4Error;
}
impl IcmpHandlerIpExt for Ipv6 {
type SourceAddress = UnicastAddr<Ipv6Addr>;
type IcmpError = Icmpv6ErrorKind;
}
/// A kind of ICMPv4 error.
pub(crate) enum Icmpv4ErrorKind {
ParameterProblem {
code: Icmpv4ParameterProblemCode,
pointer: u8,
fragment_type: Ipv4FragmentType,
},
TtlExpired {
proto: Ipv4Proto,
fragment_type: Ipv4FragmentType,
},
NetUnreachable {
proto: Ipv4Proto,
fragment_type: Ipv4FragmentType,
},
ProtocolUnreachable,
PortUnreachable,
}
/// An ICMPv4 error.
pub(crate) struct Icmpv4Error {
pub(super) kind: Icmpv4ErrorKind,
pub(super) header_len: usize,
}
/// A kind of ICMPv6 error.
pub(crate) enum Icmpv6ErrorKind {
ParameterProblem { code: Icmpv6ParameterProblemCode, pointer: u32, allow_dst_multicast: bool },
TtlExpired { proto: Ipv6Proto, header_len: usize },
NetUnreachable { proto: Ipv6Proto, header_len: usize },
PacketTooBig { proto: Ipv6Proto, header_len: usize, mtu: Mtu },
ProtocolUnreachable { header_len: usize },
PortUnreachable,
}
/// The handler exposed by ICMP.
pub(crate) trait IcmpErrorHandler<I: IcmpHandlerIpExt, BC>:
DeviceIdContext<AnyDevice>
{
/// Sends an error message in response to an incoming packet.
///
/// `src_ip` and `dst_ip` are the source and destination addresses of the
/// incoming packet.
fn send_icmp_error_message<B: BufferMut>(
&mut self,
bindings_ctx: &mut BC,
device: &Self::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: I::SourceAddress,
dst_ip: SpecifiedAddr<I::Addr>,
original_packet: B,
error: I::IcmpError,
);
}
impl<
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
> IcmpErrorHandler<Ipv4, BC> for CC
{
fn send_icmp_error_message<B: BufferMut>(
&mut self,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SpecifiedAddr<Ipv4Addr>,
dst_ip: SpecifiedAddr<Ipv4Addr>,
original_packet: B,
Icmpv4Error { kind, header_len }: Icmpv4Error,
) {
let src_ip = SocketIpAddr::new_ipv4_specified(src_ip);
let dst_ip = SocketIpAddr::new_ipv4_specified(dst_ip);
match kind {
Icmpv4ErrorKind::ParameterProblem { code, pointer, fragment_type } => {
send_icmpv4_parameter_problem(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
code,
Icmpv4ParameterProblem::new(pointer),
original_packet,
header_len,
fragment_type,
)
}
Icmpv4ErrorKind::TtlExpired { proto, fragment_type } => send_icmpv4_ttl_expired(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
original_packet,
header_len,
fragment_type,
),
Icmpv4ErrorKind::NetUnreachable { proto, fragment_type } => {
send_icmpv4_net_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
original_packet,
header_len,
fragment_type,
)
}
Icmpv4ErrorKind::ProtocolUnreachable => send_icmpv4_protocol_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
original_packet,
header_len,
),
Icmpv4ErrorKind::PortUnreachable => send_icmpv4_port_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
original_packet,
header_len,
),
}
}
}
impl<
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
> IcmpErrorHandler<Ipv6, BC> for CC
{
fn send_icmp_error_message<B: BufferMut>(
&mut self,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: UnicastAddr<Ipv6Addr>,
dst_ip: SpecifiedAddr<Ipv6Addr>,
original_packet: B,
error: Icmpv6ErrorKind,
) {
let src_ip: SocketIpAddr<Ipv6Addr> = match src_ip.into_specified().try_into() {
Ok(addr) => addr,
Err(AddrIsMappedError {}) => {
trace!("send_icmpv6_error_message: src_ip is mapped");
return;
}
};
let dst_ip: SocketIpAddr<Ipv6Addr> = match dst_ip.try_into() {
Ok(addr) => addr,
Err(AddrIsMappedError {}) => {
trace!("send_icmpv6_error_message: dst_ip is mapped");
return;
}
};
match error {
Icmpv6ErrorKind::ParameterProblem { code, pointer, allow_dst_multicast } => {
send_icmpv6_parameter_problem(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
code,
Icmpv6ParameterProblem::new(pointer),
original_packet,
allow_dst_multicast,
)
}
Icmpv6ErrorKind::TtlExpired { proto, header_len } => send_icmpv6_ttl_expired(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
original_packet,
header_len,
),
Icmpv6ErrorKind::NetUnreachable { proto, header_len } => send_icmpv6_net_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
original_packet,
header_len,
),
Icmpv6ErrorKind::PacketTooBig { proto, header_len, mtu } => send_icmpv6_packet_too_big(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
proto,
mtu,
original_packet,
header_len,
),
Icmpv6ErrorKind::ProtocolUnreachable { header_len } => {
send_icmpv6_protocol_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
original_packet,
header_len,
)
}
Icmpv6ErrorKind::PortUnreachable => send_icmpv6_port_unreachable(
self,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
original_packet,
),
}
}
}
/// A marker for all the contexts provided by bindings require by the ICMP
/// module.
pub trait IcmpBindingsContext<I: socket::IpExt, D: device::StrongId>:
InstantContext + IcmpEchoBindingsContext<I, D> + RngContext + ReferenceNotifiers + IcmpBindingsTypes
{
}
impl<
I: socket::IpExt,
BC: InstantContext
+ IcmpEchoBindingsContext<I, D>
+ RngContext
+ ReferenceNotifiers
+ IcmpBindingsTypes,
D: device::StrongId,
> IcmpBindingsContext<I, D> for BC
{
}
/// A marker trait for all bindings types required by the ICMP module.
pub trait IcmpBindingsTypes: InstantBindingsTypes + IcmpEchoBindingsTypes {}
impl<BT: InstantBindingsTypes + IcmpEchoBindingsTypes> IcmpBindingsTypes for BT {}
/// Empty trait to work around coherence issues.
///
/// This serves only to convince the coherence checker that a particular blanket
/// trait implementation could only possibly conflict with other blanket impls
/// in this crate. It can be safely implemented for any type.
/// TODO(https://github.com/rust-lang/rust/issues/97811): Remove this once the
/// coherence checker doesn't require it.
pub trait IcmpStateContext {}
/// The execution context shared by ICMP(v4) and ICMPv6 for the internal
/// operations of the IP stack.
///
/// Unlike [`IcmpEchoBindingsContext`], `InnerIcmpContext` is not exposed outside of
/// this crate.
pub trait InnerIcmpContext<I: socket::IpExt, BC: IcmpBindingsContext<I, Self::DeviceId>>:
IpSocketHandler<I, BC> + DeviceIdContext<AnyDevice>
{
type DualStackContext: datagram::DualStackDatagramBoundStateContext<
I,
BC,
socket::Icmp<BC>,
DeviceId = Self::DeviceId,
WeakDeviceId = Self::WeakDeviceId,
>
where
I: datagram::IpExt;
/// The core context passed to the callback provided to methods.
type IpSocketsCtx<'a>: TransportIpContext<I, BC>
+ MulticastMembershipHandler<I, BC>
+ DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>
+ IcmpStateContext;
// TODO(joshlf): If we end up needing to respond to these messages with new
// outbound packets, then perhaps it'd be worth passing the original buffer
// so that it can be reused?
//
// NOTE(joshlf): We don't guarantee the packet body length here for two
// reasons:
// - It's possible that some IPv4 protocol does or will exist for which
// valid packets are less than 8 bytes in length. If we were to reject all
// packets with bodies of less than 8 bytes, we might silently discard
// legitimate error messages for such protocols.
// - Even if we were to guarantee this, there's no good way to encode such a
// guarantee in the type system, and so the caller would have no recourse
// but to panic, and panics have a habit of becoming bugs or DoS
// vulnerabilities when invariants change.
/// Receives an ICMP error message and demultiplexes it to a transport layer
/// protocol.
///
/// 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 on which the packet was received.
///
/// 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(
&mut self,
bindings_ctx: &mut BC,
device: &Self::DeviceId,
original_src_ip: Option<SpecifiedAddr<I::Addr>>,
original_dst_ip: SpecifiedAddr<I::Addr>,
original_proto: I::Proto,
original_body: &[u8],
err: I::ErrorCode,
);
/// Calls the function with a mutable reference to `IpSocketsCtx` and
/// a mutable reference to ICMP sockets.
fn with_icmp_ctx_and_sockets_mut<
O,
F: FnOnce(&mut Self::IpSocketsCtx<'_>, &mut BoundSockets<I, Self::WeakDeviceId, BC>) -> O,
>(
&mut self,
cb: F,
) -> O;
/// Calls the function with a mutable reference to ICMP error send tocket
/// bucket.
fn with_error_send_bucket_mut<O, F: FnOnce(&mut TokenBucket<BC::Instant>) -> O>(
&mut self,
cb: F,
) -> O;
}
/// The execution context for ICMPv4.
///
/// `InnerIcmpv4Context` is a shorthand for a larger collection of traits.
pub(crate) trait InnerIcmpv4Context<BC: IcmpBindingsContext<Ipv4, Self::DeviceId>>:
InnerIcmpContext<Ipv4, BC>
{
/// Returns true if a timestamp reply may be sent.
fn should_send_timestamp_reply(&self) -> bool;
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::IcmpSendTimestampReply<Ipv4>>
for StackState<BC>
{
type Data = bool;
type Guard<'l> = &'l bool where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
&self.ipv4.icmp.send_timestamp_reply
}
}
impl<
BC: BindingsContext,
L: LockBefore<crate::lock_ordering::IcmpBoundMap<Ipv4>>
+ LockBefore<crate::lock_ordering::TcpAllSocketsSet<Ipv4>>
+ LockBefore<crate::lock_ordering::UdpAllSocketsSet<Ipv4>>,
> InnerIcmpv4Context<BC> for CoreCtx<'_, BC, L>
{
fn should_send_timestamp_reply(&self) -> bool {
*self.unlocked_access::<crate::lock_ordering::IcmpSendTimestampReply<Ipv4>>()
}
}
/// The execution context for ICMPv6.
///
/// `InnerIcmpv6Context` is a shorthand for a larger collection of traits.
pub(crate) trait InnerIcmpv6Context<BC: IcmpBindingsContext<Ipv6, Self::DeviceId>>:
InnerIcmpContext<Ipv6, BC>
{
}
impl<BC: IcmpBindingsContext<Ipv6, Self::DeviceId>, CC: InnerIcmpContext<Ipv6, BC>>
InnerIcmpv6Context<BC> for CC
{
}
/// Attempt to send an ICMP or ICMPv6 error message, applying a rate limit.
///
/// `try_send_error!($core_ctx, $bindings_ctx, $e)` attempts to consume a token from the
/// token bucket at `$core_ctx.get_state_mut().error_send_bucket`. If it
/// succeeds, it invokes the expression `$e`, and otherwise does nothing. It
/// assumes that the type of `$e` is `Result<(), _>` and, in the case that the
/// rate limit is exceeded and it does not invoke `$e`, returns `Ok(())`.
///
/// [RFC 4443 Section 2.4] (f) requires that we MUST limit the rate of outbound
/// ICMPv6 error messages. To our knowledge, there is no similar requirement for
/// ICMPv4, but the same rationale applies, so we do it for ICMPv4 as well.
///
/// [RFC 4443 Section 2.4]: https://tools.ietf.org/html/rfc4443#section-2.4
macro_rules! try_send_error {
($core_ctx:expr, $bindings_ctx:expr, $e:expr) => {{
// TODO(joshlf): Figure out a way to avoid querying for the current time
// unconditionally. See the documentation on the `CachedInstantCtx` type
// for more information.
let instant_ctx = crate::context::new_cached_instant_context($bindings_ctx);
let send = $core_ctx.with_error_send_bucket_mut(|error_send_bucket| {
error_send_bucket.try_take(&instant_ctx)
});
if send {
$core_ctx.increment(|counters| &counters.error);
$e
} else {
trace!("ip::icmp::try_send_error!: dropping rate-limited ICMP error message");
Ok(())
}
}};
}
/// An implementation of [`IpTransportContext`] for ICMP.
pub(crate) enum IcmpIpTransportContext {}
fn receive_ip_transport_icmp_error<
I: socket::IpExt,
CC: InnerIcmpContext<I, BC> + CounterContext<IcmpRxCounters<I>>,
BC: IcmpBindingsContext<I, CC::DeviceId>,
>(
core_ctx: &mut CC,
original_src_ip: Option<SpecifiedAddr<I::Addr>>,
original_dst_ip: SpecifiedAddr<I::Addr>,
mut original_body: &[u8],
err: I::ErrorCode,
) {
core_ctx.increment(|counters| &counters.error_delivered_to_transport_layer);
trace!("IcmpIpTransportContext::receive_icmp_error({:?})", err);
let echo_request =
if let Ok(echo_request) = original_body.parse::<IcmpPacketRaw<I, _, IcmpEchoRequest>>() {
echo_request
} else {
// NOTE: This might just mean that the error message was in response
// to a packet that we sent that wasn't an echo request, so we just
// silently ignore it.
return;
};
let original_src_ip = match original_src_ip {
Some(ip) => ip,
None => {
trace!("IcmpIpTransportContext::receive_icmp_error: unspecified source IP address");
return;
}
};
let original_src_ip: SocketIpAddr<_> = match original_src_ip.try_into() {
Ok(ip) => ip,
Err(AddrIsMappedError {}) => {
trace!("IcmpIpTransportContext::receive_icmp_error: mapped source IP address");
return;
}
};
let original_dst_ip: SocketIpAddr<_> = match original_dst_ip.try_into() {
Ok(ip) => ip,
Err(AddrIsMappedError {}) => {
trace!("IcmpIpTransportContext::receive_icmp_error: mapped destination IP address");
return;
}
};
let id = echo_request.message().id();
// NB: We extract out whether a socket was found to update the counter
// later, which simplifies the trait bounds on the inner context.
let delivered = core_ctx.with_icmp_ctx_and_sockets_mut(|_, sockets| {
if let Some(conn) = sockets.socket_map.conns().get_by_addr(&ConnAddr {
ip: ConnIpAddr {
local: (original_src_ip, NonZeroU16::new(id).unwrap()),
remote: (original_dst_ip, ())
},
device: None,
}) {
// NB: At the moment bindings has no need to consume ICMP
// errors, so we swallow them here.
debug!(
"ICMP received ICMP error {:?} from {:?}, to {:?} on socket {:?}",
err,
original_dst_ip,
original_src_ip,
conn
);
true
} else {
trace!("IcmpIpTransportContext::receive_icmp_error: Got ICMP error message for nonexistent ICMP echo socket; either the socket responsible has since been removed, or the error message was sent in error or corrupted");
false
}
});
if delivered {
core_ctx.increment(|counters: &IcmpRxCounters<I>| &counters.error_delivered_to_socket);
}
}
impl<
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC>
+ PmtuHandler<Ipv4, BC>
+ CounterContext<IcmpRxCounters<Ipv4>>
+ CounterContext<IcmpTxCounters<Ipv4>>,
> IpTransportContext<Ipv4, BC, CC> for IcmpIpTransportContext
{
fn receive_icmp_error(
core_ctx: &mut CC,
_bindings_ctx: &mut BC,
_device: &CC::DeviceId,
original_src_ip: Option<SpecifiedAddr<Ipv4Addr>>,
original_dst_ip: SpecifiedAddr<Ipv4Addr>,
original_body: &[u8],
err: Icmpv4ErrorCode,
) {
receive_ip_transport_icmp_error(
core_ctx,
original_src_ip,
original_dst_ip,
original_body,
err,
)
}
fn receive_ip_packet<B: BufferMut>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
mut buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
trace!(
"<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet({}, {})",
src_ip,
dst_ip
);
let packet =
match buffer.parse_with::<_, Icmpv4Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) {
Ok(packet) => packet,
Err(_) => return Ok(()), // TODO(joshlf): Do something else here?
};
match packet {
Icmpv4Packet::EchoRequest(echo_request) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.echo_request);
if let Some(src_ip) = SpecifiedAddr::new(src_ip) {
let req = *echo_request.message();
let code = echo_request.code();
let (local_ip, remote_ip) = (dst_ip, src_ip);
// TODO(joshlf): Do something if send_icmp_reply returns an
// error?
let _ = send_icmp_reply(
core_ctx,
bindings_ctx,
Some(device),
SocketIpAddr::new_ipv4_specified(remote_ip),
SocketIpAddr::new_ipv4_specified(local_ip),
|src_ip| {
buffer.encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
src_ip,
remote_ip,
code,
req.reply(),
))
},
);
} else {
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received echo request with an unspecified source address");
}
}
Icmpv4Packet::EchoReply(echo_reply) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.echo_reply);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received an EchoReply message");
let id = echo_reply.message().id();
let meta = echo_reply.parse_metadata();
buffer.undo_parse(meta);
let device = device.downgrade();
receive_icmp_echo_reply(core_ctx, bindings_ctx, src_ip, dst_ip, id, buffer, device);
}
Icmpv4Packet::TimestampRequest(timestamp_request) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.timestamp_request);
if let Some(src_ip) = SpecifiedAddr::new(src_ip) {
if core_ctx.should_send_timestamp_reply() {
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Responding to Timestamp Request message");
// We're supposed to respond with the time that we
// processed this message as measured in milliseconds
// since midnight UT. However, that would require that
// we knew the local time zone and had a way to convert
// `InstantContext::Instant` to a `u32` value. We can't
// do that, and probably don't want to introduce all of
// the machinery necessary just to support this one use
// case. Luckily, RFC 792 page 17 provides us with an
// out:
//
// If the time is not available in miliseconds [sic]
// or cannot be provided with respect to midnight UT
// then any time can be inserted in a timestamp
// provided the high order bit of the timestamp is
// also set to indicate this non-standard value.
//
// Thus, we provide a zero timestamp with the high order
// bit set.
const NOW: u32 = 0x80000000;
let reply = timestamp_request.message().reply(NOW, NOW);
let (local_ip, remote_ip) = (dst_ip, src_ip);
// We don't actually want to use any of the _contents_
// of the buffer, but we would like to reuse it as
// scratch space. Eventually, `IcmpPacketBuilder` will
// implement `InnerPacketBuilder` for messages without
// bodies, but until that happens, we need to give it an
// empty buffer.
buffer.shrink_front_to(0);
// TODO(joshlf): Do something if send_icmp_reply returns
// an error?
let _ = send_icmp_reply(
core_ctx,
bindings_ctx,
Some(device),
SocketIpAddr::new_ipv4_specified(remote_ip),
SocketIpAddr::new_ipv4_specified(local_ip),
|src_ip| {
buffer.encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
src_ip,
remote_ip,
IcmpUnusedCode,
reply,
))
},
);
} else {
trace!(
"<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Silently ignoring Timestamp Request message"
);
}
} else {
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received timestamp request with an unspecified source address");
}
}
Icmpv4Packet::TimestampReply(_) => {
// TODO(joshlf): Support sending Timestamp Requests and
// receiving Timestamp Replies?
debug!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received unsolicited Timestamp Reply message");
}
Icmpv4Packet::DestUnreachable(dest_unreachable) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.dest_unreachable);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received a Destination Unreachable message");
if dest_unreachable.code() == Icmpv4DestUnreachableCode::FragmentationRequired {
if let Some(next_hop_mtu) = dest_unreachable.message().next_hop_mtu() {
// We are updating the path MTU from the destination
// address of this `packet` (which is an IP address on
// this node) to some remote (identified by the source
// address of this `packet`).
//
// `update_pmtu_if_less` may return an error, but it
// will only happen if the Dest Unreachable message's
// MTU field had a value that was less than the IPv4
// minimum MTU (which as per IPv4 RFC 791, must not
// happen).
core_ctx.update_pmtu_if_less(
bindings_ctx,
dst_ip.get(),
src_ip,
Mtu::new(u32::from(next_hop_mtu.get())),
);
} else {
// If the Next-Hop MTU from an incoming ICMP message is
// `0`, then we assume the source node of the ICMP
// message does not implement RFC 1191 and therefore
// does not actually use the Next-Hop MTU field and
// still considers it as an unused field.
//
// In this case, the only information we have is the
// size of the original IP packet that was too big (the
// original packet header should be included in the ICMP
// response). Here we will simply reduce our PMTU
// estimate to a value less than the total length of the
// original packet. See RFC 1191 Section 5.
//
// `update_pmtu_next_lower` may return an error, but it
// will only happen if no valid lower value exists from
// the original packet's length. It is safe to silently
// ignore the error when we have no valid lower PMTU
// value as the node from `src_ip` would not be IP RFC
// compliant and we expect this to be very rare (for
// IPv4, the lowest MTU value for a link can be 68
// bytes).
let original_packet_buf = dest_unreachable.body().bytes();
if original_packet_buf.len() >= 4 {
// We need the first 4 bytes as the total length
// field is at bytes 2/3 of the original packet
// buffer.
let total_len =
u16::from_be_bytes(original_packet_buf[2..4].try_into().unwrap());
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Next-Hop MTU is 0 so using the next best PMTU value from {}", total_len);
core_ctx.update_pmtu_next_lower(
bindings_ctx,
dst_ip.get(),
src_ip,
Mtu::new(u32::from(total_len)),
);
} else {
// Ok to silently ignore as RFC 792 requires nodes
// to send the original IP packet header + 64 bytes
// of the original IP packet's body so the node
// itself is already violating the RFC.
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Original packet buf is too small to get original packet len so ignoring");
}
}
}
receive_icmpv4_error(
core_ctx,
bindings_ctx,
device,
&dest_unreachable,
Icmpv4ErrorCode::DestUnreachable(dest_unreachable.code()),
);
}
Icmpv4Packet::TimeExceeded(time_exceeded) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.time_exceeded);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received a Time Exceeded message");
receive_icmpv4_error(
core_ctx,
bindings_ctx,
device,
&time_exceeded,
Icmpv4ErrorCode::TimeExceeded(time_exceeded.code()),
);
}
// TODO(https://fxbug.dev/323400954): Support ICMP Redirect.
Icmpv4Packet::Redirect(_) => debug!(
"Unimplemented: <IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet::redirect"
),
Icmpv4Packet::ParameterProblem(parameter_problem) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv4>| &counters.parameter_problem);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv4>>::receive_ip_packet: Received a Parameter Problem message");
receive_icmpv4_error(
core_ctx,
bindings_ctx,
device,
&parameter_problem,
Icmpv4ErrorCode::ParameterProblem(parameter_problem.code()),
);
}
}
Ok(())
}
}
pub(crate) fn send_ndp_packet<BC, CC, S, M>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
src_ip: Option<SpecifiedAddr<Ipv6Addr>>,
dst_ip: SpecifiedAddr<Ipv6Addr>,
body: S,
code: M::Code,
message: M,
) -> Result<(), S>
where
CC: IpLayerHandler<Ipv6, BC>,
S: Serializer,
S::Buffer: BufferMut,
M: IcmpMessage<Ipv6> + MaybeTransportPacket,
{
// TODO(https://fxbug.dev/42177356): Send through ICMPv6 send path.
IpLayerHandler::<Ipv6, _>::send_ip_packet_from_device(
core_ctx,
bindings_ctx,
SendIpPacketMeta {
device: device_id,
src_ip,
dst_ip,
broadcast: None,
next_hop: dst_ip,
ttl: NonZeroU8::new(REQUIRED_NDP_IP_PACKET_HOP_LIMIT),
proto: Ipv6Proto::Icmpv6,
mtu: None,
},
body.encapsulate(IcmpPacketBuilder::<Ipv6, _>::new(
src_ip.map_or(Ipv6::UNSPECIFIED_ADDRESS, |a| a.get()),
dst_ip.get(),
code,
message,
)),
)
.map_err(|s| s.into_inner())
}
fn send_neighbor_advertisement<
BC,
CC: Ipv6DeviceHandler<BC>
+ IpDeviceHandler<Ipv6, BC>
+ IpLayerHandler<Ipv6, BC>
+ CounterContext<NdpCounters>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
solicited: bool,
device_addr: UnicastAddr<Ipv6Addr>,
dst_ip: SpecifiedAddr<Ipv6Addr>,
) {
core_ctx.increment(|counters| &counters.tx.neighbor_advertisement);
debug!("send_neighbor_advertisement from {:?} to {:?}", device_addr, dst_ip);
// We currently only allow the destination address to be:
// 1) a unicast address.
// 2) a multicast destination but the message should be an unsolicited
// neighbor advertisement.
// NOTE: this assertion may need change if more messages are to be allowed
// in the future.
debug_assert!(dst_ip.is_valid_unicast() || (!solicited && dst_ip.is_multicast()));
// We must call into the higher level send_ip_packet_from_device function
// because it is not guaranteed that we actually know the link-layer
// address of the destination IP. Typically, the solicitation request will
// carry that information, but it is not necessary. So it is perfectly valid
// that trying to send this advertisement will end up triggering a neighbor
// solicitation to be sent.
let src_ll = core_ctx.get_link_layer_addr_bytes(&device_id);
// Nothing reasonable to do with the error.
let advertisement = NeighborAdvertisement::new(
core_ctx.is_router_device(&device_id),
solicited,
false,
device_addr.get(),
);
let _: Result<(), _> = send_ndp_packet(
core_ctx,
bindings_ctx,
&device_id,
Some(device_addr.into_specified()),
dst_ip,
OptionSequenceBuilder::new(
src_ll.as_ref().map(AsRef::as_ref).map(NdpOptionBuilder::TargetLinkLayerAddress).iter(),
)
.into_serializer(),
IcmpUnusedCode,
advertisement,
);
}
fn receive_ndp_packet<
B: ByteSlice,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC>
+ Ipv6DeviceHandler<BC>
+ IpDeviceHandler<Ipv6, BC>
+ IpDeviceStateContext<Ipv6, BC>
+ NudIpHandler<Ipv6, BC>
+ IpLayerHandler<Ipv6, BC>
+ CounterContext<NdpCounters>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
src_ip: Ipv6SourceAddr,
packet: NdpPacket<B>,
) {
// TODO(https://fxbug.dev/42179534): Make sure IP's hop limit is set to 255 as
// per RFC 4861 section 6.1.2.
match packet {
NdpPacket::RouterSolicitation(_) | NdpPacket::Redirect(_) => {}
NdpPacket::NeighborSolicitation(ref p) => {
let target_address = p.message().target_address();
let target_address = match UnicastAddr::new(*target_address) {
Some(a) => a,
None => {
trace!(
"dropping NS from {} with non-unicast target={:?}",
src_ip,
target_address
);
return;
}
};
core_ctx.increment(|counters| &counters.rx.neighbor_solicitation);
match src_ip {
Ipv6SourceAddr::Unspecified => {
// The neighbor is performing Duplicate address detection.
//
// As per RFC 4861 section 4.3,
//
// Source Address
// Either an address assigned to the interface from
// which this message is sent or (if Duplicate Address
// Detection is in progress [ADDRCONF]) the
// unspecified address.
match Ipv6DeviceHandler::remove_duplicate_tentative_address(
core_ctx,
bindings_ctx,
&device_id,
target_address,
) {
IpAddressState::Assigned => {
// Address is assigned to us to we let the
// remote node performing DAD that we own the
// address.
send_neighbor_advertisement(
core_ctx,
bindings_ctx,
&device_id,
false,
target_address,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.into_specified(),
);
}
IpAddressState::Tentative => {
// Nothing further to do in response to DAD
// messages.
}
IpAddressState::Unavailable => {
// Nothing further to do for unassigned target
// addresses.
}
}
return;
}
Ipv6SourceAddr::Unicast(src_ip) => {
// Neighbor is performing link address resolution.
match core_ctx
.address_status_for_device(target_address.into_specified(), device_id)
{
AddressStatus::Present(Ipv6PresentAddressStatus::UnicastAssigned) => {}
AddressStatus::Present(
Ipv6PresentAddressStatus::UnicastTentative
| Ipv6PresentAddressStatus::Multicast,
)
| AddressStatus::Unassigned => {
// Address is not considered assigned to us as a
// unicast so don't send a neighbor advertisement
// reply.
return;
}
}
let link_addr = p.body().iter().find_map(|o| match o {
NdpOption::SourceLinkLayerAddress(a) => Some(a),
NdpOption::TargetLinkLayerAddress(_)
| NdpOption::PrefixInformation(_)
| NdpOption::RedirectedHeader { .. }
| NdpOption::RecursiveDnsServer(_)
| NdpOption::RouteInformation(_)
| NdpOption::Mtu(_) => None,
});
if let Some(link_addr) = link_addr {
NudIpHandler::handle_neighbor_probe(
core_ctx,
bindings_ctx,
&device_id,
src_ip.into_specified(),
link_addr,
);
}
send_neighbor_advertisement(
core_ctx,
bindings_ctx,
&device_id,
true,
target_address,
src_ip.into_specified(),
);
}
}
}
NdpPacket::NeighborAdvertisement(ref p) => {
// TODO(https://fxbug.dev/42179526): Invalidate discovered routers when
// neighbor entry's IsRouter field transitions to false.
let target_address = p.message().target_address();
let src_ip = match src_ip {
Ipv6SourceAddr::Unicast(src_ip) => src_ip,
Ipv6SourceAddr::Unspecified => {
trace!("dropping NA with unspecified source and target = {:?}", target_address);
return;
}
};
let target_address = match UnicastAddr::new(*target_address) {
Some(a) => a,
None => {
trace!(
"dropping NA from {} with non-unicast target={:?}",
src_ip,
target_address
);
return;
}
};
core_ctx.increment(|counters| &counters.rx.neighbor_advertisement);
match Ipv6DeviceHandler::remove_duplicate_tentative_address(
core_ctx,
bindings_ctx,
&device_id,
target_address,
) {
IpAddressState::Assigned => {
// A neighbor is advertising that it owns an address
// that we also have assigned. This is out of scope
// for DAD.
//
// As per RFC 4862 section 5.4.4,
//
// 2. If the target address matches a unicast address
// assigned to the receiving interface, it would
// possibly indicate that the address is a
// duplicate but it has not been detected by the
// Duplicate Address Detection procedure (recall
// that Duplicate Address Detection is not
// completely reliable). How to handle such a case
// is beyond the scope of this document.
//
// TODO(https://fxbug.dev/42111744): Signal to bindings
// that a duplicate address is detected.
error!(
"NA from {src_ip} with target address {target_address} that is also \
assigned on device {device_id:?}",
);
}
IpAddressState::Tentative => {
// Nothing further to do for an NA from a neighbor that
// targets an address we also have assigned.
return;
}
IpAddressState::Unavailable => {
// Address not targeting us so we know its for a neighbor.
//
// TODO(https://fxbug.dev/42182317): Move NUD to IP.
}
}
let link_addr = p.body().iter().find_map(|o| match o {
NdpOption::TargetLinkLayerAddress(a) => Some(a),
NdpOption::SourceLinkLayerAddress(_)
| NdpOption::PrefixInformation(_)
| NdpOption::RedirectedHeader { .. }
| NdpOption::RecursiveDnsServer(_)
| NdpOption::RouteInformation(_)
| NdpOption::Mtu(_) => None,
});
let link_addr = match link_addr {
Some(a) => a,
None => {
trace!(
"dropping NA from {} targetting {} with no TLL option",
src_ip,
target_address
);
return;
}
};
NudIpHandler::handle_neighbor_confirmation(
core_ctx,
bindings_ctx,
&device_id,
target_address.into_specified(),
link_addr,
ConfirmationFlags {
solicited_flag: p.message().solicited_flag(),
override_flag: p.message().override_flag(),
},
);
}
NdpPacket::RouterAdvertisement(ref p) => {
// As per RFC 4861 section 6.1.2,
//
// A node MUST silently discard any received Router Advertisement
// messages that do not satisfy all of the following validity
// checks:
//
// - IP Source Address is a link-local address. Routers must
// use their link-local address as the source for Router
// Advertisement and Redirect messages so that hosts can
// uniquely identify routers.
//
// ...
let src_ip = match src_ip {
Ipv6SourceAddr::Unicast(ip) => match LinkLocalUnicastAddr::new(*ip) {
Some(ip) => ip,
None => return,
},
Ipv6SourceAddr::Unspecified => return,
};
let ra = p.message();
debug!("received router advertisement from {:?}: {:?}", src_ip, ra);
core_ctx.increment(|counters| &counters.rx.router_advertisement);
// As per RFC 4861 section 6.3.4,
// The RetransTimer variable SHOULD be copied from the Retrans
// Timer field, if it is specified.
//
// TODO(https://fxbug.dev/42052173): Control whether or not we should
// update the retransmit timer.
if let Some(retransmit_timer) = ra.retransmit_timer() {
Ipv6DeviceHandler::set_discovered_retrans_timer(
core_ctx,
bindings_ctx,
&device_id,
retransmit_timer,
);
}
// As per RFC 4861 section 6.3.4:
// If the received Cur Hop Limit value is specified, the host
// SHOULD set its CurHopLimit variable to the received value.
//
// TODO(https://fxbug.dev/42052173): Control whether or not we should
// update the default hop limit.
if let Some(hop_limit) = ra.current_hop_limit() {
trace!("receive_ndp_packet: NDP RA: updating device's hop limit to {:?} for router: {:?}", ra.current_hop_limit(), src_ip);
IpDeviceHandler::set_default_hop_limit(core_ctx, &device_id, hop_limit);
}
// TODO(https://fxbug.dev/42077316): Support default router preference.
Ipv6DeviceHandler::update_discovered_ipv6_route(
core_ctx,
bindings_ctx,
&device_id,
Ipv6DiscoveredRoute { subnet: IPV6_DEFAULT_SUBNET, gateway: Some(src_ip) },
p.message().router_lifetime().map(NonZeroNdpLifetime::Finite),
);
for option in p.body().iter() {
match option {
NdpOption::TargetLinkLayerAddress(_)
| NdpOption::RedirectedHeader { .. }
| NdpOption::RecursiveDnsServer(_) => {}
NdpOption::SourceLinkLayerAddress(addr) => {
debug!("processing SourceLinkLayerAddress option in RA: {:?}", addr);
// As per RFC 4861 section 6.3.4,
//
// If the advertisement contains a Source Link-Layer
// Address option, the link-layer address SHOULD be
// recorded in the Neighbor Cache entry for the router
// (creating an entry if necessary) and the IsRouter
// flag in the Neighbor Cache entry MUST be set to
// TRUE. If no Source Link-Layer Address is included,
// but a corresponding Neighbor Cache entry exists,
// its IsRouter flag MUST be set to TRUE. The IsRouter
// flag is used by Neighbor Unreachability Detection
// to determine when a router changes to being a host
// (i.e., no longer capable of forwarding packets).
// If a Neighbor Cache entry is created for the
// router, its reachability state MUST be set to STALE
// as specified in Section 7.3.3. If a cache entry
// already exists and is updated with a different
// link-layer address, the reachability state MUST
// also be set to STALE.if a Neighbor Cache entry
//
// We do not yet support NUD as described in RFC 4861
// so for now we just record the link-layer address in
// our neighbor table.
//
// TODO(https://fxbug.dev/42083367): Add support for routers in NUD.
NudIpHandler::handle_neighbor_probe(
core_ctx,
bindings_ctx,
&device_id,
{
let src_ip: UnicastAddr<_> = src_ip.into_addr();
src_ip.into_specified()
},
addr,
);
}
NdpOption::PrefixInformation(prefix_info) => {
debug!("processing Prefix Information option in RA: {:?}", prefix_info);
// As per RFC 4861 section 6.3.4,
//
// For each Prefix Information option with the on-link
// flag set, a host does the following:
//
// - If the prefix is the link-local prefix,
// silently ignore the Prefix Information option.
//
// Also as per RFC 4862 section 5.5.3,
//
// For each Prefix-Information option in the Router
// Advertisement:
//
// ..
//
// b) If the prefix is the link-local prefix,
// silently ignore the Prefix Information option.
if prefix_info.prefix().is_link_local() {
continue;
}
let subnet = match prefix_info.subnet() {
Ok(subnet) => subnet,
Err(err) => match err {
SubnetError::PrefixTooLong | SubnetError::HostBitsSet => continue,
},
};
match UnicastAddr::new(subnet.network()) {
Some(UnicastAddr { .. }) => {}
None => continue,
}
let valid_lifetime = prefix_info.valid_lifetime();
if prefix_info.on_link_flag() {
// TODO(https://fxbug.dev/42077316): Support route preference.
Ipv6DeviceHandler::update_discovered_ipv6_route(
core_ctx,
bindings_ctx,
&device_id,
Ipv6DiscoveredRoute { subnet, gateway: None },
valid_lifetime,
)
}
if prefix_info.autonomous_address_configuration_flag() {
Ipv6DeviceHandler::apply_slaac_update(
core_ctx,
bindings_ctx,
&device_id,
subnet,
prefix_info.preferred_lifetime(),
valid_lifetime,
);
}
}
NdpOption::RouteInformation(rio) => {
debug!("processing Route Information option in RA: {:?}", rio);
// TODO(https://fxbug.dev/42077316): Support route preference.
Ipv6DeviceHandler::update_discovered_ipv6_route(
core_ctx,
bindings_ctx,
&device_id,
Ipv6DiscoveredRoute {
subnet: rio.prefix().clone(),
gateway: Some(src_ip),
},
rio.route_lifetime(),
)
}
NdpOption::Mtu(mtu) => {
debug!("processing MTU option in RA: {:?}", mtu);
// TODO(https://fxbug.dev/42052173): Control whether or
// not we should update the link's MTU in response to
// RAs.
Ipv6DeviceHandler::set_link_mtu(core_ctx, &device_id, Mtu::new(mtu));
}
}
}
}
}
}
impl<
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC>
+ InnerIcmpContext<Ipv6, BC>
+ Ipv6DeviceHandler<BC>
+ IpDeviceHandler<Ipv6, BC>
+ IpDeviceStateContext<Ipv6, BC>
+ PmtuHandler<Ipv6, BC>
+ NudIpHandler<Ipv6, BC>
+ IpLayerHandler<Ipv6, BC>
+ CounterContext<IcmpRxCounters<Ipv6>>
+ CounterContext<IcmpTxCounters<Ipv6>>
+ CounterContext<NdpCounters>,
> IpTransportContext<Ipv6, BC, CC> for IcmpIpTransportContext
{
fn receive_icmp_error(
core_ctx: &mut CC,
_bindings_ctx: &mut BC,
_device: &CC::DeviceId,
original_src_ip: Option<SpecifiedAddr<Ipv6Addr>>,
original_dst_ip: SpecifiedAddr<Ipv6Addr>,
original_body: &[u8],
err: Icmpv6ErrorCode,
) {
receive_ip_transport_icmp_error(
core_ctx,
original_src_ip,
original_dst_ip,
original_body,
err,
)
}
fn receive_ip_packet<B: BufferMut>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
src_ip: Ipv6SourceAddr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
mut buffer: B,
) -> Result<(), (B, TransportReceiveError)> {
trace!(
"<IcmpIpTransportContext as IpTransportContext<Ipv6>>::receive_ip_packet({:?}, {})",
src_ip,
dst_ip
);
let packet = match buffer
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), dst_ip))
{
Ok(packet) => packet,
Err(_) => return Ok(()), // TODO(joshlf): Do something else here?
};
match packet {
Icmpv6Packet::EchoRequest(echo_request) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv6>| &counters.echo_request);
if let Some(src_ip) = SocketIpAddr::new_from_ipv6_source(src_ip) {
match SocketIpAddr::try_from(dst_ip) {
Ok(dst_ip) => {
let req = *echo_request.message();
let code = echo_request.code();
let (local_ip, remote_ip) = (dst_ip, src_ip);
// TODO(joshlf): Do something if send_icmp_reply returns an
// error?
let _ = send_icmp_reply(
core_ctx,
bindings_ctx,
Some(device),
remote_ip,
local_ip,
|src_ip| {
buffer.encapsulate(IcmpPacketBuilder::<Ipv6, _>::new(
src_ip,
remote_ip.addr(),
code,
req.reply(),
))
},
);
}
Err(AddrIsMappedError {}) => {
trace!("IpTransportContext<Ipv6>::receive_ip_packet: Received echo request with an ipv4-mapped-ipv6 destination address");
}
}
} else {
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv6>>::receive_ip_packet: Received echo request with an unspecified source address");
}
}
Icmpv6Packet::EchoReply(echo_reply) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv6>| &counters.echo_reply);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv6>>::receive_ip_packet: Received an EchoReply message");
// We don't allow creating echo sockets connected to the
// unspecified address, so it's OK to bail early here if the
// source address is unspecified.
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
let id = echo_reply.message().id();
let meta = echo_reply.parse_metadata();
buffer.undo_parse(meta);
let device = device.downgrade();
receive_icmp_echo_reply(
core_ctx,
bindings_ctx,
src_ip.get(),
dst_ip,
id,
buffer,
device,
);
}
}
Icmpv6Packet::Ndp(packet) => {
receive_ndp_packet(core_ctx, bindings_ctx, device, src_ip, packet)
}
Icmpv6Packet::PacketTooBig(packet_too_big) => {
core_ctx.increment(|counters: &IcmpRxCounters<Ipv6>| &counters.packet_too_big);
trace!("<IcmpIpTransportContext as IpTransportContext<Ipv6>>::receive_ip_packet: Received a Packet Too Big message");
if let Ipv6SourceAddr::Unicast(src_ip) = src_ip {
// We are updating the path MTU from the destination address
// of this `packet` (which is an IP address on this node) to
// some remote (identified by the source address of this
// `packet`).
//
// `update_pmtu_if_less` may return an error, but it will
// only happen if the Packet Too Big message's MTU field had
// a value that was less than the IPv6 minimum MTU (which as
// per IPv6 RFC 8200, must not happen).
core_ctx.update_pmtu_if_less(
bindings_ctx,
dst_ip.get(),
src_ip.get(),
Mtu::new(packet_too_big.message().mtu()),
);
}
receive_icmpv6_error(
core_ctx,
bindings_ctx,
device,
&packet_too_big,
Icmpv6ErrorCode::PacketTooBig,
);
}
Icmpv6Packet::Mld(packet) => {
core_ctx.receive_mld_packet(bindings_ctx, &device, src_ip, dst_ip, packet);
}
Icmpv6Packet::DestUnreachable(dest_unreachable) => receive_icmpv6_error(
core_ctx,
bindings_ctx,
device,
&dest_unreachable,
Icmpv6ErrorCode::DestUnreachable(dest_unreachable.code()),
),
Icmpv6Packet::TimeExceeded(time_exceeded) => receive_icmpv6_error(
core_ctx,
bindings_ctx,
device,
&time_exceeded,
Icmpv6ErrorCode::TimeExceeded(time_exceeded.code()),
),
Icmpv6Packet::ParameterProblem(parameter_problem) => receive_icmpv6_error(
core_ctx,
bindings_ctx,
device,
&parameter_problem,
Icmpv6ErrorCode::ParameterProblem(parameter_problem.code()),
),
}
Ok(())
}
}
/// Sends an ICMP reply to a remote host.
///
/// `send_icmp_reply` sends a reply to a non-error message (e.g., "echo request"
/// or "timestamp request" messages). It takes the ingress device, source IP,
/// and destination IP of the packet *being responded to*. It uses ICMP-specific
/// logic to figure out whether and how to send an ICMP reply.
///
/// `get_body_from_src_ip` returns a `Serializer` with the bytes of the ICMP
/// packet, and, when called, is given the source IP address chosen for the
/// outbound packet. This allows `get_body_from_src_ip` to properly compute the
/// ICMP checksum, which relies on both the source and destination IP addresses
/// of the IP packet it's encapsulated in.
fn send_icmp_reply<I, BC, CC, S, F>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
original_src_ip: SocketIpAddr<I::Addr>,
original_dst_ip: SocketIpAddr<I::Addr>,
get_body_from_src_ip: F,
) where
I: crate::ip::IpExt,
CC: IpSocketHandler<I, BC> + DeviceIdContext<AnyDevice> + CounterContext<IcmpTxCounters<I>>,
S: TransportPacketSerializer,
S::Buffer: BufferMut,
F: FnOnce(SpecifiedAddr<I::Addr>) -> S,
{
trace!("send_icmp_reply({:?}, {}, {})", device, original_src_ip, original_dst_ip);
core_ctx.increment(|counters| &counters.reply);
core_ctx
.send_oneshot_ip_packet(
bindings_ctx,
None,
Some(original_dst_ip),
original_src_ip,
I::ICMP_IP_PROTO,
&DefaultSendOptions,
|src_ip| get_body_from_src_ip(src_ip.into()),
None,
)
.unwrap_or_else(|err| {
debug!("failed to send ICMP reply: {}", err);
})
}
/// Receive an ICMP(v4) error message.
///
/// `receive_icmpv4_error` handles an incoming ICMP error message by parsing the
/// original IPv4 packet and then delegating to the context.
fn receive_icmpv4_error<
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC>,
B: ByteSlice,
M: IcmpMessage<Ipv4, Body<B> = OriginalPacket<B>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
packet: &IcmpPacket<Ipv4, B, M>,
err: Icmpv4ErrorCode,
) {
packet.with_original_packet(|res| match res {
Ok(original_packet) => {
let dst_ip = match SpecifiedAddr::new(original_packet.dst_ip()) {
Some(ip) => ip,
None => {
trace!("receive_icmpv4_error: Got ICMP error message whose original IPv4 packet contains an unspecified destination address; discarding");
return;
},
};
InnerIcmpContext::receive_icmp_error(
core_ctx,
bindings_ctx,
device,
SpecifiedAddr::new(original_packet.src_ip()),
dst_ip,
original_packet.proto(),
original_packet.body().into_inner(),
err,
);
}
Err(_) => debug!(
"receive_icmpv4_error: Got ICMP error message with unparsable original IPv4 packet"
),
})
}
/// Receive an ICMPv6 error message.
///
/// `receive_icmpv6_error` handles an incoming ICMPv6 error message by parsing
/// the original IPv6 packet and then delegating to the context.
fn receive_icmpv6_error<
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC>,
B: ByteSlice,
M: IcmpMessage<Ipv6, Body<B> = OriginalPacket<B>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
packet: &IcmpPacket<Ipv6, B, M>,
err: Icmpv6ErrorCode,
) {
packet.with_original_packet(|res| match res {
Ok(original_packet) => {
let dst_ip = match SpecifiedAddr::new(original_packet.dst_ip()) {
Some(ip)=>ip,
None => {
trace!("receive_icmpv6_error: Got ICMP error message whose original IPv6 packet contains an unspecified destination address; discarding");
return;
},
};
match original_packet.body_proto() {
Ok((body, proto)) => {
InnerIcmpContext::receive_icmp_error(
core_ctx,
bindings_ctx,
device,
SpecifiedAddr::new(original_packet.src_ip()),
dst_ip,
proto,
body.into_inner(),
err,
);
}
Err(ExtHdrParseError) => {
trace!("receive_icmpv6_error: We could not parse the original packet's extension headers, and so we don't know where the original packet's body begins; discarding");
// There's nothing we can do in this case, so we just
// return.
return;
}
}
}
Err(_body) => debug!(
"receive_icmpv6_error: Got ICMPv6 error message with unparsable original IPv6 packet"
),
})
}
/// Send an ICMP(v4) message in response to receiving a packet destined for an
/// unreachable address.
///
/// `send_icmpv4_host_unreachable` sends the appropriate ICMP message in
/// response to receiving an IP packet from `src_ip` to `dst_ip`, where
/// `dst_ip` is unreachable. In particular, this is an ICMP
/// "destination unreachable" message with a "host unreachable" code.
///
/// `original_packet` must be an initial fragment or a complete IP
/// packet, per [RFC 792 Introduction]:
///
/// Also ICMP messages are only sent about errors in handling fragment zero of
/// fragemented [sic] datagrams.
///
/// `header_len` is the length of the header including all options.
///
/// [RFC 792 Introduction]: https://datatracker.ietf.org/doc/html/rfc792
pub(crate) fn send_icmpv4_host_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
core_ctx.with_counters(|counters| {
counters.address_unreachable.increment();
});
send_icmpv4_dest_unreachable(
core_ctx,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
Icmpv4DestUnreachableCode::DestHostUnreachable,
original_packet,
header_len,
fragment_type,
);
}
/// Send an ICMPv6 message in response to receiving a packet destined for an
/// unreachable address.
///
/// `send_icmpv6_address_unreachable` sends the appropriate ICMP message in
/// response to receiving an IP packet from `src_ip` to `dst_ip`, where
/// `dst_ip` is unreachable. In particular, this is an ICMP
/// "destination unreachable" message with an "address unreachable" code.
///
/// `original_packet` contains the contents of the entire original packet,
/// including extension headers.
pub(crate) fn send_icmpv6_address_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
original_packet: B,
) {
core_ctx.with_counters(|counters| {
counters.address_unreachable.increment();
});
send_icmpv6_dest_unreachable(
core_ctx,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
Icmpv6DestUnreachableCode::AddrUnreachable,
original_packet,
);
}
/// Send an ICMP(v4) message in response to receiving a packet destined for an
/// unsupported IPv4 protocol.
///
/// `send_icmpv4_protocol_unreachable` sends the appropriate ICMP message in
/// response to receiving an IP packet from `src_ip` to `dst_ip` identifying an
/// unsupported protocol - in particular, a "destination unreachable" message
/// with a "protocol unreachable" code.
///
/// `original_packet` contains the contents of the entire original packet,
/// including the IP header. This must be a whole packet, not a packet fragment.
/// `header_len` is the length of the header including all options.
pub(crate) fn send_icmpv4_protocol_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.protocol_unreachable);
send_icmpv4_dest_unreachable(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv4DestUnreachableCode::DestProtocolUnreachable,
original_packet,
header_len,
// If we are sending a protocol unreachable error it is correct to assume that, if the
// packet was initially fragmented, it has been successfully reassembled by now. It
// guarantees that we won't send more than one ICMP Destination Unreachable message for
// different fragments of the same original packet, so we should behave as if we are
// handling an initial fragment.
Ipv4FragmentType::InitialFragment,
);
}
/// Send an ICMPv6 message in response to receiving a packet destined for an
/// unsupported Next Header.
///
/// `send_icmpv6_protocol_unreachable` is like
/// [`send_icmpv4_protocol_unreachable`], but for ICMPv6. It sends an ICMPv6
/// "parameter problem" message with an "unrecognized next header type" code.
///
/// `header_len` is the length of all IPv6 headers (including extension headers)
/// *before* the payload with the problematic Next Header type.
pub(crate) fn send_icmpv6_protocol_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.protocol_unreachable);
send_icmpv6_parameter_problem(
core_ctx,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
// Per RFC 4443, the pointer refers to the first byte of the packet
// whose Next Header field was unrecognized. It is measured as an offset
// from the beginning of the first IPv6 header. E.g., a pointer of 40
// (the length of a single IPv6 header) would indicate that the Next
// Header field from that header - and hence of the first encapsulated
// packet - was unrecognized.
//
// NOTE: Since header_len is a usize, this could theoretically be a
// lossy conversion. However, all that means in practice is that, if a
// remote host somehow managed to get us to process a frame with a 4GB
// IP header and send an ICMP response, the pointer value would be
// wrong. It's not worth wasting special logic to avoid generating a
// malformed packet in a case that will almost certainly never happen.
Icmpv6ParameterProblem::new(header_len as u32),
original_packet,
false,
);
}
/// Send an ICMP(v4) message in response to receiving a packet destined for an
/// unreachable local transport-layer port.
///
/// `send_icmpv4_port_unreachable` sends the appropriate ICMP message in
/// response to receiving an IP packet from `src_ip` to `dst_ip` with an
/// unreachable local transport-layer port. In particular, this is an ICMP
/// "destination unreachable" message with a "port unreachable" code.
///
/// `original_packet` contains the contents of the entire original packet,
/// including the IP header. This must be a whole packet, not a packet fragment.
/// `header_len` is the length of the header including all options.
pub(crate) fn send_icmpv4_port_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.port_unreachable);
send_icmpv4_dest_unreachable(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv4DestUnreachableCode::DestPortUnreachable,
original_packet,
header_len,
// If we are sending a port unreachable error it is correct to assume that, if the packet
// was initially fragmented, it has been successfully reassembled by now. It guarantees that
// we won't send more than one ICMP Destination Unreachable message for different fragments
// of the same original packet, so we should behave as if we are handling an initial
// fragment.
Ipv4FragmentType::InitialFragment,
);
}
/// Send an ICMPv6 message in response to receiving a packet destined for an
/// unreachable local transport-layer port.
///
/// `send_icmpv6_port_unreachable` is like [`send_icmpv4_port_unreachable`], but
/// for ICMPv6.
///
/// `original_packet` contains the contents of the entire original packet,
/// including extension headers.
pub(crate) fn send_icmpv6_port_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
original_packet: B,
) {
core_ctx.increment(|counters| &counters.port_unreachable);
send_icmpv6_dest_unreachable(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv6DestUnreachableCode::PortUnreachable,
original_packet,
);
}
/// Send an ICMP(v4) message in response to receiving a packet destined for an
/// unreachable network.
///
/// `send_icmpv4_net_unreachable` sends the appropriate ICMP message in response
/// to receiving an IP packet from `src_ip` to an unreachable `dst_ip`. In
/// particular, this is an ICMP "destination unreachable" message with a "net
/// unreachable" code.
///
/// `original_packet` contains the contents of the entire original packet -
/// including all IP headers. `header_len` is the length of the IPv4 header. It
/// is ignored for IPv6.
pub(crate) fn send_icmpv4_net_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
proto: Ipv4Proto,
original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
core_ctx.increment(|counters| &counters.net_unreachable);
// Check whether we MUST NOT send an ICMP error message
// because the original packet was itself an ICMP error message.
if is_icmp_error_message::<Ipv4>(proto, &original_packet.as_ref()[header_len..]) {
return;
}
send_icmpv4_dest_unreachable(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
original_packet,
header_len,
fragment_type,
);
}
/// Send an ICMPv6 message in response to receiving a packet destined for an
/// unreachable network.
///
/// `send_icmpv6_net_unreachable` is like [`send_icmpv4_net_unreachable`], but
/// for ICMPv6. It sends an ICMPv6 "destination unreachable" message with a "no
/// route to destination" code.
///
/// `original_packet` contains the contents of the entire original packet
/// including extension headers. `header_len` is the length of the IP header and
/// all extension headers.
pub(crate) fn send_icmpv6_net_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
proto: Ipv6Proto,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.net_unreachable);
// Check whether we MUST NOT send an ICMP error message
// because the original packet was itself an ICMP error message.
if is_icmp_error_message::<Ipv6>(proto, &original_packet.as_ref()[header_len..]) {
return;
}
send_icmpv6_dest_unreachable(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv6DestUnreachableCode::NoRoute,
original_packet,
);
}
/// Send an ICMP(v4) message in response to receiving a packet whose TTL has
/// expired.
///
/// `send_icmpv4_ttl_expired` sends the appropriate ICMP in response to
/// receiving an IP packet from `src_ip` to `dst_ip` whose TTL has expired. In
/// particular, this is an ICMP "time exceeded" message with a "time to live
/// exceeded in transit" code.
///
/// `original_packet` contains the contents of the entire original packet,
/// including the header. `header_len` is the length of the IP header including
/// options.
pub(crate) fn send_icmpv4_ttl_expired<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
proto: Ipv4Proto,
original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
core_ctx.increment(|counters| &counters.ttl_expired);
// Check whether we MUST NOT send an ICMP error message because the original
// packet was itself an ICMP error message.
if is_icmp_error_message::<Ipv4>(proto, &original_packet.as_ref()[header_len..]) {
return;
}
send_icmpv4_error_message(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv4TimeExceededCode::TtlExpired,
IcmpTimeExceeded::default(),
original_packet,
header_len,
fragment_type,
)
}
/// Send an ICMPv6 message in response to receiving a packet whose hop limit has
/// expired.
///
/// `send_icmpv6_ttl_expired` is like [`send_icmpv4_ttl_expired`], but for
/// ICMPv6. It sends an ICMPv6 "time exceeded" message with a "hop limit
/// exceeded in transit" code.
///
/// `original_packet` contains the contents of the entire original packet
/// including extension headers. `header_len` is the length of the IP header and
/// all extension headers.
pub(crate) fn send_icmpv6_ttl_expired<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
proto: Ipv6Proto,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.ttl_expired);
// Check whether we MUST NOT send an ICMP error message because the
// original packet was itself an ICMP error message.
if is_icmp_error_message::<Ipv6>(proto, &original_packet.as_ref()[header_len..]) {
return;
}
send_icmpv6_error_message(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
Icmpv6TimeExceededCode::HopLimitExceeded,
IcmpTimeExceeded::default(),
original_packet,
false, /* allow_dst_multicast */
)
}
// TODO(joshlf): Test send_icmpv6_packet_too_big once we support fake IPv6 test
// setups.
/// Send an ICMPv6 message in response to receiving a packet whose size exceeds
/// the MTU of the next hop interface.
///
/// `send_icmpv6_packet_too_big` sends an ICMPv6 "packet too big" message in
/// response to receiving an IP packet from `src_ip` to `dst_ip` whose size
/// exceeds the `mtu` of the next hop interface.
pub(crate) fn send_icmpv6_packet_too_big<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
proto: Ipv6Proto,
mtu: Mtu,
original_packet: B,
header_len: usize,
) {
core_ctx.increment(|counters| &counters.packet_too_big);
// Check whether we MUST NOT send an ICMP error message because the
// original packet was itself an ICMP error message.
if is_icmp_error_message::<Ipv6>(proto, &original_packet.as_ref()[header_len..]) {
return;
}
send_icmpv6_error_message(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
IcmpUnusedCode,
Icmpv6PacketTooBig::new(mtu.into()),
original_packet,
// As per RFC 4443 section 2.4.e,
//
// An ICMPv6 error message MUST NOT be originated as a result of
// receiving the following:
//
// (e.3) A packet destined to an IPv6 multicast address. (There are
// two exceptions to this rule: (1) the Packet Too Big Message
// (Section 3.2) to allow Path MTU discovery to work for IPv6
// multicast, and (2) the Parameter Problem Message, Code 2
// (Section 3.4) reporting an unrecognized IPv6 option (see
// Section 4.2 of [IPv6]) that has the Option Type highest-
// order two bits set to 10).
//
// (e.4) A packet sent as a link-layer multicast (the exceptions
// from e.3 apply to this case, too).
//
// Thus, we explicitly allow sending a Packet Too Big error if the
// destination was a multicast packet.
true, /* allow_dst_multicast */
)
}
pub(crate) fn send_icmpv4_parameter_problem<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
code: Icmpv4ParameterProblemCode,
parameter_problem: Icmpv4ParameterProblem,
original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
core_ctx.increment(|counters| &counters.parameter_problem);
send_icmpv4_error_message(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
code,
parameter_problem,
original_packet,
header_len,
fragment_type,
)
}
/// Send an ICMPv6 Parameter Problem error message.
///
/// If the error message is Code 2 reporting an unrecognized IPv6 option that
/// has the Option Type highest-order two bits set to 10, `allow_dst_multicast`
/// must be set to `true`. See [`should_send_icmpv6_error`] for more details.
///
/// # Panics
///
/// Panics if `allow_multicast_addr` is set to `true`, but this Parameter
/// Problem's code is not 2 (Unrecognized IPv6 Option).
pub(crate) fn send_icmpv6_parameter_problem<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: &CC::DeviceId,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
code: Icmpv6ParameterProblemCode,
parameter_problem: Icmpv6ParameterProblem,
original_packet: B,
allow_dst_multicast: bool,
) {
// Only allow the `allow_dst_multicast` parameter to be set if the code is
// the unrecognized IPv6 option as that is one of the few exceptions where
// we can send an ICMP packet in response to a packet that was destined for
// a multicast address.
assert!(!allow_dst_multicast || code == Icmpv6ParameterProblemCode::UnrecognizedIpv6Option);
core_ctx.increment(|counters| &counters.parameter_problem);
send_icmpv6_error_message(
core_ctx,
bindings_ctx,
Some(device),
frame_dst,
src_ip,
dst_ip,
code,
parameter_problem,
original_packet,
allow_dst_multicast,
)
}
fn send_icmpv4_dest_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv4Addr>,
dst_ip: SocketIpAddr<Ipv4Addr>,
code: Icmpv4DestUnreachableCode,
original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
core_ctx.increment(|counters| &counters.dest_unreachable);
send_icmpv4_error_message(
core_ctx,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
code,
IcmpDestUnreachable::default(),
original_packet,
header_len,
fragment_type,
)
}
fn send_icmpv6_dest_unreachable<
B: BufferMut,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
src_ip: SocketIpAddr<Ipv6Addr>,
dst_ip: SocketIpAddr<Ipv6Addr>,
code: Icmpv6DestUnreachableCode,
original_packet: B,
) {
send_icmpv6_error_message(
core_ctx,
bindings_ctx,
device,
frame_dst,
src_ip,
dst_ip,
code,
IcmpDestUnreachable::default(),
original_packet,
false, /* allow_dst_multicast */
)
}
fn send_icmpv4_error_message<
B: BufferMut,
M: IcmpMessage<Ipv4> + MaybeTransportPacket,
BC: IcmpBindingsContext<Ipv4, CC::DeviceId>,
CC: InnerIcmpv4Context<BC> + CounterContext<IcmpTxCounters<Ipv4>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
original_src_ip: SocketIpAddr<Ipv4Addr>,
original_dst_ip: SocketIpAddr<Ipv4Addr>,
code: M::Code,
message: M,
mut original_packet: B,
header_len: usize,
fragment_type: Ipv4FragmentType,
) {
// TODO(https://fxbug.dev/42177876): Come up with rules for when to send ICMP
// error messages.
if !should_send_icmpv4_error(
frame_dst,
original_src_ip.into(),
original_dst_ip.into(),
fragment_type,
) {
return;
}
// Per RFC 792, body contains entire IPv4 header + 64 bytes of original
// body.
original_packet.shrink_back_to(header_len + 64);
// TODO(https://fxbug.dev/42177877): Improve source address selection for ICMP
// errors sent from unnumbered/router interfaces.
let _ = try_send_error!(
core_ctx,
bindings_ctx,
core_ctx.send_oneshot_ip_packet(
bindings_ctx,
device.map(EitherDeviceId::Strong),
None,
original_src_ip,
Ipv4Proto::Icmp,
&DefaultSendOptions,
|local_ip| {
original_packet.encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
local_ip.addr(),
original_src_ip.addr(),
code,
message,
))
},
None
)
);
}
fn send_icmpv6_error_message<
B: BufferMut,
M: IcmpMessage<Ipv6> + MaybeTransportPacket,
BC: IcmpBindingsContext<Ipv6, CC::DeviceId>,
CC: InnerIcmpv6Context<BC> + CounterContext<IcmpTxCounters<Ipv6>>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device: Option<&CC::DeviceId>,
frame_dst: Option<FrameDestination>,
original_src_ip: SocketIpAddr<Ipv6Addr>,
original_dst_ip: SocketIpAddr<Ipv6Addr>,
code: M::Code,
message: M,
original_packet: B,
allow_dst_multicast: bool,
) {
// TODO(https://fxbug.dev/42177876): Come up with rules for when to send ICMP
// error messages.
if !should_send_icmpv6_error(
frame_dst,
original_src_ip.into(),
original_dst_ip.into(),
allow_dst_multicast,
) {
return;
}
// TODO(https://fxbug.dev/42177877): Improve source address selection for ICMP
// errors sent from unnumbered/router interfaces.
let _ = try_send_error!(
core_ctx,
bindings_ctx,
core_ctx.send_oneshot_ip_packet(
bindings_ctx,
device.map(EitherDeviceId::Strong),
None,
original_src_ip,
Ipv6Proto::Icmpv6,
&DefaultSendOptions,
|local_ip| {
let icmp_builder = IcmpPacketBuilder::<Ipv6, _>::new(
local_ip.addr(),
original_src_ip.addr(),
code,
message,
);
// Per RFC 4443, body contains as much of the original body as
// possible without exceeding IPv6 minimum MTU.
TruncatingSerializer::new(original_packet, TruncateDirection::DiscardBack)
.encapsulate(icmp_builder)
},
Some(Ipv6::MINIMUM_LINK_MTU.get()),
)
);
}
/// Should we send an ICMP(v4) response?
///
/// `should_send_icmpv4_error` implements the logic described in RFC 1122
/// Section 3.2.2. It decides whether, upon receiving an incoming packet with
/// the given parameters, we should send an ICMP response or not. In particular,
/// we do not send an ICMP response if we've received:
/// - a packet destined to a broadcast or multicast address
/// - a packet sent in a link-layer broadcast
/// - a non-initial fragment
/// - a packet whose source address does not define a single host (a
/// zero/unspecified address, a loopback address, a broadcast address, a
/// multicast address, or a Class E address)
///
/// Note that `should_send_icmpv4_error` does NOT check whether the incoming
/// packet contained an ICMP error message. This is because that check is
/// unnecessary for some ICMP error conditions. The ICMP error message check can
/// be performed separately with `is_icmp_error_message`.
fn should_send_icmpv4_error(
frame_dst: Option<FrameDestination>,
src_ip: SpecifiedAddr<Ipv4Addr>,
dst_ip: SpecifiedAddr<Ipv4Addr>,
fragment_type: Ipv4FragmentType,
) -> bool {
// NOTE: We do not explicitly implement the "unspecified address" check, as
// it is enforced by the types of the arguments.
// TODO(joshlf): Implement the rest of the rules:
// - a packet destined to a subnet broadcast address
// - a packet whose source address is a subnet broadcast address
// NOTE: The FrameDestination type has variants for unicast, multicast, and
// broadcast. One implication of the fact that we only check for broadcast
// here (in compliance with the RFC) is that we could, in one very unlikely
// edge case, respond with an ICMP error message to an IP packet which was
// sent in a link-layer multicast frame. In particular, that can happen if
// we subscribe to a multicast IP group and, as a result, subscribe to the
// corresponding multicast MAC address, and we receive a unicast IP packet
// in a multicast link-layer frame destined to that MAC address.
//
// TODO(joshlf): Should we filter incoming multicast IP traffic to make sure
// that it matches the multicast MAC address of the frame it was
// encapsulated in?
fragment_type == Ipv4FragmentType::InitialFragment
&& !(dst_ip.is_multicast()
|| dst_ip.is_limited_broadcast()
|| frame_dst.is_some_and(|dst| dst.is_broadcast())
|| src_ip.is_loopback()
|| src_ip.is_limited_broadcast()
|| src_ip.is_multicast()
|| src_ip.is_class_e())
}
/// Should we send an ICMPv6 response?
///
/// `should_send_icmpv6_error` implements the logic described in RFC 4443
/// Section 2.4.e. It decides whether, upon receiving an incoming packet with
/// the given parameters, we should send an ICMP response or not. In particular,
/// we do not send an ICMP response if we've received:
/// - a packet destined to a multicast address
/// - Two exceptions to this rules:
/// 1) the Packet Too Big Message to allow Path MTU discovery to work for
/// IPv6 multicast
/// 2) the Parameter Problem Message, Code 2 reporting an unrecognized IPv6
/// option that has the Option Type highest-order two bits set to 10
/// - a packet sent as a link-layer multicast or broadcast
/// - same exceptions apply here as well.
/// - a packet whose source address does not define a single host (a
/// zero/unspecified address, a loopback address, or a multicast address)
///
/// If an ICMP response will be a Packet Too Big Message or a Parameter Problem
/// Message, Code 2 reporting an unrecognized IPv6 option that has the Option
/// Type highest-order two bits set to 10, `info.allow_dst_multicast` must be
/// set to `true` so this function will allow the exception mentioned above.
///
/// Note that `should_send_icmpv6_error` does NOT check whether the incoming
/// packet contained an ICMP error message. This is because that check is
/// unnecessary for some ICMP error conditions. The ICMP error message check can
/// be performed separately with `is_icmp_error_message`.
fn should_send_icmpv6_error(
frame_dst: Option<FrameDestination>,
src_ip: SpecifiedAddr<Ipv6Addr>,
dst_ip: SpecifiedAddr<Ipv6Addr>,
allow_dst_multicast: bool,
) -> bool {
// NOTE: We do not explicitly implement the "unspecified address" check, as
// it is enforced by the types of the arguments.
let multicast_frame_dst = match frame_dst {
Some(FrameDestination::Individual { local: _ }) | None => false,
Some(FrameDestination::Broadcast) | Some(FrameDestination::Multicast) => true,
};
if (dst_ip.is_multicast() || multicast_frame_dst) && !allow_dst_multicast {
return false;
}
if src_ip.is_loopback() || src_ip.is_multicast() {
return false;
}
true
}
/// Determine whether or not an IP packet body contains an ICMP error message
/// for the purposes of determining whether or not to send an ICMP response.
///
/// `is_icmp_error_message` checks whether `proto` is ICMP(v4) for IPv4 or
/// ICMPv6 for IPv6 and, if so, attempts to parse `buf` as an ICMP packet in
/// order to determine whether it is an error message or not. If parsing fails,
/// it conservatively assumes that it is an error packet in order to avoid
/// violating the MUST NOT directives of RFC 1122 Section 3.2.2 and [RFC 4443
/// Section 2.4.e].
///
/// [RFC 4443 Section 2.4.e]: https://tools.ietf.org/html/rfc4443#section-2.4
fn is_icmp_error_message<I: IcmpIpExt>(proto: I::Proto, buf: &[u8]) -> bool {
proto == I::ICMP_IP_PROTO
&& peek_message_type::<I::IcmpMessageType>(buf).map(IcmpMessageType::is_err).unwrap_or(true)
}
/// Common logic for receiving an ICMP echo reply.
fn receive_icmp_echo_reply<
I: socket::IpExt,
B: BufferMut,
BC: IcmpBindingsContext<I, CC::DeviceId>,
CC: InnerIcmpContext<I, BC>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
src_ip: I::Addr,
dst_ip: SpecifiedAddr<I::Addr>,
id: u16,
body: B,
device: CC::WeakDeviceId,
) {
let src_ip = match SpecifiedAddr::new(src_ip) {
Some(src_ip) => src_ip,
None => {
trace!("receive_icmp_echo_reply: unspecified source address");
return;
}
};
let src_ip: SocketIpAddr<_> = match src_ip.try_into() {
Ok(src_ip) => src_ip,
Err(AddrIsMappedError {}) => {
trace!("receive_icmp_echo_reply: mapped source address");
return;
}
};
let dst_ip: SocketIpAddr<_> = match dst_ip.try_into() {
Ok(dst_ip) => dst_ip,
Err(AddrIsMappedError {}) => {
trace!("receive_icmp_echo_reply: mapped destination address");
return;
}
};
core_ctx.with_icmp_ctx_and_sockets_mut(|_core_ctx, sockets| {
if let Some((id, strong_device)) = NonZeroU16::new(id).zip(device.upgrade()) {
let mut addrs_to_search = AddrVecIter::<I, CC::WeakDeviceId, IcmpAddrSpec>::with_device(
ConnIpAddr { local: (dst_ip, id), remote: (src_ip, ()) }.into(),
device,
);
let socket = match addrs_to_search.try_for_each(|addr_vec| {
match addr_vec {
AddrVec::Conn(c) => {
if let Some(id) = sockets.socket_map.conns().get_by_addr(&c) {
return ControlFlow::Break(id);
}
}
AddrVec::Listen(l) => {
if let Some(id) = sockets.socket_map.listeners().get_by_addr(&l) {
return ControlFlow::Break(id);
}
}
}
ControlFlow::Continue(())
}) {
ControlFlow::Continue(()) => None,
ControlFlow::Break(id) => Some(id),
};
if let Some(socket) = socket {
trace!("receive_icmp_echo_reply: Received echo reply for local socket");
bindings_ctx.receive_icmp_echo_reply(
socket,
&strong_device,
src_ip.addr(),
dst_ip.addr(),
id.get(),
body,
);
return;
}
}
// TODO(https://fxbug.dev/42124755): Neither the ICMPv4 or ICMPv6 RFCs
// explicitly state what to do in case we receive an "unsolicited"
// echo reply. We only expose the replies if we have a registered
// connection for the IcmpAddr of the incoming reply for now. Given
// that a reply should only be sent in response to a request, an
// ICMP unreachable-type message is probably not appropriate for
// unsolicited replies. However, it's also possible that we sent a
// request and then closed the socket before receiving the reply, so
// this doesn't necessarily indicate a buggy or malicious remote
// host. We should figure this out definitively.
//
// If we do decide to send an ICMP error message, the appropriate
// thing to do is probably to have this function return a `Result`,
// and then have the top-level implementation of
// `IpTransportContext::receive_ip_packet` return the
// appropriate error.
trace!("receive_icmp_echo_reply: Received echo reply with no local socket");
})
}
#[cfg(test)]
mod tests {
use alloc::{vec, vec::Vec};
use assert_matches::assert_matches;
use core::{
ops::{Deref, DerefMut},
time::Duration,
};
use net_types::{ip::Subnet, ZonedAddr};
use packet::Buf;
use packet_formats::{
ethernet::EthernetFrameLengthCheck,
icmp::{mld::MldPacket, Icmpv4TimestampRequest},
ip::{IpPacketBuilder, IpProto},
testutil::parse_icmp_packet_in_ip_packet_in_ethernet_frame,
udp::UdpPacketBuilder,
utils::NonZeroDuration,
};
use super::*;
use crate::{
context::testutil::{
FakeBindingsCtx, FakeCoreCtx, FakeCtxWithCoreCtx, FakeInstant, Wrapped,
},
device::{
testutil::{set_forwarding_enabled, FakeDeviceId, FakeWeakDeviceId},
DeviceId,
},
ip::{
device::state::IpDeviceStateIpExt,
icmp::socket::{
IcmpEchoSocketApi, IcmpSocketId, IcmpSocketSet, IcmpSocketState, StateContext,
},
socket::{
testutil::{FakeDeviceConfig, FakeDualStackIpSocketCtx},
IpSock, IpSockCreationError, IpSockSendError, SendOptions,
},
testutil::DualStackSendIpPacketMeta,
types::IpTypesIpExt,
},
state::StackStateBuilder,
testutil::{Ctx, TestIpExt, FAKE_CONFIG_V4, FAKE_CONFIG_V6},
transport::udp::UdpStateBuilder,
uninstantiable::UninstantiableWrapper,
};
/// The FakeCoreCtx held as the inner state of the [`WrappedFakeCoreCtx`] that
/// is [`FakeCoreCtx`].
type FakeBufferCoreCtx = FakeCoreCtx<
FakeDualStackIpSocketCtx<FakeDeviceId>,
DualStackSendIpPacketMeta<FakeDeviceId>,
FakeDeviceId,
>;
/// `FakeCoreCtx` specialized for ICMP.
type FakeIcmpCoreCtx<I> =
Wrapped<FakeIcmpCoreCtxState<I, FakeWeakDeviceId<FakeDeviceId>>, FakeBufferCoreCtx>;
/// `FakeBindingsCtx` specialized for ICMP.
type FakeIcmpBindingsCtx<I> = FakeBindingsCtx<(), (), FakeIcmpBindingsCtxState<I>, ()>;
/// A fake ICMP bindings and core contexts.
///
/// This is exposed to super so it can be shared with the socket tests.
pub(super) type FakeIcmpCtx<I> =
FakeCtxWithCoreCtx<FakeIcmpCoreCtx<I>, (), (), FakeIcmpBindingsCtxState<I>>;
pub(super) struct FakeIcmpCoreCtxState<I: socket::IpExt, D: device::WeakId> {
bound_socket_map_and_allocator: BoundSockets<I, D, FakeIcmpBindingsCtx<I>>,
socket_set: IcmpSocketSet<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>,
error_send_bucket: TokenBucket<FakeInstant>,
receive_icmp_error: Vec<I::ErrorCode>,
rx_counters: IcmpRxCounters<I>,
tx_counters: IcmpTxCounters<I>,
ndp_counters: NdpCounters,
}
impl<I: socket::IpExt, D: device::WeakId> FakeIcmpCoreCtxState<I, D> {
fn with_errors_per_second(errors_per_second: u64) -> Self {
Self {
socket_set: Default::default(),
bound_socket_map_and_allocator: Default::default(),
error_send_bucket: TokenBucket::new(errors_per_second),
receive_icmp_error: Default::default(),
rx_counters: Default::default(),
tx_counters: Default::default(),
ndp_counters: Default::default(),
}
}
}
impl<I: TestIpExt + socket::IpExt> Default for FakeIcmpCoreCtx<I> {
fn default() -> Self {
Wrapped::with_inner_and_outer_state(
FakeDualStackIpSocketCtx::new(core::iter::once(FakeDeviceConfig {
device: FakeDeviceId,
local_ips: vec![I::FAKE_CONFIG.local_ip],
remote_ips: vec![I::FAKE_CONFIG.remote_ip],
})),
FakeIcmpCoreCtxState::with_errors_per_second(DEFAULT_ERRORS_PER_SECOND),
)
}
}
impl<I: datagram::IpExt> IcmpStateContext for FakeIcmpCoreCtx<I> {}
impl IcmpStateContext for FakeBufferCoreCtx {}
impl<I: socket::IpExt> CounterContext<IcmpRxCounters<I>> for FakeIcmpCoreCtx<I> {
fn with_counters<O, F: FnOnce(&IcmpRxCounters<I>) -> O>(&self, cb: F) -> O {
cb(&self.outer.rx_counters)
}
}
impl<I: socket::IpExt> CounterContext<IcmpTxCounters<I>> for FakeIcmpCoreCtx<I> {
fn with_counters<O, F: FnOnce(&IcmpTxCounters<I>) -> O>(&self, cb: F) -> O {
cb(&self.outer.tx_counters)
}
}
impl<I: socket::IpExt> CounterContext<NdpCounters> for FakeIcmpCoreCtx<I> {
fn with_counters<O, F: FnOnce(&NdpCounters) -> O>(&self, cb: F) -> O {
cb(&self.outer.ndp_counters)
}
}
impl<I: datagram::IpExt + IpDeviceStateIpExt> InnerIcmpContext<I, FakeIcmpBindingsCtx<I>>
for FakeIcmpCoreCtx<I>
{
type DualStackContext = UninstantiableWrapper<Self>;
type IpSocketsCtx<'a> = FakeBufferCoreCtx;
fn receive_icmp_error(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<I>,
_device: &Self::DeviceId,
original_src_ip: Option<SpecifiedAddr<I::Addr>>,
original_dst_ip: SpecifiedAddr<I::Addr>,
original_proto: I::Proto,
original_body: &[u8],
err: I::ErrorCode,
) {
self.increment(|counters: &IcmpRxCounters<I>| &counters.error);
self.outer.receive_icmp_error.push(err);
if original_proto == I::ICMP_IP_PROTO {
receive_ip_transport_icmp_error(
self,
original_src_ip,
original_dst_ip,
original_body,
err,
)
}
}
fn with_icmp_ctx_and_sockets_mut<
O,
F: FnOnce(
&mut Self::IpSocketsCtx<'_>,
&mut BoundSockets<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
) -> O,
>(
&mut self,
cb: F,
) -> O {
let Self { outer, inner } = self;
cb(inner, &mut outer.bound_socket_map_and_allocator)
}
fn with_error_send_bucket_mut<O, F: FnOnce(&mut TokenBucket<FakeInstant>) -> O>(
&mut self,
cb: F,
) -> O {
let Self { outer, inner: _ } = self;
cb(&mut outer.error_send_bucket)
}
}
/// Utilities for accessing locked internal state in tests.
impl<I: socket::IpExt, D: device::WeakId, BT: IcmpEchoBindingsTypes> IcmpSocketId<I, D, BT> {
fn get(&self) -> impl Deref<Target = IcmpSocketState<I, D, BT>> + '_ {
self.state_for_locking().read()
}
fn get_mut(&self) -> impl DerefMut<Target = IcmpSocketState<I, D, BT>> + '_ {
self.state_for_locking().write()
}
}
impl<I: datagram::IpExt> StateContext<I, FakeIcmpBindingsCtx<I>> for FakeIcmpCoreCtx<I> {
type SocketStateCtx<'a> = FakeIcmpCoreCtx<I>;
fn with_all_sockets_mut<
O,
F: FnOnce(&mut IcmpSocketSet<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>) -> O,
>(
&mut self,
cb: F,
) -> O {
cb(&mut self.outer.socket_set)
}
fn with_all_sockets<
O,
F: FnOnce(&IcmpSocketSet<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>) -> O,
>(
&mut self,
cb: F,
) -> O {
cb(&self.outer.socket_set)
}
fn with_socket_state<
O,
F: FnOnce(
&mut Self::SocketStateCtx<'_>,
&IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
) -> O,
>(
&mut self,
id: &IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
cb: F,
) -> O {
cb(self, &id.get())
}
fn with_socket_state_mut<
O,
F: FnOnce(
&mut Self::SocketStateCtx<'_>,
&mut IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
) -> O,
>(
&mut self,
id: &IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
cb: F,
) -> O {
cb(self, &mut id.get_mut())
}
fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> O>(
&mut self,
cb: F,
) -> O {
cb(self)
}
fn for_each_socket<
F: FnMut(
&mut Self::SocketStateCtx<'_>,
&IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
&IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
),
>(
&mut self,
mut cb: F,
) {
let socks = self
.outer
.socket_set
.keys()
.map(|id| IcmpSocketId::from(id.clone()))
.collect::<Vec<_>>();
for id in socks {
cb(self, &id, &id.get());
}
}
}
impl<I: socket::IpExt> IcmpEchoBindingsContext<I, FakeDeviceId> for FakeIcmpBindingsCtx<I> {
fn receive_icmp_echo_reply<B: BufferMut>(
&mut self,
_conn: &IcmpSocketId<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>,
_device_id: &FakeDeviceId,
_src_ip: I::Addr,
_dst_ip: I::Addr,
_id: u16,
_data: B,
) {
unimplemented!()
}
}
impl<I: socket::IpExt> IcmpEchoBindingsTypes for FakeIcmpBindingsCtx<I> {
type ExternalData<II: Ip> = ();
}
// Tests that require an entire IP stack.
/// Test that receiving a particular IP packet results in a particular ICMP
/// response.
///
/// Test that receiving an IP packet from remote host
/// `I::FAKE_CONFIG.remote_ip` to host `dst_ip` with `ttl` and `proto`
/// results in all of the counters in `assert_counters` being triggered at
/// least once.
///
/// If `expect_message_code` is `Some`, expect that exactly one ICMP packet
/// was sent in response with the given message and code, and invoke the
/// given function `f` on the packet. Otherwise, if it is `None`, expect
/// that no response was sent.
///
/// `modify_packet_builder` is invoked on the `PacketBuilder` before the
/// packet is serialized.
///
/// `modify_stack_state_builder` is invoked on the `StackStateBuilder`
/// before it is used to build the context.
///
/// The state is initialized to `I::FAKE_CONFIG` when testing.
#[allow(clippy::too_many_arguments)]
#[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)]
fn test_receive_ip_packet<
I: TestIpExt + crate::IpExt,
C: PartialEq + Debug,
M: IcmpMessage<I, Code = C> + PartialEq + Debug,
PBF: FnOnce(&mut <I as packet_formats::ip::IpExt>::PacketBuilder),
SSBF: FnOnce(&mut StackStateBuilder),
F: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>),
>(
modify_packet_builder: PBF,
modify_stack_state_builder: SSBF,
body: &mut [u8],
dst_ip: SpecifiedAddr<I::Addr>,
ttl: u8,
proto: I::Proto,
assert_counters: &[&str],
expect_message_code: Option<(M, C)>,
f: F,
) {
crate::testutil::set_logger_for_test();
let mut pb = <I as packet_formats::ip::IpExt>::PacketBuilder::new(
*I::FAKE_CONFIG.remote_ip,
dst_ip.get(),
ttl,
proto,
);
modify_packet_builder(&mut pb);
let buffer = Buf::new(body, ..).encapsulate(pb).serialize_vec_outer().unwrap();
let (mut ctx, device_ids) =
I::FAKE_CONFIG.into_builder().build_with_modifications(modify_stack_state_builder);
let device: DeviceId<_> = device_ids[0].clone().into();
set_forwarding_enabled::<_, I>(&mut ctx, &device, true);
ctx.test_api().receive_ip_packet::<I, _>(
&device,
Some(FrameDestination::Individual { local: true }),
buffer,
);
let Ctx { core_ctx, bindings_ctx } = &mut ctx;
for counter in assert_counters {
// TODO(https://fxbug.dev/42084333): Redesign iterating through
// assert_counters once CounterContext is removed.
let count = match *counter {
"send_ipv4_packet" => core_ctx.ipv4.inner.counters().send_ip_packet.get(),
"send_ipv6_packet" => core_ctx.ipv6.inner.counters().send_ip_packet.get(),
"echo_request" => core_ctx.inner_icmp_state::<I>().rx_counters.echo_request.get(),
"timestamp_request" => {
core_ctx.inner_icmp_state::<I>().rx_counters.timestamp_request.get()
}
"protocol_unreachable" => {
core_ctx.inner_icmp_state::<I>().tx_counters.protocol_unreachable.get()
}
"port_unreachable" => {
core_ctx.inner_icmp_state::<I>().tx_counters.port_unreachable.get()
}
"net_unreachable" => {
core_ctx.inner_icmp_state::<I>().tx_counters.net_unreachable.get()
}
"ttl_expired" => core_ctx.inner_icmp_state::<I>().tx_counters.ttl_expired.get(),
"packet_too_big" => {
core_ctx.inner_icmp_state::<I>().tx_counters.packet_too_big.get()
}
"parameter_problem" => {
core_ctx.inner_icmp_state::<I>().tx_counters.parameter_problem.get()
}
"dest_unreachable" => {
core_ctx.inner_icmp_state::<I>().tx_counters.dest_unreachable.get()
}
"error" => core_ctx.inner_icmp_state::<I>().tx_counters.error.get(),
c => panic!("unrecognized counter: {c}"),
};
assert!(count > 0, "counter at zero: {counter}");
}
if let Some((expect_message, expect_code)) = expect_message_code {
let frames = bindings_ctx.take_ethernet_frames();
let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame);
assert_eq!(frames.len(), 1);
let (src_mac, dst_mac, src_ip, dst_ip, _, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<I, _, M, _>(
&frame,
EthernetFrameLengthCheck::NoCheck,
f,
)
.unwrap();
assert_eq!(src_mac, I::FAKE_CONFIG.local_mac.get());
assert_eq!(dst_mac, I::FAKE_CONFIG.remote_mac.get());
assert_eq!(src_ip, I::FAKE_CONFIG.local_ip.get());
assert_eq!(dst_ip, I::FAKE_CONFIG.remote_ip.get());
assert_eq!(message, expect_message);
assert_eq!(code, expect_code);
} else {
assert_matches!(bindings_ctx.take_ethernet_frames()[..], []);
}
}
#[test]
fn test_receive_echo() {
crate::testutil::set_logger_for_test();
// Test that, when receiving an echo request, we respond with an echo
// reply with the appropriate parameters.
#[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)]
fn test<I: TestIpExt + crate::IpExt>(assert_counters: &[&str]) {
let req = IcmpEchoRequest::new(0, 0);
let req_body = &[1, 2, 3, 4];
let mut buffer = Buf::new(req_body.to_vec(), ..)
.encapsulate(IcmpPacketBuilder::<I, _>::new(
I::FAKE_CONFIG.remote_ip.get(),
I::FAKE_CONFIG.local_ip.get(),
IcmpUnusedCode,
req,
))
.serialize_vec_outer()
.unwrap();
test_receive_ip_packet::<I, _, _, _, _, _>(
|_| {},
|_| {},
buffer.as_mut(),
I::FAKE_CONFIG.local_ip,
64,
I::ICMP_IP_PROTO,
assert_counters,
Some((req.reply(), IcmpUnusedCode)),
|packet| assert_eq!(packet.original_packet().bytes(), req_body),
);
}
test::<Ipv4>(&["echo_request", "send_ipv4_packet"]);
test::<Ipv6>(&["echo_request", "send_ipv6_packet"]);
}
#[test]
fn test_receive_timestamp() {
crate::testutil::set_logger_for_test();
let req = Icmpv4TimestampRequest::new(1, 2, 3);
let mut buffer = Buf::new(Vec::new(), ..)
.encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
FAKE_CONFIG_V4.remote_ip,
FAKE_CONFIG_V4.local_ip,
IcmpUnusedCode,
req,
))
.serialize_vec_outer()
.unwrap();
test_receive_ip_packet::<Ipv4, _, _, _, _, _>(
|_| {},
|builder| {
let _: &mut Icmpv4StateBuilder =
builder.ipv4_builder().icmpv4_builder().send_timestamp_reply(true);
},
buffer.as_mut(),
FAKE_CONFIG_V4.local_ip,
64,
Ipv4Proto::Icmp,
&["timestamp_request", "send_ipv4_packet"],
Some((req.reply(0x80000000, 0x80000000), IcmpUnusedCode)),
|_| {},
);
}
#[test]
fn test_protocol_unreachable() {
// Test receiving an IP packet for an unreachable protocol. Check to
// make sure that we respond with the appropriate ICMP message.
//
// Currently, for IPv4, we test for all unreachable protocols, while for
// IPv6, we only test for IGMP and TCP. See the comment below for why
// that limitation exists. Once the limitation is fixed, we should test
// with all unreachable protocols for both versions.
for proto in 0u8..=255 {
let v4proto = Ipv4Proto::from(proto);
match v4proto {
Ipv4Proto::Other(_) => {
test_receive_ip_packet::<Ipv4, _, _, _, _, _>(
|_| {},
|_| {},
&mut [0u8; 128],
FAKE_CONFIG_V4.local_ip,
64,
v4proto,
&["protocol_unreachable"],
Some((
IcmpDestUnreachable::default(),
Icmpv4DestUnreachableCode::DestProtocolUnreachable,
)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
}
Ipv4Proto::Icmp
| Ipv4Proto::Igmp
| Ipv4Proto::Proto(IpProto::Udp)
| Ipv4Proto::Proto(IpProto::Tcp) => {}
}
// TODO(https://fxbug.dev/42124756): We seem to fail to parse an IPv6 packet if
// its Next Header value is unrecognized (rather than treating this
// as a valid parsing but then replying with a parameter problem
// error message). We should a) fix this and, b) expand this test to
// ensure we don't regress.
let v6proto = Ipv6Proto::from(proto);
match v6proto {
Ipv6Proto::Icmpv6
| Ipv6Proto::NoNextHeader
| Ipv6Proto::Proto(IpProto::Udp)
| Ipv6Proto::Proto(IpProto::Tcp)
| Ipv6Proto::Other(_) => {}
}
}
}
#[test]
fn test_port_unreachable() {
// TODO(joshlf): Test TCP as well.
// Receive an IP packet for an unreachable UDP port (1234). Check to
// make sure that we respond with the appropriate ICMP message. Then, do
// the same for a stack which has the UDP `send_port_unreachable` option
// disable, and make sure that we DON'T respond with an ICMP message.
#[netstack3_macros::context_ip_bounds(I, crate::testutil::FakeBindingsCtx, crate)]
fn test<I: TestIpExt + crate::IpExt, C: PartialEq + Debug>(
code: C,
assert_counters: &[&str],
original_packet_len: usize,
) where
IcmpDestUnreachable:
for<'a> IcmpMessage<I, Code = C, Body<&'a [u8]> = OriginalPacket<&'a [u8]>>,
{
let mut buffer = Buf::new(vec![0; 128], ..)
.encapsulate(UdpPacketBuilder::new(
I::FAKE_CONFIG.remote_ip.get(),
I::FAKE_CONFIG.local_ip.get(),
None,
NonZeroU16::new(1234).unwrap(),
))
.serialize_vec_outer()
.unwrap();
test_receive_ip_packet::<I, _, _, _, _, _>(
|_| {},
// Enable the `send_port_unreachable` feature.
|builder| {
let _: &mut UdpStateBuilder =
builder.transport_builder().udp_builder().send_port_unreachable(true);
},
buffer.as_mut(),
I::FAKE_CONFIG.local_ip,
64,
IpProto::Udp.into(),
assert_counters,
Some((IcmpDestUnreachable::default(), code)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), original_packet_len),
);
test_receive_ip_packet::<I, C, IcmpDestUnreachable, _, _, _>(
|_| {},
// Leave the `send_port_unreachable` feature disabled.
|_: &mut StackStateBuilder| {},
buffer.as_mut(),
I::FAKE_CONFIG.local_ip,
64,
IpProto::Udp.into(),
&[],
None,
|_| {},
);
}
test::<Ipv4, _>(Icmpv4DestUnreachableCode::DestPortUnreachable, &["port_unreachable"], 84);
test::<Ipv6, _>(Icmpv6DestUnreachableCode::PortUnreachable, &["port_unreachable"], 176);
}
#[test]
fn test_net_unreachable() {
// Receive an IP packet for an unreachable destination address. Check to
// make sure that we respond with the appropriate ICMP message.
test_receive_ip_packet::<Ipv4, _, _, _, _, _>(
|_| {},
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
SpecifiedAddr::new(Ipv4Addr::new([1, 2, 3, 4])).unwrap(),
64,
IpProto::Udp.into(),
&["net_unreachable"],
Some((
IcmpDestUnreachable::default(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
test_receive_ip_packet::<Ipv6, _, _, _, _, _>(
|_| {},
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
SpecifiedAddr::new(Ipv6Addr::from_bytes([
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
]))
.unwrap(),
64,
IpProto::Udp.into(),
&["net_unreachable"],
Some((IcmpDestUnreachable::default(), Icmpv6DestUnreachableCode::NoRoute)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), 168),
);
// Same test for IPv4 but with a non-initial fragment. No ICMP error
// should be sent.
test_receive_ip_packet::<Ipv4, _, IcmpDestUnreachable, _, _, _>(
|pb| pb.fragment_offset(64),
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
SpecifiedAddr::new(Ipv4Addr::new([1, 2, 3, 4])).unwrap(),
64,
IpProto::Udp.into(),
&[],
None,
|_| {},
);
}
#[test]
fn test_ttl_expired() {
// Receive an IP packet with an expired TTL. Check to make sure that we
// respond with the appropriate ICMP message.
test_receive_ip_packet::<Ipv4, _, _, _, _, _>(
|_| {},
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
FAKE_CONFIG_V4.remote_ip,
1,
IpProto::Udp.into(),
&["ttl_expired"],
Some((IcmpTimeExceeded::default(), Icmpv4TimeExceededCode::TtlExpired)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
test_receive_ip_packet::<Ipv6, _, _, _, _, _>(
|_| {},
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
FAKE_CONFIG_V6.remote_ip,
1,
IpProto::Udp.into(),
&["ttl_expired"],
Some((IcmpTimeExceeded::default(), Icmpv6TimeExceededCode::HopLimitExceeded)),
// Ensure packet is truncated to the right length.
|packet| assert_eq!(packet.original_packet().bytes().len(), 168),
);
// Same test for IPv4 but with a non-initial fragment. No ICMP error
// should be sent.
test_receive_ip_packet::<Ipv4, _, IcmpTimeExceeded, _, _, _>(
|pb| pb.fragment_offset(64),
|_: &mut StackStateBuilder| {},
&mut [0u8; 128],
SpecifiedAddr::new(Ipv4Addr::new([1, 2, 3, 4])).unwrap(),
64,
IpProto::Udp.into(),
&[],
None,
|_| {},
);
}
#[test]
fn test_should_send_icmpv4_error() {
let src_ip = FAKE_CONFIG_V4.local_ip;
let dst_ip = FAKE_CONFIG_V4.remote_ip;
let frame_dst = FrameDestination::Individual { local: true };
let multicast_ip_1 = SpecifiedAddr::new(Ipv4Addr::new([224, 0, 0, 1])).unwrap();
let multicast_ip_2 = SpecifiedAddr::new(Ipv4Addr::new([224, 0, 0, 2])).unwrap();
// Should Send, unless non initial fragment.
assert!(should_send_icmpv4_error(
Some(frame_dst),
src_ip,
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(should_send_icmpv4_error(None, src_ip, dst_ip, Ipv4FragmentType::InitialFragment));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
src_ip,
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because destined for IP broadcast addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
src_ip,
Ipv4::LIMITED_BROADCAST_ADDRESS,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
src_ip,
Ipv4::LIMITED_BROADCAST_ADDRESS,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because destined for multicast addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
src_ip,
multicast_ip_1,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
src_ip,
multicast_ip_1,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because Link Layer Broadcast.
assert!(!should_send_icmpv4_error(
Some(FrameDestination::Broadcast),
src_ip,
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(FrameDestination::Broadcast),
src_ip,
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because from loopback addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
Ipv4::LOOPBACK_ADDRESS,
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
Ipv4::LOOPBACK_ADDRESS,
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because from limited broadcast addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
Ipv4::LIMITED_BROADCAST_ADDRESS,
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
Ipv4::LIMITED_BROADCAST_ADDRESS,
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because from multicast addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
multicast_ip_2,
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
multicast_ip_2,
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
// Should not send because from class E addr
assert!(!should_send_icmpv4_error(
Some(frame_dst),
SpecifiedAddr::new(Ipv4Addr::new([240, 0, 0, 1])).unwrap(),
dst_ip,
Ipv4FragmentType::InitialFragment
));
assert!(!should_send_icmpv4_error(
Some(frame_dst),
SpecifiedAddr::new(Ipv4Addr::new([240, 0, 0, 1])).unwrap(),
dst_ip,
Ipv4FragmentType::NonInitialFragment
));
}
#[test]
fn test_should_send_icmpv6_error() {
let src_ip = FAKE_CONFIG_V6.local_ip;
let dst_ip = FAKE_CONFIG_V6.remote_ip;
let frame_dst = FrameDestination::Individual { local: true };
let multicast_ip_1 =
SpecifiedAddr::new(Ipv6Addr::new([0xff00, 0, 0, 0, 0, 0, 0, 1])).unwrap();
let multicast_ip_2 =
SpecifiedAddr::new(Ipv6Addr::new([0xff00, 0, 0, 0, 0, 0, 0, 2])).unwrap();
// Should Send.
assert!(should_send_icmpv6_error(
Some(frame_dst),
src_ip,
dst_ip,
false /* allow_dst_multicast */
));
assert!(should_send_icmpv6_error(None, src_ip, dst_ip, false /* allow_dst_multicast */));
assert!(should_send_icmpv6_error(
Some(frame_dst),
src_ip,
dst_ip,
true /* allow_dst_multicast */
));
// Should not send because destined for multicast addr, unless exception
// applies.
assert!(!should_send_icmpv6_error(
Some(frame_dst),
src_ip,
multicast_ip_1,
false /* allow_dst_multicast */
));
assert!(should_send_icmpv6_error(
Some(frame_dst),
src_ip,
multicast_ip_1,
true /* allow_dst_multicast */
));
// Should not send because Link Layer Broadcast, unless exception
// applies.
assert!(!should_send_icmpv6_error(
Some(FrameDestination::Broadcast),
src_ip,
dst_ip,
false /* allow_dst_multicast */
));
assert!(should_send_icmpv6_error(
Some(FrameDestination::Broadcast),
src_ip,
dst_ip,
true /* allow_dst_multicast */
));
// Should not send because from loopback addr.
assert!(!should_send_icmpv6_error(
Some(frame_dst),
Ipv6::LOOPBACK_ADDRESS,
dst_ip,
false /* allow_dst_multicast */
));
assert!(!should_send_icmpv6_error(
Some(frame_dst),
Ipv6::LOOPBACK_ADDRESS,
dst_ip,
true /* allow_dst_multicast */
));
// Should not send because from multicast addr.
assert!(!should_send_icmpv6_error(
Some(frame_dst),
multicast_ip_2,
dst_ip,
false /* allow_dst_multicast */
));
assert!(!should_send_icmpv6_error(
Some(frame_dst),
multicast_ip_2,
dst_ip,
true /* allow_dst_multicast */
));
// Should not send because from multicast addr, even though dest
// multicast exception applies.
assert!(!should_send_icmpv6_error(
Some(FrameDestination::Broadcast),
multicast_ip_2,
dst_ip,
false /* allow_dst_multicast */
));
assert!(!should_send_icmpv6_error(
Some(FrameDestination::Broadcast),
multicast_ip_2,
dst_ip,
true /* allow_dst_multicast */
));
assert!(!should_send_icmpv6_error(
Some(frame_dst),
multicast_ip_2,
multicast_ip_1,
false /* allow_dst_multicast */
));
assert!(!should_send_icmpv6_error(
Some(frame_dst),
multicast_ip_2,
multicast_ip_1,
true /* allow_dst_multicast */
));
}
// Tests that only require an ICMP stack. Unlike the preceding tests, these
// only test the ICMP stack and state, and fake everything else. We define
// the `FakeIcmpv4Ctx` and `FakeIcmpv6Ctx` types, which we wrap in a
// `FakeCtx` to provide automatic implementations of a number of required
// traits. The rest we implement manually.
#[derive(Default)]
pub(super) struct FakeIcmpBindingsCtxState<I: socket::IpExt> {
_marker: core::marker::PhantomData<I>,
}
impl InnerIcmpv4Context<FakeIcmpBindingsCtx<Ipv4>> for FakeIcmpCoreCtx<Ipv4> {
fn should_send_timestamp_reply(&self) -> bool {
false
}
}
impl_pmtu_handler!(FakeIcmpCoreCtx<Ipv4>, FakeIcmpBindingsCtx<Ipv4>, Ipv4);
impl_pmtu_handler!(FakeIcmpCoreCtx<Ipv6>, FakeIcmpBindingsCtx<Ipv6>, Ipv6);
impl<I: datagram::IpExt> crate::ip::socket::IpSocketHandler<I, FakeIcmpBindingsCtx<I>>
for FakeIcmpCoreCtx<I>
{
fn new_ip_socket(
&mut self,
bindings_ctx: &mut FakeIcmpBindingsCtx<I>,
device: Option<EitherDeviceId<&Self::DeviceId, &Self::WeakDeviceId>>,
local_ip: Option<SocketIpAddr<I::Addr>>,
remote_ip: SocketIpAddr<I::Addr>,
proto: I::Proto,
) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError> {
self.inner.new_ip_socket(bindings_ctx, device, local_ip, remote_ip, proto)
}
fn send_ip_packet<S, O>(
&mut self,
bindings_ctx: &mut FakeIcmpBindingsCtx<I>,
socket: &IpSock<I, Self::WeakDeviceId>,
body: S,
mtu: Option<u32>,
options: &O,
) -> Result<(), (S, IpSockSendError)>
where
S: TransportPacketSerializer,
S::Buffer: BufferMut,
O: SendOptions<I, Self::WeakDeviceId>,
{
self.inner.send_ip_packet(bindings_ctx, socket, body, mtu, options)
}
}
impl IpDeviceHandler<Ipv6, FakeIcmpBindingsCtx<Ipv6>> for FakeIcmpCoreCtx<Ipv6> {
fn is_router_device(&mut self, _device_id: &Self::DeviceId) -> bool {
unimplemented!()
}
fn set_default_hop_limit(&mut self, _device_id: &Self::DeviceId, _hop_limit: NonZeroU8) {
unreachable!()
}
}
impl IpDeviceStateContext<Ipv6, FakeIcmpBindingsCtx<Ipv6>> for FakeIcmpCoreCtx<Ipv6> {
fn with_next_packet_id<O, F: FnOnce(&()) -> O>(&self, cb: F) -> O {
cb(&())
}
fn get_local_addr_for_remote(
&mut self,
_device_id: &Self::DeviceId,
_remote: Option<SpecifiedAddr<Ipv6Addr>>,
) -> Option<SocketIpAddr<Ipv6Addr>> {
unimplemented!()
}
fn get_hop_limit(&mut self, _device_id: &Self::DeviceId) -> NonZeroU8 {
unimplemented!()
}
fn address_status_for_device(
&mut self,
_addr: SpecifiedAddr<Ipv6Addr>,
_device_id: &Self::DeviceId,
) -> AddressStatus<Ipv6PresentAddressStatus> {
unimplemented!()
}
}
impl Ipv6DeviceHandler<FakeIcmpBindingsCtx<Ipv6>> for FakeIcmpCoreCtx<Ipv6> {
type LinkLayerAddr = [u8; 0];
fn get_link_layer_addr_bytes(&mut self, _device_id: &Self::DeviceId) -> Option<[u8; 0]> {
unimplemented!()
}
fn set_discovered_retrans_timer(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_retrans_timer: NonZeroDuration,
) {
unimplemented!()
}
fn remove_duplicate_tentative_address(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_addr: UnicastAddr<Ipv6Addr>,
) -> IpAddressState {
unimplemented!()
}
fn set_link_mtu(&mut self, _device_id: &Self::DeviceId, _mtu: Mtu) {
unimplemented!()
}
fn update_discovered_ipv6_route(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_route: Ipv6DiscoveredRoute,
_lifetime: Option<NonZeroNdpLifetime>,
) {
unimplemented!()
}
fn apply_slaac_update(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_subnet: Subnet<Ipv6Addr>,
_preferred_lifetime: Option<NonZeroNdpLifetime>,
_valid_lifetime: Option<NonZeroNdpLifetime>,
) {
unimplemented!()
}
fn receive_mld_packet<B: ByteSlice>(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device: &FakeDeviceId,
_src_ip: Ipv6SourceAddr,
_dst_ip: SpecifiedAddr<Ipv6Addr>,
_packet: MldPacket<B>,
) {
unimplemented!()
}
}
impl IpLayerHandler<Ipv6, FakeIcmpBindingsCtx<Ipv6>> for FakeIcmpCoreCtx<Ipv6> {
fn send_ip_packet_from_device<S>(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_meta: SendIpPacketMeta<Ipv6, &Self::DeviceId, Option<SpecifiedAddr<Ipv6Addr>>>,
_body: S,
) -> Result<(), S> {
unimplemented!()
}
fn send_ip_frame<S>(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device: &Self::DeviceId,
_next_hop: SpecifiedAddr<<Ipv6 as Ip>::Addr>,
_body: S,
_broadcast: Option<<Ipv6 as IpTypesIpExt>::BroadcastMarker>,
) -> Result<(), S>
where
S: Serializer,
S::Buffer: BufferMut,
{
unimplemented!()
}
}
impl NudIpHandler<Ipv6, FakeIcmpBindingsCtx<Ipv6>> for FakeIcmpCoreCtx<Ipv6> {
fn handle_neighbor_probe(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_neighbor: SpecifiedAddr<Ipv6Addr>,
_link_addr: &[u8],
) {
unimplemented!()
}
fn handle_neighbor_confirmation(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
_neighbor: SpecifiedAddr<Ipv6Addr>,
_link_addr: &[u8],
_flags: ConfirmationFlags,
) {
unimplemented!()
}
fn flush_neighbor_table(
&mut self,
_bindings_ctx: &mut FakeIcmpBindingsCtx<Ipv6>,
_device_id: &Self::DeviceId,
) {
unimplemented!()
}
}
const REMOTE_ID: u16 = 1;
#[test]
fn test_receive_icmpv4_error() {
// Chosen arbitrarily to be a) non-zero (it's easy to accidentally get
// the value 0) and, b) different from each other.
const ICMP_ID: u16 = 0x0F;
const SEQ_NUM: u16 = 0xF0;
/// Test receiving an ICMP error message.
///
/// Test that receiving an ICMP error message with the given code and
/// message contents, and containing the given original IPv4 packet,
/// results in the counter values in `assert_counters`. After that
/// assertion passes, `f` is called on the context so that the caller
/// can perform whatever extra validation they want.
///
/// The error message will be sent from `FAKE_CONFIG_V4.remote_ip` to
/// `FAKE_CONFIG_V4.local_ip`. Before the message is sent, an ICMP
/// socket will be established with the ID `ICMP_ID`, and
/// `test_receive_icmpv4_error_helper` will assert that its `SocketId`
/// is 0. This allows the caller to craft the `original_packet` so that
/// it should be delivered to this socket.
fn test_receive_icmpv4_error_helper<
C: Debug,
M: IcmpMessage<Ipv4, Code = C> + Debug,
F: Fn(&FakeIcmpCtx<Ipv4>),
>(
original_packet: &mut [u8],
code: C,
msg: M,
assert_counters: &[(&str, u64)],
f: F,
) {
crate::testutil::set_logger_for_test();
let mut ctx: FakeIcmpCtx<Ipv4> = FakeIcmpCtx::default();
let mut socket_api = IcmpEchoSocketApi::<Ipv4, _>::new(ctx.as_mut());
let conn = socket_api.create();
socket_api.bind(&conn, None, NonZeroU16::new(ICMP_ID)).unwrap();
socket_api
.connect(&conn, Some(ZonedAddr::Unzoned(FAKE_CONFIG_V4.remote_ip)), REMOTE_ID)
.unwrap();
let FakeCtxWithCoreCtx { core_ctx, bindings_ctx } = &mut ctx;
<IcmpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_ip_packet(
core_ctx,
bindings_ctx,
&FakeDeviceId,
FAKE_CONFIG_V4.remote_ip.get(),
FAKE_CONFIG_V4.local_ip,
Buf::new(original_packet, ..)
.encapsulate(IcmpPacketBuilder::new(
FAKE_CONFIG_V4.remote_ip,
FAKE_CONFIG_V4.local_ip,
code,
msg,
))
.serialize_vec_outer()
.unwrap(),
)
.unwrap();
for (ctr, expected) in assert_counters {
let actual = match *ctr {
"InnerIcmpContext::receive_icmp_error" => {
core_ctx.outer.rx_counters.error.get()
}
"IcmpIpTransportContext::receive_icmp_error" => {
core_ctx.outer.rx_counters.error_delivered_to_transport_layer.get()
}
"IcmpEchoBindingsContext::receive_icmp_error" => {
core_ctx.outer.rx_counters.error_delivered_to_socket.get()
}
c => panic!("unrecognized counter: {c}"),
};
assert_eq!(actual, *expected, "wrong count for {ctr}");
}
f(&ctx);
}
// Test that, when we receive various ICMPv4 error messages, we properly
// pass them up to the IP layer and, sometimes, to the transport layer.
// First, test with an original packet containing an ICMP message. Since
// this test fake supports ICMP sockets, this error can be delivered all
// the way up the stack.
// A buffer containing an ICMP echo request with ID `ICMP_ID` and
// sequence number `SEQ_NUM` from the local IP to the remote IP. Any
// ICMP error message which contains this as its original packet should
// be delivered to the socket created in
// `test_receive_icmpv4_error_helper`.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
FAKE_CONFIG_V4.local_ip,
FAKE_CONFIG_V4.remote_ip,
IcmpUnusedCode,
IcmpEchoRequest::new(ICMP_ID, SEQ_NUM),
))
.encapsulate(<Ipv4 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V4.local_ip,
FAKE_CONFIG_V4.remote_ip,
64,
Ipv4Proto::Icmp,
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::DestUnreachable(
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4TimeExceededCode::TtlExpired,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4ParameterProblemCode::PointerIndicatesError,
Icmpv4ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::ParameterProblem(
Icmpv4ParameterProblemCode::PointerIndicatesError,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
// Second, test with an original packet containing a malformed ICMP
// packet (we accomplish this by leaving the IP packet's body empty). We
// should process this packet in
// `IcmpIpTransportContext::receive_icmp_error`, but we should go no
// further - in particular, we should not call
// `IcmpEchoBindingsContext::receive_icmp_error`.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(<Ipv4 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V4.local_ip,
FAKE_CONFIG_V4.remote_ip,
64,
Ipv4Proto::Icmp,
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::DestUnreachable(
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4TimeExceededCode::TtlExpired,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4ParameterProblemCode::PointerIndicatesError,
Icmpv4ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::ParameterProblem(
Icmpv4ParameterProblemCode::PointerIndicatesError,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
// Third, test with an original packet containing a UDP packet. This
// allows us to verify that protocol numbers are handled properly by
// checking that `IcmpIpTransportContext::receive_icmp_error` was NOT
// called.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(<Ipv4 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V4.local_ip,
FAKE_CONFIG_V4.remote_ip,
64,
IpProto::Udp.into(),
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::DestUnreachable(
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4TimeExceededCode::TtlExpired,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv4_error_helper(
buffer.as_mut(),
Icmpv4ParameterProblemCode::PointerIndicatesError,
Icmpv4ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv4ErrorCode::ParameterProblem(
Icmpv4ParameterProblemCode::PointerIndicatesError,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
}
#[test]
fn test_receive_icmpv6_error() {
// Chosen arbitrarily to be a) non-zero (it's easy to accidentally get
// the value 0) and, b) different from each other.
const ICMP_ID: u16 = 0x0F;
const SEQ_NUM: u16 = 0xF0;
/// Test receiving an ICMPv6 error message.
///
/// Test that receiving an ICMP error message with the given code and
/// message contents, and containing the given original IPv4 packet,
/// results in the counter values in `assert_counters`. After that
/// assertion passes, `f` is called on the context so that the caller
/// can perform whatever extra validation they want.
///
/// The error message will be sent from `FAKE_CONFIG_V6.remote_ip` to
/// `FAKE_CONFIG_V6.local_ip`. Before the message is sent, an ICMP
/// socket will be established with the ID `ICMP_ID`, and
/// `test_receive_icmpv6_error_helper` will assert that its `SocketId`
/// is 0. This allows the caller to craft the `original_packet` so that
/// it should be delivered to this socket.
fn test_receive_icmpv6_error_helper<
C: Debug,
M: IcmpMessage<Ipv6, Code = C> + Debug,
F: Fn(&FakeIcmpCtx<Ipv6>),
>(
original_packet: &mut [u8],
code: C,
msg: M,
assert_counters: &[(&str, u64)],
f: F,
) {
crate::testutil::set_logger_for_test();
let mut ctx = FakeIcmpCtx::<Ipv6>::default();
let mut socket_api = IcmpEchoSocketApi::<Ipv6, _>::new(ctx.as_mut());
let conn = socket_api.create();
socket_api.bind(&conn, None, NonZeroU16::new(ICMP_ID)).unwrap();
socket_api
.connect(&conn, Some(ZonedAddr::Unzoned(FAKE_CONFIG_V6.remote_ip)), REMOTE_ID)
.unwrap();
let FakeCtxWithCoreCtx { core_ctx, bindings_ctx } = &mut ctx;
<IcmpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_ip_packet(
core_ctx,
bindings_ctx,
&FakeDeviceId,
FAKE_CONFIG_V6.remote_ip.get().try_into().unwrap(),
FAKE_CONFIG_V6.local_ip,
Buf::new(original_packet, ..)
.encapsulate(IcmpPacketBuilder::new(
FAKE_CONFIG_V6.remote_ip,
FAKE_CONFIG_V6.local_ip,
code,
msg,
))
.serialize_vec_outer()
.unwrap(),
)
.unwrap();
for (ctr, count) in assert_counters {
match *ctr {
"InnerIcmpContext::receive_icmp_error" => assert_eq!(
core_ctx.outer.rx_counters.error.get(),
*count,
"wrong count for counter {ctr}",
),
"IcmpIpTransportContext::receive_icmp_error" => assert_eq!(
core_ctx.outer.rx_counters.error_delivered_to_transport_layer.get(),
*count,
"wrong count for counter {ctr}",
),
"IcmpEchoBindingsContext::receive_icmp_error" => assert_eq!(
core_ctx.outer.rx_counters.error_delivered_to_socket.get(),
*count,
"wrong count for counter {ctr}",
),
c => assert!(false, "unrecognized counter: {c}"),
}
}
f(&ctx);
}
// Test that, when we receive various ICMPv6 error messages, we properly
// pass them up to the IP layer and, sometimes, to the transport layer.
// First, test with an original packet containing an ICMPv6 message.
// Since this test fake supports ICMPv6 sockets, this error can be
// delivered all the way up the stack.
// A buffer containing an ICMPv6 echo request with ID `ICMP_ID` and
// sequence number `SEQ_NUM` from the local IP to the remote IP. Any
// ICMPv6 error message which contains this as its original packet
// should be delivered to the socket created in
// `test_receive_icmpv6_error_helper`.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(IcmpPacketBuilder::<Ipv6, _>::new(
FAKE_CONFIG_V6.local_ip,
FAKE_CONFIG_V6.remote_ip,
IcmpUnusedCode,
IcmpEchoRequest::new(ICMP_ID, SEQ_NUM),
))
.encapsulate(<Ipv6 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V6.local_ip,
FAKE_CONFIG_V6.remote_ip,
64,
Ipv6Proto::Icmpv6,
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6DestUnreachableCode::NoRoute,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6TimeExceededCode::HopLimitExceeded,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
Icmpv6ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 1),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::ParameterProblem(
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
// Second, test with an original packet containing a malformed ICMPv6
// packet (we accomplish this by leaving the IP packet's body empty). We
// should process this packet in
// `IcmpIpTransportContext::receive_icmp_error`, but we should go no
// further - in particular, we should not call
// `IcmpEchoBindingsContext::receive_icmp_error`.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(<Ipv6 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V6.local_ip,
FAKE_CONFIG_V6.remote_ip,
64,
Ipv6Proto::Icmpv6,
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6DestUnreachableCode::NoRoute,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6TimeExceededCode::HopLimitExceeded,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
Icmpv6ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 1),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::ParameterProblem(
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
// Third, test with an original packet containing a UDP packet. This
// allows us to verify that protocol numbers are handled properly by
// checking that `IcmpIpTransportContext::receive_icmp_error` was NOT
// called.
let mut buffer = Buf::new(&mut [], ..)
.encapsulate(<Ipv6 as packet_formats::ip::IpExt>::PacketBuilder::new(
FAKE_CONFIG_V6.local_ip,
FAKE_CONFIG_V6.remote_ip,
64,
IpProto::Udp.into(),
))
.serialize_vec_outer()
.unwrap();
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6DestUnreachableCode::NoRoute,
IcmpDestUnreachable::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6TimeExceededCode::HopLimitExceeded,
IcmpTimeExceeded::default(),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
test_receive_icmpv6_error_helper(
buffer.as_mut(),
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
Icmpv6ParameterProblem::new(0),
&[
("InnerIcmpContext::receive_icmp_error", 1),
("IcmpIpTransportContext::receive_icmp_error", 0),
("IcmpEchoBindingsContext::receive_icmp_error", 0),
],
|FakeCtxWithCoreCtx { core_ctx, bindings_ctx: _ }| {
let err = Icmpv6ErrorCode::ParameterProblem(
Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType,
);
assert_eq!(core_ctx.outer.receive_icmp_error, [err]);
},
);
}
#[test]
fn test_error_rate_limit() {
crate::testutil::set_logger_for_test();
/// Call `send_icmpv4_ttl_expired` with fake values.
fn send_icmpv4_ttl_expired_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv4>,
) {
send_icmpv4_ttl_expired(
core_ctx,
bindings_ctx,
&FakeDeviceId,
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V4.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V4.local_ip.try_into().unwrap(),
IpProto::Udp.into(),
Buf::new(&mut [], ..),
0,
Ipv4FragmentType::InitialFragment,
);
}
/// Call `send_icmpv4_parameter_problem` with fake values.
fn send_icmpv4_parameter_problem_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv4>,
) {
send_icmpv4_parameter_problem(
core_ctx,
bindings_ctx,
&FakeDeviceId,
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V4.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V4.local_ip.try_into().unwrap(),
Icmpv4ParameterProblemCode::PointerIndicatesError,
Icmpv4ParameterProblem::new(0),
Buf::new(&mut [], ..),
0,
Ipv4FragmentType::InitialFragment,
);
}
/// Call `send_icmpv4_dest_unreachable` with fake values.
fn send_icmpv4_dest_unreachable_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv4>,
) {
send_icmpv4_dest_unreachable(
core_ctx,
bindings_ctx,
Some(&FakeDeviceId),
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V4.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V4.local_ip.try_into().unwrap(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
Buf::new(&mut [], ..),
0,
Ipv4FragmentType::InitialFragment,
);
}
/// Call `send_icmpv6_ttl_expired` with fake values.
fn send_icmpv6_ttl_expired_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv6>,
) {
send_icmpv6_ttl_expired(
core_ctx,
bindings_ctx,
&FakeDeviceId,
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V6.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V6.local_ip.try_into().unwrap(),
IpProto::Udp.into(),
Buf::new(&mut [], ..),
0,
);
}
/// Call `send_icmpv6_packet_too_big` with fake values.
fn send_icmpv6_packet_too_big_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv6>,
) {
send_icmpv6_packet_too_big(
core_ctx,
bindings_ctx,
&FakeDeviceId,
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V6.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V6.local_ip.try_into().unwrap(),
IpProto::Udp.into(),
Mtu::new(0),
Buf::new(&mut [], ..),
0,
);
}
/// Call `send_icmpv6_parameter_problem` with fake values.
fn send_icmpv6_parameter_problem_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv6>,
) {
send_icmpv6_parameter_problem(
core_ctx,
bindings_ctx,
&FakeDeviceId,
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V6.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V6.local_ip.try_into().unwrap(),
Icmpv6ParameterProblemCode::ErroneousHeaderField,
Icmpv6ParameterProblem::new(0),
Buf::new(&mut [], ..),
false,
);
}
/// Call `send_icmpv6_dest_unreachable` with fake values.
fn send_icmpv6_dest_unreachable_helper(
FakeCtxWithCoreCtx { core_ctx, bindings_ctx }: &mut FakeIcmpCtx<Ipv6>,
) {
send_icmpv6_dest_unreachable(
core_ctx,
bindings_ctx,
Some(&FakeDeviceId),
Some(FrameDestination::Individual { local: true }),
FAKE_CONFIG_V6.remote_ip.try_into().unwrap(),
FAKE_CONFIG_V6.local_ip.try_into().unwrap(),
Icmpv6DestUnreachableCode::NoRoute,
Buf::new(&mut [], ..),
);
}
// Run tests for each function that sends error messages to make sure
// they're all properly rate limited.
fn run_test<
I: datagram::IpExt,
W: Fn(u64) -> FakeIcmpCtx<I>,
S: Fn(&mut FakeIcmpCtx<I>),
>(
with_errors_per_second: W,
send: S,
) {
// Note that we could theoretically have more precise tests here
// (e.g., a test that we send at the correct rate over the long
// term), but those would amount to testing the `TokenBucket`
// implementation, which has its own exhaustive tests. Instead, we
// just have a few sanity checks to make sure that we're actually
// invoking it when we expect to (as opposed to bypassing it
// entirely or something).
// Test that, if no time has elapsed, we can successfully send up to
// `ERRORS_PER_SECOND` error messages, but no more.
// Don't use `DEFAULT_ERRORS_PER_SECOND` because it's 2^16 and it
// makes this test take a long time.
const ERRORS_PER_SECOND: u64 = 64;
let mut ctx = with_errors_per_second(ERRORS_PER_SECOND);
for i in 0..ERRORS_PER_SECOND {
send(&mut ctx);
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), i + 1);
}
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), ERRORS_PER_SECOND);
send(&mut ctx);
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), ERRORS_PER_SECOND);
// Test that, if we set a rate of 0, we are not able to send any
// error messages regardless of how much time has elapsed.
let mut ctx = with_errors_per_second(0);
send(&mut ctx);
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), 0);
ctx.bindings_ctx.sleep_skip_timers(Duration::from_secs(1));
send(&mut ctx);
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), 0);
ctx.bindings_ctx.sleep_skip_timers(Duration::from_secs(1));
send(&mut ctx);
assert_eq!(ctx.core_ctx.outer.tx_counters.error.get(), 0);
}
fn with_errors_per_second_v4(errors_per_second: u64) -> FakeIcmpCtx<Ipv4> {
FakeCtxWithCoreCtx::with_core_ctx(Wrapped {
outer: FakeIcmpCoreCtxState::with_errors_per_second(errors_per_second),
inner: Default::default(),
})
}
run_test::<Ipv4, _, _>(with_errors_per_second_v4, send_icmpv4_ttl_expired_helper);
run_test::<Ipv4, _, _>(with_errors_per_second_v4, send_icmpv4_parameter_problem_helper);
run_test::<Ipv4, _, _>(with_errors_per_second_v4, send_icmpv4_dest_unreachable_helper);
fn with_errors_per_second_v6(errors_per_second: u64) -> FakeIcmpCtx<Ipv6> {
FakeCtxWithCoreCtx::with_core_ctx(Wrapped {
outer: FakeIcmpCoreCtxState::with_errors_per_second(errors_per_second),
inner: Default::default(),
})
}
run_test::<Ipv6, _, _>(with_errors_per_second_v6, send_icmpv6_ttl_expired_helper);
run_test::<Ipv6, _, _>(with_errors_per_second_v6, send_icmpv6_packet_too_big_helper);
run_test::<Ipv6, _, _>(with_errors_per_second_v6, send_icmpv6_parameter_problem_helper);
run_test::<Ipv6, _, _>(with_errors_per_second_v6, send_icmpv6_dest_unreachable_helper);
}
}