| // 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). |
| |
| use alloc::vec::Vec; |
| use core::fmt::Debug; |
| |
| use byteorder::{ByteOrder, NetworkEndian}; |
| use log::{debug, trace}; |
| use net_types::ip::{Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use net_types::{MulticastAddress, SpecifiedAddr, Witness}; |
| use packet::{BufferMut, ParseBuffer, Serializer, TruncateDirection, TruncatingSerializer}; |
| use zerocopy::ByteSlice; |
| |
| use crate::context::{CounterContext, InstantContext, StateContext}; |
| use crate::data_structures::token_bucket::TokenBucket; |
| use crate::device::ndp::NdpPacketHandler; |
| use crate::device::{DeviceId, FrameDestination}; |
| use crate::error::{ExistsError, NoRouteError, SocketError}; |
| use crate::ip::forwarding::ForwardingTable; |
| use crate::ip::{ |
| gmp::mld::MldPacketHandler, |
| path_mtu::PmtuHandler, |
| socket::{ |
| BufferIpSocketContext, IpSock, IpSocket, IpSocketContext, SendError, UnroutableBehavior, |
| }, |
| BufferIpTransportContext, IpDeviceIdContext, IpExt, IpProto, IpTransportContext, |
| IpVersionMarker, TransportReceiveError, |
| }; |
| use crate::socket::Socket; |
| use crate::transport::ConnAddrMap; |
| use crate::wire::icmp::{ |
| self as wire_icmp, 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, |
| }; |
| use crate::wire::ipv4::Ipv4Header; |
| use crate::wire::ipv6::{Ipv6Header, UndefinedBodyBoundsError}; |
| use crate::{BufferDispatcher, Context, EventDispatcher}; |
| |
| /// 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), |
| } |
| |
| /// 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), |
| } |
| |
| pub(crate) struct IcmpState<A: IpAddress, Instant, S> { |
| conns: ConnAddrMap<IcmpAddr<A>, IcmpConn<S>>, |
| error_send_bucket: TokenBucket<Instant>, |
| } |
| |
| /// A builder for ICMPv4 state. |
| #[derive(Copy, Clone)] |
| pub 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. |
| pub fn send_timestamp_reply(&mut self, send_timestamp_reply: bool) -> &mut Self { |
| self.send_timestamp_reply = send_timestamp_reply; |
| self |
| } |
| |
| /// Configure the number of ICMPv4 error messages to send per second |
| /// (default: [`DEFAULT_ERRORS_PER_SECOND`]). |
| /// |
| /// The number of ICMPv4 error messages sent by the stack will be rate |
| /// limited to the given number of errors per second. Any messages that |
| /// exceed this rate will be silently dropped. |
| pub fn errors_per_second(&mut self, errors_per_second: u64) -> &mut Self { |
| self.errors_per_second = errors_per_second; |
| self |
| } |
| |
| pub(crate) fn build<Instant, S>(self) -> Icmpv4State<Instant, S> { |
| Icmpv4State { |
| inner: IcmpState { |
| conns: ConnAddrMap::default(), |
| error_send_bucket: TokenBucket::new(self.errors_per_second), |
| }, |
| send_timestamp_reply: self.send_timestamp_reply, |
| } |
| } |
| } |
| |
| /// The state associated with the ICMPv4 protocol. |
| pub(crate) struct Icmpv4State<Instant, S> { |
| inner: IcmpState<Ipv4Addr, Instant, S>, |
| send_timestamp_reply: bool, |
| } |
| |
| // Used by `receive_icmp_echo_reply`. |
| impl<Instant, S> AsRef<IcmpState<Ipv4Addr, Instant, S>> for Icmpv4State<Instant, S> { |
| fn as_ref(&self) -> &IcmpState<Ipv4Addr, Instant, S> { |
| &self.inner |
| } |
| } |
| |
| // Used by `send_icmpv4_echo_request_inner`. |
| impl<Instant, S> AsMut<IcmpState<Ipv4Addr, Instant, S>> for Icmpv4State<Instant, S> { |
| fn as_mut(&mut self) -> &mut IcmpState<Ipv4Addr, Instant, S> { |
| &mut self.inner |
| } |
| } |
| |
| /// A builder for ICMPv6 state. |
| #[derive(Copy, Clone)] |
| pub struct Icmpv6StateBuilder { |
| errors_per_second: u64, |
| } |
| |
| impl Default for Icmpv6StateBuilder { |
| fn default() -> Icmpv6StateBuilder { |
| Icmpv6StateBuilder { errors_per_second: DEFAULT_ERRORS_PER_SECOND } |
| } |
| } |
| |
| impl Icmpv6StateBuilder { |
| /// Configure the number of ICMPv6 error messages to send per second |
| /// (default: [`DEFAULT_ERRORS_PER_SECOND`]). |
| /// |
| /// The number of ICMPv6 error messages sent by the stack will be rate |
| /// limited to the given number of errors per second. Any messages that |
| /// exceed this rate will be silently dropped. |
| /// |
| /// This rate limit is required by [RFC 4443 Section 2.4] (f). |
| /// |
| /// [RFC 4443 Section 2.4]: https://tools.ietf.org/html/rfc4443#section-2.4 |
| pub fn errors_per_second(&mut self, errors_per_second: u64) -> &mut Self { |
| self.errors_per_second = errors_per_second; |
| self |
| } |
| |
| pub(crate) fn build<Instant, S>(self) -> Icmpv6State<Instant, S> { |
| Icmpv6State { |
| inner: IcmpState { |
| conns: ConnAddrMap::default(), |
| error_send_bucket: TokenBucket::new(self.errors_per_second), |
| }, |
| } |
| } |
| } |
| |
| /// The state associated with the ICMPv6 protocol. |
| pub(crate) struct Icmpv6State<Instant, S> { |
| inner: IcmpState<Ipv6Addr, Instant, S>, |
| } |
| |
| // Used by `receive_icmp_echo_reply`. |
| impl<Instant, S> AsRef<IcmpState<Ipv6Addr, Instant, S>> for Icmpv6State<Instant, S> { |
| fn as_ref(&self) -> &IcmpState<Ipv6Addr, Instant, S> { |
| &self.inner |
| } |
| } |
| |
| // Used by `send_icmpv6_echo_request_inner`. |
| impl<Instant, S> AsMut<IcmpState<Ipv6Addr, Instant, S>> for Icmpv6State<Instant, S> { |
| fn as_mut(&mut self) -> &mut IcmpState<Ipv6Addr, Instant, S> { |
| &mut self.inner |
| } |
| } |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Hash)] |
| pub(crate) struct IcmpAddr<A: IpAddress> { |
| local_addr: SpecifiedAddr<A>, |
| remote_addr: SpecifiedAddr<A>, |
| icmp_id: u16, |
| } |
| |
| #[derive(Clone)] |
| pub(crate) struct IcmpConn<S> { |
| icmp_id: u16, |
| ip: S, |
| } |
| |
| impl<'a, A: IpAddress, S: IpSocket<A::Version>> From<&'a IcmpConn<S>> for IcmpAddr<A> { |
| fn from(conn: &'a IcmpConn<S>) -> IcmpAddr<A> { |
| IcmpAddr { |
| local_addr: *conn.ip.local_ip(), |
| remote_addr: *conn.ip.remote_ip(), |
| icmp_id: conn.icmp_id, |
| } |
| } |
| } |
| |
| /// The ID identifying an ICMP connection. |
| /// |
| /// When a new ICMP connection is added, it is given a unique `IcmpConnId`. |
| /// These are opaque `usize`s which are intentionally allocated as densely as |
| /// possible around 0, making it possible to store any associated data in a |
| /// `Vec` indexed by the ID. `IcmpConnId` implements `Into<usize>`. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] |
| pub struct IcmpConnId<I: Ip>(usize, IpVersionMarker<I>); |
| |
| impl<I: Ip> IcmpConnId<I> { |
| fn new(id: usize) -> IcmpConnId<I> { |
| IcmpConnId(id, IpVersionMarker::default()) |
| } |
| } |
| |
| impl<I: Ip> From<IcmpConnId<I>> for usize { |
| fn from(id: IcmpConnId<I>) -> usize { |
| id.0 |
| } |
| } |
| |
| /// Apply an update to all IPv4 sockets that this module is responsible for |
| /// - namely, those contained in ICMPv4 sockets. |
| pub(super) fn apply_ipv4_socket_update<C: Icmpv4SocketContext>( |
| ctx: &mut C, |
| update: <C::IpSocket as Socket>::Update, |
| ) { |
| let (state, meta) = ctx.get_state_and_update_meta(); |
| // We have to collect into a `Vec` here because the iterator borrows `ctx`, |
| // which we need mutable access to in order to report the closures. |
| let closed_conns = state |
| .inner |
| .conns |
| .update_retain(|conn| conn.ip.apply_update(&update, meta)) |
| .collect::<Vec<_>>(); |
| for (id, _conn, err) in closed_conns { |
| ctx.close_icmp_connection(IcmpConnId::new(id), err); |
| } |
| } |
| |
| /// Apply an update to all IPv6 sockets that this module is responsible for |
| /// - namely, those contained in ICMPv6 sockets. |
| pub(super) fn apply_ipv6_socket_update<C: Icmpv6SocketContext>( |
| ctx: &mut C, |
| update: <C::IpSocket as Socket>::Update, |
| ) { |
| let (state, meta) = ctx.get_state_and_update_meta(); |
| // We have to collect into a `Vec` here because the iterator borrows `ctx`, |
| // which we need mutable access to in order to report the closures. |
| let closed_conns = state |
| .inner |
| .conns |
| .update_retain(|conn| conn.ip.apply_update(&update, meta)) |
| .collect::<Vec<_>>(); |
| for (id, _conn, err) in closed_conns { |
| ctx.close_icmp_connection(IcmpConnId::new(id), err); |
| } |
| } |
| |
| /// An extension trait adding extra ICMP-related functionality to IP versions. |
| pub trait IcmpIpExt: wire_icmp::IcmpIpExt { |
| /// The type of error code for this version of ICMP - [`Icmpv4ErrorCode`] or |
| /// [`Icmpv6ErrorCode`]. |
| type ErrorCode: Debug; |
| } |
| |
| impl IcmpIpExt for Ipv4 { |
| type ErrorCode = Icmpv4ErrorCode; |
| } |
| |
| impl IcmpIpExt for Ipv6 { |
| type ErrorCode = Icmpv6ErrorCode; |
| } |
| |
| /// An event dispatcher for the ICMP layer. |
| /// |
| /// See the `EventDispatcher` trait in the crate root for more details. |
| pub trait IcmpEventDispatcher<I: IcmpIpExt> { |
| /// Receive an ICMP error message related to a previously-sent ICMP echo |
| /// request. |
| /// |
| /// `seq_num` is the sequence number of the original echo request that |
| /// triggered the error, and `err` is the specific error identified by the |
| /// incoming ICMP error message. |
| fn receive_icmp_error(&mut self, _conn: IcmpConnId<I>, _seq_num: u16, _err: I::ErrorCode) { |
| log_unimplemented!((), "IcmpEventDispatcher::receive_icmp_error: not implemented"); |
| } |
| |
| /// Close an ICMP connection because it is no longer routable. |
| /// |
| /// `close_icmp_connection` is called when a change to routing state has |
| /// made an ICMP socket no longer routable. After the call has returned, the |
| /// core will completely remove the socket, and any future calls referencing |
| /// it will either panic (because of an unrecognized [`IcmpConnId`]) or |
| /// incorrectly refer to a different, newly-opened ICMP connection. |
| fn close_icmp_connection(&mut self, conn: IcmpConnId<I>, err: NoRouteError); |
| } |
| |
| /// An event dispatcher for the ICMP layer when a buffer is required. |
| /// |
| /// `BufferIcmpEventDispatcher` extends [`IcmpEventDispatcher`] by adding |
| /// methods that require a buffer. |
| pub trait BufferIcmpEventDispatcher<I: IcmpIpExt, B: BufferMut>: IcmpEventDispatcher<I> { |
| /// Receive an ICMP echo reply. |
| fn receive_icmp_echo_reply(&mut self, _conn: IcmpConnId<I>, _seq_num: u16, _data: B) { |
| log_unimplemented!((), "IcmpEventDispatcher::receive_icmp_echo_reply: not implemented"); |
| } |
| } |
| |
| /// The execution context for ICMP sockets. |
| /// |
| /// `IcmpSocketContext` provides support for receiving ICMP echo replies for |
| /// both ICMP(v4) and ICMPv6 sockets. |
| pub(crate) trait IcmpSocketContext<I: IcmpIpExt>: |
| IpDeviceIdContext + CounterContext + InstantContext + IpSocketContext<I> |
| { |
| /// Receive an ICMP error message related to a previously-sent ICMP echo |
| /// request. |
| /// |
| /// `seq_num` is the sequence number of the original echo request that |
| /// triggered the error, and `err` is the specific error identified by the |
| /// incoming ICMP error message. |
| fn receive_icmp_error(&mut self, _conn: IcmpConnId<I>, _seq_num: u16, _err: I::ErrorCode) { |
| log_unimplemented!((), "IcmpSocketContext::receive_icmp_error: not implemented"); |
| } |
| |
| /// Close an ICMP connection because it is no longer routable. |
| /// |
| /// `close_icmp_connection` is called when a change to routing state has |
| /// made an ICMP socket no longer routable. After the call has returned, the |
| /// core will completely remove the socket, and any future calls referencing |
| /// it will either panic (because of an unrecognized [`IcmpConnId`]) or |
| /// incorrectly refer to a different, newly-opened ICMP connection. |
| fn close_icmp_connection(&mut self, conn: IcmpConnId<I>, err: NoRouteError); |
| } |
| |
| /// The execution context for ICMP sockets when a buffer is required. |
| /// |
| /// `BufferIcmpSocketContext` extends [`IcmpSocketContext`], adding methods that |
| /// require a buffer type parameter. |
| pub(crate) trait BufferIcmpSocketContext<I: IcmpIpExt, B: BufferMut>: |
| IcmpSocketContext<I> + BufferIpSocketContext<I, B> |
| { |
| /// Receive an ICMP echo reply. |
| /// |
| /// If `I` is `Ipv4`, then this is an ICMP(v4) echo reply, and if it's |
| /// `Ipv6`, then this is an ICMPv6 echo reply. |
| fn receive_icmp_echo_reply(&mut self, _conn: IcmpConnId<I>, _seq_num: u16, _data: B) { |
| log_unimplemented!((), "IcmpSocketContext::receive_icmp_echo_reply: not implemented"); |
| } |
| } |
| |
| /// The execution context for ICMPv4 sockets. |
| /// |
| /// `Icmpv4SocketContext` extends [`IcmpSocketContext`] with ICMPv4-specific |
| /// functionality. |
| pub(crate) trait Icmpv4SocketContext: |
| IpDeviceIdContext |
| + IcmpSocketContext<Ipv4> |
| + StateContext< |
| Icmpv4State<<Self as InstantContext>::Instant, <Self as IpSocketContext<Ipv4>>::IpSocket>, |
| > |
| { |
| /// Get the [`Icmpv4State`] and the metadata needed to apply an IP socket |
| /// update at the same time. |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> (&mut Icmpv4State<Self::Instant, Self::IpSocket>, &<Self::IpSocket as Socket>::UpdateMeta); |
| } |
| |
| /// The execution context for ICMPv6 sockets. |
| /// |
| /// `Icmpv6SocketContext` extends [`IcmpSocketContext`] with ICMPv6-specific |
| /// functionality. |
| pub(crate) trait Icmpv6SocketContext: |
| IpDeviceIdContext |
| + IcmpSocketContext<Ipv6> |
| + StateContext< |
| Icmpv6State<<Self as InstantContext>::Instant, <Self as IpSocketContext<Ipv6>>::IpSocket>, |
| > |
| { |
| /// Get the [`Icmpv6State`] and the metadata needed to apply an IP socket |
| /// update at the same time. |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> (&mut Icmpv6State<Self::Instant, Self::IpSocket>, &<Self::IpSocket as Socket>::UpdateMeta); |
| } |
| |
| impl<D: EventDispatcher> IcmpSocketContext<Ipv4> for Context<D> { |
| fn receive_icmp_error(&mut self, conn: IcmpConnId<Ipv4>, seq_num: u16, err: Icmpv4ErrorCode) { |
| IcmpEventDispatcher::receive_icmp_error(self.dispatcher_mut(), conn, seq_num, err); |
| } |
| |
| fn close_icmp_connection(&mut self, conn: IcmpConnId<Ipv4>, err: NoRouteError) { |
| self.dispatcher_mut().close_icmp_connection(conn, err); |
| } |
| } |
| |
| impl<D: EventDispatcher> IcmpSocketContext<Ipv6> for Context<D> { |
| fn receive_icmp_error(&mut self, conn: IcmpConnId<Ipv6>, seq_num: u16, err: Icmpv6ErrorCode) { |
| IcmpEventDispatcher::receive_icmp_error(self.dispatcher_mut(), conn, seq_num, err); |
| } |
| |
| fn close_icmp_connection(&mut self, conn: IcmpConnId<Ipv6>, err: NoRouteError) { |
| self.dispatcher_mut().close_icmp_connection(conn, err); |
| } |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> BufferIcmpSocketContext<Ipv4, B> for Context<D> { |
| fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId<Ipv4>, seq_num: u16, data: B) { |
| self.dispatcher_mut().receive_icmp_echo_reply(conn, seq_num, data); |
| } |
| } |
| |
| impl<B: BufferMut, D: BufferDispatcher<B>> BufferIcmpSocketContext<Ipv6, B> for Context<D> { |
| fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId<Ipv6>, seq_num: u16, data: B) { |
| self.dispatcher_mut().receive_icmp_echo_reply(conn, seq_num, data); |
| } |
| } |
| |
| impl<D: EventDispatcher> Icmpv4SocketContext for Context<D> { |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> ( |
| &mut Icmpv4State<Self::Instant, IpSock<Ipv4, DeviceId>>, |
| &ForwardingTable<Ipv4, Self::DeviceId>, |
| ) { |
| let state = self.state_mut(); |
| (&mut state.ipv4.icmp, &state.ipv4.inner.table) |
| } |
| } |
| |
| impl<D: EventDispatcher> Icmpv6SocketContext for Context<D> { |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> ( |
| &mut Icmpv6State<Self::Instant, IpSock<Ipv6, DeviceId>>, |
| &ForwardingTable<Ipv6, Self::DeviceId>, |
| ) { |
| let state = self.state_mut(); |
| (&mut state.ipv6.icmp, &state.ipv6.inner.table) |
| } |
| } |
| |
| /// The execution context shared by ICMP(v4) and ICMPv6 for the internal |
| /// operations of the IP stack. |
| /// |
| /// Unlike [`IcmpSocketContext`], `IcmpContext` is not exposed outside of this |
| /// crate. |
| pub(crate) trait IcmpContext<I: IcmpIpExt>: |
| IcmpSocketContext<I> |
| + StateContext< |
| IcmpState< |
| I::Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<I>>::IpSocket, |
| >, |
| > + CounterContext |
| + InstantContext |
| { |
| // 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. |
| |
| /// Receive an ICMP error message. |
| /// |
| /// All arguments beginning with `original_` are fields from the IP packet |
| /// that triggered the error. The `original_body` is provided here so that |
| /// the error can be associated with a transport-layer socket. |
| /// |
| /// 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, |
| original_src_ip: Option<SpecifiedAddr<I::Addr>>, |
| original_dst_ip: SpecifiedAddr<I::Addr>, |
| original_proto: IpProto, |
| original_body: &[u8], |
| err: I::ErrorCode, |
| ); |
| } |
| |
| /// The execution context shared by ICMP(v4) and ICMPv6 for the internal |
| /// operations of the IP stack when a buffer is required. |
| pub(crate) trait BufferIcmpContext<I: IcmpIpExt, B: BufferMut>: |
| IcmpContext<I> + BufferIcmpSocketContext<I, B> |
| { |
| /// Send 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. |
| /// |
| /// `dst_ip` must not be the unspecified address, but this is guaranteed |
| /// statically because packets destined to the unspecified address are never |
| /// delivered locally. `src_ip` must not be the unspecified address, as we |
| /// are replying to this packet, and the unspecified address is not |
| /// routable. |
| /// |
| /// `get_body` 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` 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<S: Serializer<Buffer = B>, F: FnOnce(SpecifiedAddr<I::Addr>) -> S>( |
| &mut self, |
| device: Option<Self::DeviceId>, |
| src_ip: SpecifiedAddr<I::Addr>, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| get_body: F, |
| ) -> Result<(), S>; |
| |
| /// Send an ICMP error message to a remote host. |
| /// |
| /// `send_icmp_error_message` sends an ICMP error message. It takes the |
| /// ingress device, source IP, and destination IP of the packet that |
| /// generated the error. It uses ICMP-specific logic to figure out whether |
| /// and how to send an ICMP error message. `ip_mtu` is an optional MTU size |
| /// for the final IP packet generated by this ICMP response. `src_ip` must |
| /// not be the unspecified address, as we are replying to this packet, and |
| /// the unspecified address is not routable (RFCs 792 and 4443 also disallow |
| /// sending error messages in response to packets with an unspecified source |
| /// address, probably for exactly this reason). |
| /// |
| /// `send_icmp_error_message` is responsible for calling |
| /// [`should_send_icmpv4_error`] or [`should_send_icmpv6_error`]. If those |
| /// return `false`, then it must not send the message regardless of whatever |
| /// other logic is used. |
| /// |
| /// If the encapsulated error message is an ICMPv6 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, |
| /// `allow_dst_multicast` must be set to `true`. See |
| /// [`should_send_icmpv6_error`] for more details. |
| /// |
| /// get_body` 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` 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_error_message<S: Serializer<Buffer = B>, F: FnOnce(SpecifiedAddr<I::Addr>) -> S>( |
| &mut self, |
| device: Self::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<I::Addr>, |
| dst_ip: I::Addr, |
| get_body: F, |
| ip_mtu: Option<u32>, |
| allow_dst_multicast: bool, |
| ) -> Result<(), S>; |
| } |
| |
| /// The execution context for ICMPv4. |
| /// |
| /// `Icmpv4Context` is a shorthand for a larger collection of trait impls. |
| pub(crate) trait Icmpv4Context: |
| StateContext< |
| Icmpv4State<<Self as InstantContext>::Instant, <Self as IpSocketContext<Ipv4>>::IpSocket>, |
| > + Icmpv4SocketContext |
| { |
| } |
| |
| impl<C> Icmpv4Context for C where |
| C: StateContext< |
| Icmpv4State< |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv4>>::IpSocket, |
| >, |
| > + Icmpv4SocketContext |
| { |
| } |
| |
| /// The execution context for ICMPv4 where a buffer is required. |
| /// |
| /// `BufferIcmpv4Context<B>` is a shorthand for `Icmpv4Context + BufferIcmpContext<Ipv4, B>`. |
| pub(crate) trait BufferIcmpv4Context<B: BufferMut>: |
| Icmpv4Context + BufferIcmpContext<Ipv4, B> |
| { |
| } |
| |
| impl<B: BufferMut, C: Icmpv4Context + BufferIcmpContext<Ipv4, B>> BufferIcmpv4Context<B> for C {} |
| |
| impl<C> |
| StateContext< |
| IcmpState< |
| Ipv4Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv4>>::IpSocket, |
| >, |
| > for C |
| where |
| C: InstantContext |
| + IpSocketContext<Ipv4> |
| + StateContext< |
| Icmpv4State< |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv4>>::IpSocket, |
| >, |
| >, |
| { |
| fn get_state_with( |
| &self, |
| _id: (), |
| ) -> &IcmpState< |
| Ipv4Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv4>>::IpSocket, |
| > { |
| &self.get_state().inner |
| } |
| |
| fn get_state_mut_with( |
| &mut self, |
| _id: (), |
| ) -> &mut IcmpState< |
| Ipv4Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv4>>::IpSocket, |
| > { |
| &mut self.get_state_mut().inner |
| } |
| } |
| |
| /// The execution context for ICMPv6. |
| /// |
| /// `Icmpv6Context` is a shorthand for a larger collection of trait impls. |
| pub(crate) trait Icmpv6Context: |
| StateContext< |
| Icmpv6State<<Self as InstantContext>::Instant, <Self as IpSocketContext<Ipv6>>::IpSocket>, |
| > + Icmpv6SocketContext |
| { |
| } |
| |
| impl<C> Icmpv6Context for C where |
| C: StateContext< |
| Icmpv6State< |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv6>>::IpSocket, |
| >, |
| > + Icmpv6SocketContext |
| { |
| } |
| |
| /// The execution context for ICMPv6 where a buffer is required. |
| /// |
| /// `BufferIcmpv6Context<B>` is a shorthand for `Icmpv6Context + BufferIcmpContext<Ipv6, B>`. |
| pub(crate) trait BufferIcmpv6Context<B: BufferMut>: |
| Icmpv6Context + BufferIcmpContext<Ipv6, B> |
| { |
| } |
| |
| impl<B: BufferMut, C: Icmpv6Context + BufferIcmpContext<Ipv6, B>> BufferIcmpv6Context<B> for C {} |
| |
| impl<C> |
| StateContext< |
| IcmpState< |
| Ipv6Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv6>>::IpSocket, |
| >, |
| > for C |
| where |
| C: InstantContext |
| + IpSocketContext<Ipv6> |
| + StateContext< |
| Icmpv6State< |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv6>>::IpSocket, |
| >, |
| >, |
| { |
| fn get_state_with( |
| &self, |
| _id: (), |
| ) -> &IcmpState< |
| Ipv6Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv6>>::IpSocket, |
| > { |
| &self.get_state().inner |
| } |
| |
| fn get_state_mut_with( |
| &mut self, |
| _id: (), |
| ) -> &mut IcmpState< |
| Ipv6Addr, |
| <Self as InstantContext>::Instant, |
| <Self as IpSocketContext<Ipv6>>::IpSocket, |
| > { |
| &mut self.get_state_mut().inner |
| } |
| } |
| |
| /// Attempt to send an ICMP or ICMPv6 error message, applying a rate limit. |
| /// |
| /// `try_send_error!($ctx, $e)` attempts to consume a token from the token |
| /// bucket at `$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 { |
| ($ctx:expr, $e:expr) => {{ |
| // TODO(joshlf): Figure out a way to avoid querying for the current time |
| // unconditionally. See the documentation on the `CachedInstantContext` |
| // type for more information. |
| let instant_ctx = crate::context::new_cached_instant_context($ctx); |
| let state: &mut IcmpState<_, _, _> = $ctx.get_state_mut(); |
| if state.error_send_bucket.try_take(&instant_ctx) { |
| $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 {} |
| |
| impl<I: IcmpIpExt, C: IcmpContext<I>> IpTransportContext<I, C> for IcmpIpTransportContext |
| where |
| IcmpEchoRequest: for<'a> IcmpMessage<I, &'a [u8]>, |
| { |
| fn receive_icmp_error( |
| ctx: &mut C, |
| original_src_ip: Option<SpecifiedAddr<I::Addr>>, |
| original_dst_ip: SpecifiedAddr<I::Addr>, |
| mut original_body: &[u8], |
| err: I::ErrorCode, |
| ) { |
| ctx.increment_counter("IcmpIpTransportContext::receive_icmp_error"); |
| 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 = try_unit!(original_src_ip.ok_or_else(|| { |
| trace!("IcmpIpTransportContext::receive_icmp_error: Got ICMP error message for IP packet with an unspecified destination IP address"); |
| })); |
| let id = echo_request.message().id(); |
| if let Some(conn) = ctx.get_state().conns.get_id_by_addr(&IcmpAddr { |
| local_addr: original_src_ip, |
| remote_addr: original_dst_ip, |
| icmp_id: id, |
| }) { |
| let seq = echo_request.message().seq(); |
| IcmpSocketContext::receive_icmp_error(ctx, IcmpConnId::new(conn), seq, err); |
| } 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"); |
| } |
| } |
| } |
| |
| impl<B: BufferMut, C: BufferIcmpv4Context<B> + PmtuHandler<Ipv4>> |
| BufferIpTransportContext<Ipv4, B, C> for IcmpIpTransportContext |
| { |
| fn receive_ip_packet( |
| ctx: &mut C, |
| device: Option<C::DeviceId>, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| mut buffer: B, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| trace!( |
| "<IcmpIpTransportContext as BufferIpTransportContext<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) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::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 _ = ctx.send_icmp_reply(device, remote_ip, local_ip, |src_ip| { |
| buffer.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| src_ip, |
| remote_ip, |
| code, |
| req.reply(), |
| )) |
| }); |
| } else { |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Received echo request with an unspecified source address"); |
| } |
| } |
| Icmpv4Packet::EchoReply(echo_reply) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::echo_reply"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Received an EchoReply message"); |
| let id = echo_reply.message().id(); |
| let seq = echo_reply.message().seq(); |
| receive_icmp_echo_reply(ctx, src_ip, dst_ip, id, seq, buffer); |
| } |
| Icmpv4Packet::TimestampRequest(timestamp_request) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::timestamp_request"); |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| if StateContext::<Icmpv4State<_, _>>::get_state(ctx).send_timestamp_reply { |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<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 _ = ctx.send_icmp_reply(device, remote_ip, local_ip, |src_ip| { |
| buffer.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| src_ip, |
| remote_ip, |
| IcmpUnusedCode, |
| reply, |
| )) |
| }); |
| } else { |
| trace!( |
| "<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Silently ignoring Timestamp Request message" |
| ); |
| } |
| } else { |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<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 BufferIpTransportContext<Ipv4>>::receive_ip_packet: Received unsolicited Timestamp Reply message"); |
| } |
| Icmpv4Packet::DestUnreachable(dest_unreachable) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::dest_unreachable"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<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). |
| ctx.update_pmtu_if_less( |
| dst_ip.get(), |
| src_ip, |
| 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 = NetworkEndian::read_u16(&original_packet_buf[2..4]); |
| |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Next-Hop MTU is 0 so using the next best PMTU value from {}", total_len); |
| |
| ctx.update_pmtu_next_lower(dst_ip.get(), src_ip, 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 BufferIpTransportContext<Ipv4>>::receive_ip_packet: Original packet buf is too small to get original packet len so ignoring"); |
| } |
| } |
| } |
| |
| receive_icmpv4_error( |
| ctx, |
| &dest_unreachable, |
| Icmpv4ErrorCode::DestUnreachable(dest_unreachable.code()), |
| ); |
| } |
| Icmpv4Packet::TimeExceeded(time_exceeded) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::time_exceeded"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Received a Time Exceeded message"); |
| |
| receive_icmpv4_error( |
| ctx, |
| &time_exceeded, |
| Icmpv4ErrorCode::TimeExceeded(time_exceeded.code()), |
| ); |
| } |
| Icmpv4Packet::Redirect(_) => log_unimplemented!((), "<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::redirect"), |
| Icmpv4Packet::ParameterProblem(parameter_problem) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::parameter_problem"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet: Received a Parameter Problem message"); |
| |
| receive_icmpv4_error( |
| ctx, |
| ¶meter_problem, |
| Icmpv4ErrorCode::ParameterProblem(parameter_problem.code()), |
| ); |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| impl< |
| B: BufferMut, |
| C: Icmpv6Context |
| + BufferIcmpContext<Ipv6, B> |
| + PmtuHandler<Ipv6> |
| + MldPacketHandler<(), <C as IpDeviceIdContext>::DeviceId> |
| + NdpPacketHandler<<C as IpDeviceIdContext>::DeviceId>, |
| > BufferIpTransportContext<Ipv6, B, C> for IcmpIpTransportContext |
| { |
| fn receive_ip_packet( |
| ctx: &mut C, |
| device: Option<C::DeviceId>, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| mut buffer: B, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| trace!( |
| "<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet({}, {})", |
| src_ip, |
| dst_ip |
| ); |
| |
| let packet = |
| match buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) { |
| Ok(packet) => packet, |
| Err(_) => return Ok(()), // TODO(joshlf): Do something else here? |
| }; |
| |
| match packet { |
| Icmpv6Packet::EchoRequest(echo_request) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet::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 _ = ctx.send_icmp_reply(device, remote_ip, local_ip, |src_ip| { |
| buffer.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| src_ip, |
| remote_ip, |
| code, |
| req.reply(), |
| )) |
| }); |
| } else { |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet: Received echo request with an unspecified source address"); |
| } |
| } |
| Icmpv6Packet::EchoReply(echo_reply) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet::echo_reply"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet: Received an EchoReply message"); |
| let id = echo_reply.message().id(); |
| let seq = echo_reply.message().seq(); |
| receive_icmp_echo_reply(ctx, src_ip, dst_ip, id, seq, buffer); |
| } |
| Icmpv6Packet::Ndp(packet) => ctx.receive_ndp_packet( |
| device.expect("received NDP packet from localhost"), |
| src_ip, |
| dst_ip, |
| packet, |
| ), |
| Icmpv6Packet::PacketTooBig(packet_too_big) => { |
| ctx.increment_counter("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet::packet_too_big"); |
| trace!("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet: Received a Packet Too Big message"); |
| // 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). |
| ctx.update_pmtu_if_less(dst_ip.get(), src_ip, packet_too_big.message().mtu()); |
| receive_icmpv6_error(ctx, &packet_too_big, Icmpv6ErrorCode::PacketTooBig); |
| } |
| Icmpv6Packet::Mld(packet) => ctx.receive_mld_packet( |
| device.expect("MLD messages must come from a device"), |
| src_ip, |
| dst_ip, |
| packet, |
| ), |
| Icmpv6Packet::DestUnreachable(dest_unreachable) => receive_icmpv6_error( |
| ctx, |
| &dest_unreachable, |
| Icmpv6ErrorCode::DestUnreachable(dest_unreachable.code()), |
| ), |
| Icmpv6Packet::TimeExceeded(time_exceeded) => receive_icmpv6_error( |
| ctx, |
| &time_exceeded, |
| Icmpv6ErrorCode::TimeExceeded(time_exceeded.code()), |
| ), |
| Icmpv6Packet::ParameterProblem(parameter_problem) => receive_icmpv6_error( |
| ctx, |
| ¶meter_problem, |
| Icmpv6ErrorCode::ParameterProblem(parameter_problem.code()), |
| ), |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| /// 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< |
| B: BufferMut, |
| C: BufferIcmpv4Context<B>, |
| BB: ByteSlice, |
| M: IcmpMessage<Ipv4, BB, Body = OriginalPacket<BB>>, |
| >( |
| ctx: &mut C, |
| packet: &IcmpPacket<Ipv4, BB, M>, |
| err: Icmpv4ErrorCode, |
| ) { |
| packet.with_original_packet(|res| match res { |
| Ok(original_packet) => { |
| let dst_ip = try_unit!(SpecifiedAddr::new(original_packet.dst_ip()).ok_or_else(|| { |
| trace!("receive_icmpv4_error: Got ICMP error message whose original IPv4 packet contains an unspecified destination address; discarding"); |
| })); |
| IcmpContext::receive_icmp_error( |
| ctx, |
| 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< |
| B: BufferMut, |
| C: BufferIcmpv6Context<B>, |
| BB: ByteSlice, |
| M: IcmpMessage<Ipv6, BB, Body = OriginalPacket<BB>>, |
| >( |
| ctx: &mut C, |
| packet: &IcmpPacket<Ipv6, BB, M>, |
| err: Icmpv6ErrorCode, |
| ) { |
| packet.with_original_packet(|res| match res { |
| Ok(original_packet) => { |
| let dst_ip = try_unit!(SpecifiedAddr::new(original_packet.dst_ip()).ok_or_else(|| { |
| trace!("receive_icmpv6_error: Got ICMP error message whose original IPv6 packet contains an unspecified destination address; discarding"); |
| })); |
| let body = match original_packet.body() { |
| Ok(body) => body.into_inner(), |
| Err(UndefinedBodyBoundsError) => { |
| 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; |
| } |
| }; |
| IcmpContext::receive_icmp_error( |
| ctx, |
| SpecifiedAddr::new(original_packet.src_ip()), |
| dst_ip, |
| original_packet.next_header(), |
| body, |
| err, |
| ); |
| } |
| Err(_) => 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 |
| /// 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. `header_len` is the length of the header including |
| /// all options. |
| pub(crate) fn send_icmpv4_protocol_unreachable<B: BufferMut, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_protocol_unreachable"); |
| |
| send_icmpv4_dest_unreachable( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip.into_addr(), |
| Icmpv4DestUnreachableCode::DestProtocolUnreachable, |
| original_packet, |
| header_len, |
| ); |
| } |
| |
| /// 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv6_protocol_unreachable"); |
| |
| send_icmpv6_parameter_problem( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip.into_addr(), |
| 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 packet header. `header_len` is the length of the entire |
| /// header, including options. |
| pub(crate) fn send_icmpv4_port_unreachable<B: BufferMut, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_port_unreachable"); |
| |
| send_icmpv4_dest_unreachable( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip.into_addr(), |
| Icmpv4DestUnreachableCode::DestPortUnreachable, |
| original_packet, |
| header_len, |
| ); |
| } |
| |
| /// 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| original_packet: B, |
| ) { |
| ctx.increment_counter("send_icmpv6_port_unreachable"); |
| |
| send_icmpv6_dest_unreachable( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip.into_addr(), |
| 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, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| proto: IpProto, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_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( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| original_packet, |
| header_len, |
| ); |
| } |
| |
| /// 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: Ipv6Addr, |
| proto: IpProto, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv6_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( |
| ctx, |
| 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, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| proto: IpProto, |
| mut original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_ttl_expired"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // 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; |
| } |
| |
| // Per RFC 792, body contains entire IPv4 header + 64 bytes of original |
| // body. |
| original_packet.shrink_back_to(header_len + 64); |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| original_packet.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| Icmpv4TimeExceededCode::TtlExpired, |
| IcmpTimeExceeded::default(), |
| )) |
| }, |
| None, |
| false, |
| ) |
| ); |
| } |
| } |
| |
| /// 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: Ipv6Addr, |
| proto: IpProto, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv6_ttl_expired"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // 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; |
| } |
| |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| Icmpv6TimeExceededCode::HopLimitExceeded, |
| IcmpTimeExceeded::default(), |
| ); |
| |
| // 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.into()), |
| false, |
| ) |
| ); |
| } |
| } |
| |
| // TODO(joshlf): Test send_icmpv6_packet_too_big once we support dummy 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: Ipv6Addr, |
| proto: IpProto, |
| mtu: u32, |
| original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv6_packet_too_big"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // 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; |
| } |
| |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| IcmpUnusedCode, |
| Icmpv6PacketTooBig::new(mtu), |
| ); |
| |
| // Per RFC 4443, body contains as much of the original body |
| // as possible without exceeding IPv6 minimum MTU. |
| // |
| // The final IP packet must fit within the MTU, so we shrink |
| // the original packet to the MTU minus the IPv6 and ICMP |
| // header sizes. |
| TruncatingSerializer::new(original_packet, TruncateDirection::DiscardBack) |
| .encapsulate(icmp_builder) |
| }, |
| Some(Ipv6::MINIMUM_LINK_MTU.into()), |
| // Note, here we explicitly let `should_send_icmpv6_error` allow |
| // a multicast destination (link-layer or destination IP) as RFC |
| // 4443 Section 2.4.e explicitly allows sending an ICMP response |
| // if the original packet was sent to a multicast IP or link |
| // layer if the ICMP response message will be a Packet Too Big |
| // Message. |
| true, |
| ) |
| ); |
| } |
| } |
| |
| pub(crate) fn send_icmpv4_parameter_problem<B: BufferMut, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| code: Icmpv4ParameterProblemCode, |
| parameter_problem: Icmpv4ParameterProblem, |
| mut original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_parameter_problem"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // Per RFC 792, body contains entire IPv4 header + 64 bytes of original |
| // body. |
| original_packet.shrink_back_to(header_len + 64); |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| original_packet.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| code, |
| parameter_problem, |
| )) |
| }, |
| None, |
| false, |
| ) |
| ); |
| } else { |
| trace!("send_icmpv4_parameter_problem: Can't send ICMP error response to the unspecified address"); |
| } |
| } |
| |
| /// 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, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: Ipv6Addr, |
| code: Icmpv6ParameterProblemCode, |
| parameter_problem: Icmpv6ParameterProblem, |
| original_packet: B, |
| allow_multicast_dst: bool, |
| ) { |
| // Only allow the `allow_multicast_dst` 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_multicast_dst || code == Icmpv6ParameterProblemCode::UnrecognizedIpv6Option); |
| |
| ctx.increment_counter("send_icmpv6_parameter_problem"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| code, |
| parameter_problem, |
| ); |
| |
| // 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.into()), |
| allow_multicast_dst, |
| ) |
| ); |
| } |
| } |
| |
| fn send_icmpv4_dest_unreachable<B: BufferMut, C: BufferIcmpv4Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: Ipv4Addr, |
| code: Icmpv4DestUnreachableCode, |
| mut original_packet: B, |
| header_len: usize, |
| ) { |
| ctx.increment_counter("send_icmpv4_dest_unreachable"); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // Per RFC 792, body contains entire IPv4 header + 64 bytes of original |
| // body. |
| original_packet.shrink_back_to(header_len + 64); |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| original_packet.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| code, |
| IcmpDestUnreachable::default(), |
| )) |
| }, |
| None, |
| false, |
| ) |
| ); |
| } |
| } |
| |
| fn send_icmpv6_dest_unreachable<B: BufferMut, C: BufferIcmpv6Context<B>>( |
| ctx: &mut C, |
| device: C::DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: Ipv6Addr, |
| code: Icmpv6DestUnreachableCode, |
| original_packet: B, |
| ) { |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| // TODO(joshlf): Do something if send_icmp_error_message returns an |
| // error? |
| let _ = try_send_error!( |
| ctx, |
| ctx.send_icmp_error_message( |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| |local_ip| { |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| local_ip, |
| src_ip, |
| code, |
| IcmpDestUnreachable::default(), |
| ); |
| |
| // 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.into()), |
| false, |
| ) |
| ); |
| } |
| } |
| |
| /// 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`. |
| pub(crate) fn should_send_icmpv4_error( |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<Ipv4Addr>, |
| dst_ip: Ipv4Addr, |
| ) -> 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 non-initial fragment |
| // - 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? |
| !(dst_ip.is_multicast() |
| || dst_ip.is_global_broadcast() |
| || frame_dst.is_broadcast() |
| || src_ip.is_loopback() |
| || src_ip.is_global_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, `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`. |
| pub(crate) fn should_send_icmpv6_error( |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<Ipv6Addr>, |
| dst_ip: 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. |
| !((!allow_dst_multicast |
| && (dst_ip.is_multicast() || frame_dst.is_multicast() || frame_dst.is_broadcast())) |
| || src_ip.is_loopback() |
| || src_ip.is_multicast()) |
| } |
| |
| /// 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: IpProto, 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: IcmpIpExt, B: BufferMut, C: BufferIcmpContext<I, B>>( |
| ctx: &mut C, |
| src_ip: I::Addr, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| id: u16, |
| seq: u16, |
| body: B, |
| ) { |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| if let Some(conn) = ctx.get_state().conns.get_id_by_addr(&IcmpAddr { |
| local_addr: dst_ip, |
| remote_addr: src_ip, |
| icmp_id: id, |
| }) { |
| trace!("receive_icmp_echo_reply: Received echo reply for local socket"); |
| ctx.receive_icmp_echo_reply(IcmpConnId::new(conn), seq, body); |
| } else { |
| // TODO(fxb/47952): 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 |
| // `BufferIpTransportContext::receive_ip_packet` return the |
| // appropriate error. |
| trace!("receive_icmp_echo_reply: Received echo reply with no local socket"); |
| } |
| } else { |
| trace!("receive_icmp_echo_reply: Received echo reply with an unspecified source address"); |
| } |
| } |
| |
| /// Send an ICMPv4 echo request on an existing connection. |
| /// |
| /// # Panics |
| /// |
| /// `send_icmpv4_echo_request` panics if `conn` is not associated with an ICMPv4 |
| /// connection. |
| pub fn send_icmpv4_echo_request<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| conn: IcmpConnId<Ipv4>, |
| seq_num: u16, |
| body: B, |
| ) -> Result<(), SendError> { |
| send_icmp_echo_request_inner(ctx, conn, seq_num, body) |
| } |
| |
| /// Send an ICMPv6 echo request on an existing connection. |
| /// |
| /// # Panics |
| /// |
| /// `send_icmpv6_echo_request` panics if `conn` is not associated with an ICMPv6 |
| /// connection. |
| pub fn send_icmpv6_echo_request<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| conn: IcmpConnId<Ipv6>, |
| seq_num: u16, |
| body: B, |
| ) -> Result<(), SendError> { |
| send_icmp_echo_request_inner(ctx, conn, seq_num, body) |
| } |
| |
| fn send_icmp_echo_request_inner<I: IcmpIpExt, B: BufferMut, C: BufferIcmpContext<I, B>>( |
| ctx: &mut C, |
| conn: IcmpConnId<I>, |
| seq_num: u16, |
| body: B, |
| ) -> Result<(), SendError> |
| where |
| IcmpEchoRequest: for<'a> IcmpMessage<I, &'a [u8], Code = IcmpUnusedCode>, |
| { |
| // TODO(joshlf): Come up with a better approach to the lifetimes issues than |
| // cloning the entire socket. |
| let conn = ctx |
| .get_state_mut() |
| .conns |
| .get_conn_by_id(conn.0) |
| .expect("icmp::send_icmp_echo_request_inner: no such conn") |
| .clone(); |
| ctx.send_ip_packet( |
| &conn.ip, |
| body.encapsulate(IcmpPacketBuilder::<I, &[u8], _>::new( |
| conn.ip.local_ip().get(), |
| conn.ip.remote_ip().get(), |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(conn.icmp_id, seq_num), |
| )), |
| ) |
| .map_err(|(_body, err)| err) |
| } |
| |
| /// Creates a new ICMPv4 connection. |
| /// |
| /// Creates a new ICMPv4 connection with the provided parameters `local_addr`, |
| /// `remote_addr` and `icmp_id`, and returns its newly-allocated ID. If |
| /// `local_addr` is `None`, one will be chosen automatically. |
| /// |
| /// If a connection with the conflicting parameters already exists, the call |
| /// fails and returns an [`NetstackError`]. |
| pub fn new_icmpv4_connection<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| local_addr: Option<SpecifiedAddr<Ipv4Addr>>, |
| remote_addr: SpecifiedAddr<Ipv4Addr>, |
| icmp_id: u16, |
| ) -> Result<IcmpConnId<Ipv4>, SocketError> { |
| new_icmpv4_connection_inner(ctx, local_addr, remote_addr, icmp_id) |
| } |
| |
| // TODO(joshlf): Make this the external function (replacing the existing |
| // `new_icmpv4_connection`) once the ICMP context traits are part of the public |
| // API. |
| fn new_icmpv4_connection_inner<C: Icmpv4SocketContext>( |
| ctx: &mut C, |
| local_addr: Option<SpecifiedAddr<Ipv4Addr>>, |
| remote_addr: SpecifiedAddr<Ipv4Addr>, |
| icmp_id: u16, |
| ) -> Result<IcmpConnId<Ipv4>, SocketError> { |
| let ip = |
| ctx.new_ip_socket(local_addr, remote_addr, IpProto::Icmp, UnroutableBehavior::Close, None)?; |
| Ok(new_icmp_connection_inner(&mut ctx.get_state_mut().inner.conns, remote_addr, icmp_id, ip)?) |
| } |
| |
| /// Creates a new ICMPv4 connection. |
| /// |
| /// Creates a new ICMPv4 connection with the provided parameters `local_addr`, |
| /// `remote_addr` and `icmp_id`, and returns its newly-allocated ID. If |
| /// `local_addr` is `None`, one will be chosen automatically. |
| /// |
| /// If a connection with the conflicting parameters already exists, the call |
| /// fails and returns an [`NetstackError`]. |
| pub fn new_icmpv6_connection<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| local_addr: Option<SpecifiedAddr<Ipv6Addr>>, |
| remote_addr: SpecifiedAddr<Ipv6Addr>, |
| icmp_id: u16, |
| ) -> Result<IcmpConnId<Ipv6>, SocketError> { |
| new_icmpv6_connection_inner(ctx, local_addr, remote_addr, icmp_id) |
| } |
| |
| // TODO(joshlf): Make this the external function (replacing the existing |
| // `new_icmpv6_connection`) once the ICMP context traits are part of the public |
| // API. |
| fn new_icmpv6_connection_inner<C: Icmpv6SocketContext>( |
| ctx: &mut C, |
| local_addr: Option<SpecifiedAddr<Ipv6Addr>>, |
| remote_addr: SpecifiedAddr<Ipv6Addr>, |
| icmp_id: u16, |
| ) -> Result<IcmpConnId<Ipv6>, SocketError> { |
| let ip = ctx.new_ip_socket( |
| local_addr, |
| remote_addr, |
| IpProto::Icmpv6, |
| UnroutableBehavior::Close, |
| None, |
| )?; |
| Ok(new_icmp_connection_inner(&mut ctx.get_state_mut().inner.conns, remote_addr, icmp_id, ip)?) |
| } |
| |
| fn new_icmp_connection_inner<I: IcmpIpExt + IpExt, S: IpSocket<I>>( |
| conns: &mut ConnAddrMap<IcmpAddr<I::Addr>, IcmpConn<S>>, |
| remote_addr: SpecifiedAddr<I::Addr>, |
| icmp_id: u16, |
| ip: S, |
| ) -> Result<IcmpConnId<I>, ExistsError> { |
| let addr = IcmpAddr { local_addr: *ip.local_ip(), remote_addr, icmp_id }; |
| if conns.get_id_by_addr(&addr).is_some() { |
| return Err(ExistsError); |
| } |
| Ok(IcmpConnId::new(conns.insert(addr, IcmpConn { icmp_id, ip }))) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::fmt::Debug; |
| use std::num::NonZeroU16; |
| use std::time::Duration; |
| |
| use net_types::ip::{Ip, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use packet::{Buf, Serializer}; |
| use specialize_ip_macro::{ip_test, specialize_ip}; |
| |
| use super::*; |
| use crate::context::testutil::{DummyContext, DummyInstant}; |
| use crate::device::{set_routing_enabled, DeviceId, FrameDestination}; |
| use crate::ip::gmp::mld::MldPacketHandler; |
| use crate::ip::path_mtu::testutil::DummyPmtuState; |
| use crate::ip::socket::testutil::{DummyIpSocket, DummyIpSocketContext}; |
| use crate::ip::{ |
| receive_ipv4_packet, receive_ipv6_packet, DummyDeviceId, IpExt, IpPacketBuilder, |
| }; |
| use crate::testutil::{ |
| DummyEventDispatcher, DummyEventDispatcherBuilder, TestIpExt, DUMMY_CONFIG_V4, |
| DUMMY_CONFIG_V6, |
| }; |
| use crate::wire::icmp::{ |
| mld::MldPacket, IcmpEchoReply, IcmpEchoRequest, IcmpMessage, IcmpPacket, IcmpUnusedCode, |
| Icmpv4TimestampRequest, MessageBody, NdpPacket, |
| }; |
| use crate::wire::udp::UdpPacketBuilder; |
| use crate::StackStateBuilder; |
| |
| // |
| // 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::DUMMY_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_stack_state_builder` is invoked on the `StackStateBuilder` |
| /// before it is used to build the context. |
| /// |
| /// The state is initialized to `I::DUMMY_CONFIG` when testing. |
| #[allow(clippy::too_many_arguments)] |
| fn test_receive_ip_packet< |
| I: TestIpExt + IcmpIpExt, |
| C: PartialEq + Debug, |
| M: for<'a> IcmpMessage<I, &'a [u8], Code = C> + PartialEq + Debug, |
| F: FnOnce(&mut StackStateBuilder), |
| FF: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>), |
| >( |
| modify_stack_state_builder: F, |
| body: &mut [u8], |
| dst_ip: I::Addr, |
| ttl: u8, |
| proto: IpProto, |
| assert_counters: &[&str], |
| expect_message_code: Option<(M, C)>, |
| f: FF, |
| ) { |
| crate::testutil::set_logger_for_test(); |
| let buffer = Buf::new(body, ..) |
| .encapsulate(<I as IpExt>::PacketBuilder::new( |
| *I::DUMMY_CONFIG.remote_ip, |
| dst_ip, |
| ttl, |
| proto, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| let mut ctx = DummyEventDispatcherBuilder::from_config(I::DUMMY_CONFIG) |
| .build_with_modifications::<DummyEventDispatcher, _>(modify_stack_state_builder); |
| |
| let device = DeviceId::new_ethernet(0); |
| set_routing_enabled::<_, I>(&mut ctx, device, true); |
| match I::VERSION { |
| IpVersion::V4 => { |
| receive_ipv4_packet(&mut ctx, device, FrameDestination::Unicast, buffer) |
| } |
| IpVersion::V6 => { |
| receive_ipv6_packet(&mut ctx, device, FrameDestination::Unicast, buffer) |
| } |
| } |
| |
| for counter in assert_counters { |
| assert!(*ctx.state().test_counters.get(counter) > 0, "counter at zero: {}", counter); |
| } |
| |
| if let Some((expect_message, expect_code)) = expect_message_code { |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 1); |
| let (src_mac, dst_mac, src_ip, dst_ip, _, message, code) = |
| crate::testutil::parse_icmp_packet_in_ip_packet_in_ethernet_frame::<I, _, M, _>( |
| &ctx.dispatcher().frames_sent()[0].1, |
| f, |
| ) |
| .unwrap(); |
| |
| assert_eq!(src_mac, I::DUMMY_CONFIG.local_mac); |
| assert_eq!(dst_mac, I::DUMMY_CONFIG.remote_mac); |
| assert_eq!(src_ip, I::DUMMY_CONFIG.local_ip.get()); |
| assert_eq!(dst_ip, I::DUMMY_CONFIG.remote_ip.get()); |
| assert_eq!(message, expect_message); |
| assert_eq!(code, expect_code); |
| } else { |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 0); |
| } |
| } |
| |
| #[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. |
| |
| fn test<I: TestIpExt + IcmpIpExt>(assert_counters: &[&str]) |
| where |
| IcmpEchoRequest: for<'a> IcmpMessage<I, &'a [u8], Code = IcmpUnusedCode>, |
| IcmpEchoReply: for<'a> IcmpMessage< |
| I, |
| &'a [u8], |
| Code = IcmpUnusedCode, |
| Body = OriginalPacket<&'a [u8]>, |
| >, |
| { |
| 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, &[u8], _>::new( |
| I::DUMMY_CONFIG.remote_ip.get(), |
| I::DUMMY_CONFIG.local_ip.get(), |
| IcmpUnusedCode, |
| req, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| test_receive_ip_packet::<I, _, _, _, _>( |
| |_| {}, |
| buffer.as_mut(), |
| I::DUMMY_CONFIG.local_ip.get(), |
| 64, |
| I::ICMP_IP_PROTO, |
| assert_counters, |
| Some((req.reply(), IcmpUnusedCode)), |
| |packet| assert_eq!(packet.original_packet().bytes(), req_body), |
| ); |
| } |
| |
| test::<Ipv4>(&["<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::echo_request", "send_ipv4_packet"]); |
| test::<Ipv6>(&["<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet::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, &[u8], _>::new( |
| DUMMY_CONFIG_V4.remote_ip, |
| DUMMY_CONFIG_V4.local_ip, |
| IcmpUnusedCode, |
| req, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| test_receive_ip_packet::<Ipv4, _, _, _, _>( |
| |builder| { |
| builder.ipv4_builder().icmpv4_builder().send_timestamp_reply(true); |
| }, |
| buffer.as_mut(), |
| DUMMY_CONFIG_V4.local_ip.get(), |
| 64, |
| IpProto::Icmp, |
| &["<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::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 0..256 { |
| let proto = IpProto::from(proto as u8); |
| |
| const IPV4_RECOGNIZED_PROTOS: &[IpProto] = |
| &[IpProto::Icmp, IpProto::Igmp, IpProto::Udp]; |
| if !IPV4_RECOGNIZED_PROTOS.iter().any(|p| *p == proto) { |
| test_receive_ip_packet::<Ipv4, _, _, _, _>( |
| |_| {}, |
| &mut [0u8; 128], |
| DUMMY_CONFIG_V4.local_ip.get(), |
| 64, |
| proto, |
| &["send_icmpv4_protocol_unreachable", "send_icmp_error_message"], |
| Some(( |
| IcmpDestUnreachable::default(), |
| Icmpv4DestUnreachableCode::DestProtocolUnreachable, |
| )), |
| // ensure packet is truncated to the right length |
| |packet| assert_eq!(packet.original_packet().bytes().len(), 84), |
| ); |
| } |
| |
| // TODO(fxb/47953): 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. |
| if (&[IpProto::Igmp, IpProto::Tcp]).iter().any(|p| *p == proto) { |
| test_receive_ip_packet::<Ipv6, _, _, _, _>( |
| |_| {}, |
| &mut [0u8; 128], |
| DUMMY_CONFIG_V6.local_ip.get(), |
| 64, |
| proto, |
| &["send_icmpv6_protocol_unreachable", "send_icmp_error_message"], |
| Some(( |
| Icmpv6ParameterProblem::new(40), |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| )), |
| // ensure packet is truncated to the right length |
| |packet| assert_eq!(packet.original_packet().bytes().len(), 168), |
| ); |
| } |
| } |
| } |
| |
| #[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. |
| |
| fn test<I: TestIpExt + IcmpIpExt, C: PartialEq + Debug>( |
| code: C, |
| assert_counters: &[&str], |
| original_packet_len: usize, |
| ) where |
| IcmpDestUnreachable: |
| for<'a> IcmpMessage<I, &'a [u8], Code = C, Body = OriginalPacket<&'a [u8]>>, |
| { |
| let mut buffer = Buf::new(vec![0; 128], ..) |
| .encapsulate(UdpPacketBuilder::new( |
| I::DUMMY_CONFIG.remote_ip.get(), |
| I::DUMMY_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| { |
| builder.transport_builder().udp_builder().send_port_unreachable(true); |
| }, |
| buffer.as_mut(), |
| I::DUMMY_CONFIG.local_ip.get(), |
| 64, |
| IpProto::Udp, |
| 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 |
| |_| {}, |
| buffer.as_mut(), |
| I::DUMMY_CONFIG.local_ip.get(), |
| 64, |
| IpProto::Udp, |
| &[], |
| None, |
| |_| {}, |
| ); |
| } |
| |
| test::<Ipv4, _>( |
| Icmpv4DestUnreachableCode::DestPortUnreachable, |
| &["send_icmpv4_port_unreachable", "send_icmp_error_message"], |
| 84, |
| ); |
| test::<Ipv6, _>( |
| Icmpv6DestUnreachableCode::PortUnreachable, |
| &["send_icmpv6_port_unreachable", "send_icmp_error_message"], |
| 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, _, _, _, _>( |
| |sb| { |
| sb.ipv4_builder().forward(true); |
| }, |
| &mut [0u8; 128], |
| Ipv4Addr::new([1, 2, 3, 4]), |
| 64, |
| IpProto::Udp, |
| &["send_icmpv4_net_unreachable", "send_icmp_error_message"], |
| 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, _, _, _, _>( |
| |sb| { |
| sb.ipv6_builder().forward(true); |
| }, |
| &mut [0u8; 128], |
| Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]), |
| 64, |
| IpProto::Udp, |
| &["send_icmpv6_net_unreachable", "send_icmp_error_message"], |
| Some((IcmpDestUnreachable::default(), Icmpv6DestUnreachableCode::NoRoute)), |
| // ensure packet is truncated to the right length |
| |packet| assert_eq!(packet.original_packet().bytes().len(), 168), |
| ); |
| } |
| |
| #[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, _, _, _, _>( |
| |builder| { |
| builder.ipv4_builder().forward(true); |
| }, |
| &mut [0u8; 128], |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| 1, |
| IpProto::Udp, |
| &["send_icmpv4_ttl_expired", "send_icmp_error_message"], |
| 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, _, _, _, _>( |
| |builder| { |
| builder.ipv6_builder().forward(true); |
| }, |
| &mut [0u8; 128], |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| 1, |
| IpProto::Udp, |
| &["send_icmpv6_ttl_expired", "send_icmp_error_message"], |
| Some((IcmpTimeExceeded::default(), Icmpv6TimeExceededCode::HopLimitExceeded)), |
| // ensure packet is truncated to the right length |
| |packet| assert_eq!(packet.original_packet().bytes().len(), 168), |
| ); |
| } |
| |
| #[test] |
| fn test_should_send_icmpv4_error() { |
| let src_ip = DUMMY_CONFIG_V4.local_ip; |
| let dst_ip = DUMMY_CONFIG_V4.remote_ip.get(); |
| let frame_dst = FrameDestination::Unicast; |
| let multicast_ip_1 = Ipv4Addr::new([224, 0, 0, 1]); |
| let multicast_ip_2 = SpecifiedAddr::new(Ipv4Addr::new([224, 0, 0, 2])).unwrap(); |
| |
| // Should Send. |
| assert!(should_send_icmpv4_error(frame_dst, src_ip, dst_ip)); |
| |
| // Should not send because destined for IP broadcast addr |
| assert!(!should_send_icmpv4_error(frame_dst, src_ip, Ipv4::GLOBAL_BROADCAST_ADDRESS.get())); |
| |
| // Should not send because destined for multicast addr |
| assert!(!should_send_icmpv4_error(frame_dst, src_ip, multicast_ip_1)); |
| |
| // Should not send because Link Layer Broadcast. |
| assert!(!should_send_icmpv4_error(FrameDestination::Broadcast, src_ip, dst_ip)); |
| |
| // Should not send because from loopback addr |
| assert!(!should_send_icmpv4_error(frame_dst, Ipv4::LOOPBACK_ADDRESS, dst_ip)); |
| |
| // Should not send because from global broadcast addr |
| assert!(!should_send_icmpv4_error(frame_dst, Ipv4::GLOBAL_BROADCAST_ADDRESS, dst_ip)); |
| |
| // Should not send because from multicast addr |
| assert!(!should_send_icmpv4_error(frame_dst, multicast_ip_2, dst_ip)); |
| |
| // Should not send because from class E addr |
| assert!(!should_send_icmpv4_error( |
| frame_dst, |
| SpecifiedAddr::new(Ipv4Addr::new([240, 0, 0, 1])).unwrap(), |
| dst_ip |
| )); |
| } |
| |
| #[test] |
| fn test_should_send_icmpv6_error() { |
| let src_ip = DUMMY_CONFIG_V6.local_ip; |
| let dst_ip = DUMMY_CONFIG_V6.remote_ip.get(); |
| let frame_dst = FrameDestination::Unicast; |
| let multicast_ip_1 = Ipv6Addr::new([255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); |
| let multicast_ip_2 = |
| SpecifiedAddr::new(Ipv6Addr::new([255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2])) |
| .unwrap(); |
| |
| // Should Send. |
| assert!(should_send_icmpv6_error(frame_dst, src_ip, dst_ip, false)); |
| assert!(should_send_icmpv6_error(frame_dst, src_ip, dst_ip, true)); |
| |
| // Should not send because destined for multicast addr, |
| // unless exception applies |
| assert!(!should_send_icmpv6_error(frame_dst, src_ip, multicast_ip_1, false)); |
| assert!(should_send_icmpv6_error(frame_dst, src_ip, multicast_ip_1, true)); |
| |
| // Should not send because Link Layer Broadcast., |
| // unless exception applies |
| assert!(!should_send_icmpv6_error(FrameDestination::Broadcast, src_ip, dst_ip, false)); |
| assert!(should_send_icmpv6_error(FrameDestination::Broadcast, src_ip, dst_ip, true)); |
| |
| // Should not send because from loopback addr |
| assert!(!should_send_icmpv6_error(frame_dst, Ipv6::LOOPBACK_ADDRESS, dst_ip, false)); |
| assert!(!should_send_icmpv6_error(frame_dst, Ipv6::LOOPBACK_ADDRESS, dst_ip, true)); |
| |
| // Should not send because from multicast addr |
| assert!(!should_send_icmpv6_error(frame_dst, multicast_ip_2, dst_ip, false)); |
| assert!(!should_send_icmpv6_error(frame_dst, multicast_ip_2, dst_ip, true)); |
| |
| // Should not send becuase from multicast addr, |
| // even though dest multicast exception applies |
| assert!(!should_send_icmpv6_error( |
| FrameDestination::Broadcast, |
| multicast_ip_2, |
| dst_ip, |
| false |
| )); |
| assert!(!should_send_icmpv6_error( |
| FrameDestination::Broadcast, |
| multicast_ip_2, |
| dst_ip, |
| true |
| )); |
| assert!(!should_send_icmpv6_error(frame_dst, multicast_ip_2, multicast_ip_1, false)); |
| assert!(!should_send_icmpv6_error(frame_dst, multicast_ip_2, multicast_ip_1, true)); |
| } |
| |
| #[specialize_ip] |
| #[ip_test] |
| fn test_icmp_connections<I: Ip>() { |
| crate::testutil::set_logger_for_test(); |
| #[ipv4] |
| let recv_icmp_packet_name = |
| "<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet"; |
| #[ipv6] |
| let recv_icmp_packet_name = |
| "<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_packet"; |
| |
| let config = I::DUMMY_CONFIG; |
| let mut net = |
| crate::testutil::new_dummy_network_from_config("alice", "bob", config.clone()); |
| |
| let icmp_id = 13; |
| |
| #[ipv4] |
| let conn = new_icmpv4_connection( |
| net.context("alice"), |
| Some(config.local_ip), |
| config.remote_ip, |
| icmp_id, |
| ) |
| .unwrap(); |
| #[ipv6] |
| let conn = new_icmpv6_connection( |
| net.context("alice"), |
| Some(config.local_ip), |
| config.remote_ip, |
| icmp_id, |
| ) |
| .unwrap(); |
| |
| let echo_body = vec![1, 2, 3, 4]; |
| |
| #[ipv4] |
| send_icmpv4_echo_request(net.context("alice"), conn, 7, Buf::new(echo_body.clone(), ..)) |
| .unwrap(); |
| #[ipv6] |
| send_icmpv6_echo_request(net.context("alice"), conn, 7, Buf::new(echo_body.clone(), ..)) |
| .unwrap(); |
| |
| net.run_until_idle().unwrap(); |
| assert_eq!( |
| *net.context("bob") |
| .state() |
| .test_counters |
| .get(&format!("{}::echo_request", recv_icmp_packet_name)), |
| 1 |
| ); |
| assert_eq!( |
| *net.context("alice") |
| .state() |
| .test_counters |
| .get(&format!("{}::echo_reply", recv_icmp_packet_name)), |
| 1 |
| ); |
| let replies = net.context("alice").dispatcher_mut().take_icmp_replies(conn); |
| assert!(!replies.is_empty()); |
| let (seq, body) = &replies[0]; |
| assert_eq!(*seq, 7); |
| assert_eq!(*body, echo_body); |
| } |
| |
| // |
| // Tests that only require an ICMP stack. Unlike the preceding tests, these |
| // only test the ICMP stack and state, and mock everything else. We define |
| // the `DummyIcmpv4Context` and `DummyIcmpv6Context` types, which we wrap in |
| // a `DummyContext` to provide automatic implementations of a number of |
| // required traits. The rest we implement manually. |
| // |
| |
| // The arguments to `IcmpContext::send_icmp_reply`. |
| #[derive(Debug, PartialEq)] |
| struct SendIcmpReplyArgs<A: IpAddress> { |
| device: Option<DummyDeviceId>, |
| src_ip: SpecifiedAddr<A>, |
| dst_ip: SpecifiedAddr<A>, |
| body: Vec<u8>, |
| } |
| |
| // The arguments to `IcmpContext::send_icmp_error_message`. |
| #[derive(Debug, PartialEq)] |
| struct SendIcmpErrorMessageArgs<A: IpAddress> { |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<A>, |
| dst_ip: A, |
| body: Vec<u8>, |
| ip_mtu: Option<u32>, |
| allow_dst_multicast: bool, |
| // Whether `should_send_icmpv{4,6}_error` returned true. |
| sent: bool, |
| } |
| |
| // The arguments to `BufferIcmpSocketContext::receive_icmp_echo_reply`. |
| #[allow(unused)] // TODO(joshlf): Remove once we access these fields. |
| struct ReceiveIcmpEchoReply<I: Ip> { |
| conn: IcmpConnId<I>, |
| seq_num: u16, |
| data: Vec<u8>, |
| } |
| |
| // The arguments to `IcmpSocketContext::close_icmp_connection`. |
| #[allow(unused)] // TODO(joshlf): Remove once we access these fields. |
| struct CloseIcmpConnectionArgs<I: Ip> { |
| conn: IcmpConnId<I>, |
| err: NoRouteError, |
| } |
| |
| // The arguments to `IcmpSocketContext::receive_icmp_error`. |
| #[derive(Debug, PartialEq)] |
| struct ReceiveIcmpSocketErrorArgs<I: IcmpIpExt> { |
| conn: IcmpConnId<I>, |
| seq_num: u16, |
| err: I::ErrorCode, |
| } |
| |
| struct DummyIcmpContext<I: IcmpIpExt> { |
| // All calls to `IcmpContext::send_icmp_reply`. |
| send_icmp_reply: Vec<SendIcmpReplyArgs<I::Addr>>, |
| // All calls to `IcmpContext::send_icmp_error_message`. |
| send_icmp_error_message: Vec<SendIcmpErrorMessageArgs<I::Addr>>, |
| // We store calls to `IcmpContext::receive_icmp_error` AND calls to |
| // `IcmpSocketContext::receive_icmp_error`. Any call to |
| // `IcmpContext::receive_icmp_error` with an IP proto of ICMP(v4|v6) |
| // will be stored here and also passed to `receive_icmp_socket_error`, |
| // which will in turn call `IcmpSocketContext::receive_icmp_error`. |
| receive_icmp_echo_reply: Vec<ReceiveIcmpEchoReply<I>>, |
| receive_icmp_error: Vec<I::ErrorCode>, |
| receive_icmp_socket_error: Vec<ReceiveIcmpSocketErrorArgs<I>>, |
| close_icmp_connection: Vec<CloseIcmpConnectionArgs<I>>, |
| pmtu_state: DummyPmtuState<I::Addr>, |
| socket_ctx: DummyIpSocketContext<I>, |
| } |
| |
| impl Default for DummyIcmpContext<Ipv4> { |
| fn default() -> DummyIcmpContext<Ipv4> { |
| DummyIcmpContext::new(DummyIpSocketContext::new(DUMMY_CONFIG_V4.local_ip, true)) |
| } |
| } |
| |
| impl Default for DummyIcmpContext<Ipv6> { |
| fn default() -> DummyIcmpContext<Ipv6> { |
| DummyIcmpContext::new(DummyIpSocketContext::new(DUMMY_CONFIG_V6.local_ip, true)) |
| } |
| } |
| |
| impl<I: IcmpIpExt> DummyIcmpContext<I> { |
| fn new(socket_ctx: DummyIpSocketContext<I>) -> DummyIcmpContext<I> { |
| DummyIcmpContext { |
| send_icmp_reply: Vec::new(), |
| send_icmp_error_message: Vec::new(), |
| receive_icmp_echo_reply: Vec::new(), |
| receive_icmp_error: Vec::new(), |
| receive_icmp_socket_error: Vec::new(), |
| close_icmp_connection: Vec::new(), |
| pmtu_state: DummyPmtuState::default(), |
| socket_ctx, |
| } |
| } |
| } |
| |
| struct DummyIcmpv4Context { |
| inner: DummyIcmpContext<Ipv4>, |
| icmp_state: Icmpv4State<DummyInstant, DummyIpSocket<Ipv4Addr>>, |
| } |
| |
| struct DummyIcmpv6Context { |
| inner: DummyIcmpContext<Ipv6>, |
| icmp_state: Icmpv6State<DummyInstant, DummyIpSocket<Ipv6Addr>>, |
| } |
| |
| impl Default for DummyIcmpv4Context { |
| fn default() -> DummyIcmpv4Context { |
| DummyIcmpv4Context { |
| inner: DummyIcmpContext::default(), |
| icmp_state: Icmpv4StateBuilder::default().build(), |
| } |
| } |
| } |
| |
| impl Default for DummyIcmpv6Context { |
| fn default() -> DummyIcmpv6Context { |
| DummyIcmpv6Context { |
| inner: DummyIcmpContext::default(), |
| icmp_state: Icmpv6StateBuilder::default().build(), |
| } |
| } |
| } |
| |
| impl Icmpv4SocketContext for Dummyv4Context { |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> (&mut Icmpv4State<DummyInstant, DummyIpSocket<Ipv4Addr>>, &()) { |
| (&mut self.get_mut().icmp_state, &()) |
| } |
| } |
| |
| impl Icmpv6SocketContext for Dummyv6Context { |
| fn get_state_and_update_meta( |
| &mut self, |
| ) -> (&mut Icmpv6State<DummyInstant, DummyIpSocket<Ipv6Addr>>, &()) { |
| (&mut self.get_mut().icmp_state, &()) |
| } |
| } |
| |
| /// Implement a number of traits and methods for the `$inner` and `$outer` |
| /// context types. |
| macro_rules! impl_context_traits { |
| ($ip:ident, $inner:ident, $outer:ident, $state:ident, $should_send:expr) => { |
| type $outer = DummyContext<$inner>; |
| |
| impl $inner { |
| fn with_errors_per_second(errors_per_second: u64) -> $inner { |
| let mut ctx = $inner::default(); |
| ctx.icmp_state.inner.error_send_bucket = TokenBucket::new(errors_per_second); |
| ctx |
| } |
| } |
| |
| impl IpDeviceIdContext for $outer { |
| type DeviceId = DummyDeviceId; |
| } |
| |
| impl_pmtu_handler!($outer, $ip); |
| |
| impl AsMut<DummyPmtuState<<$ip as Ip>::Addr>> for $outer { |
| fn as_mut(&mut self) -> &mut DummyPmtuState<<$ip as Ip>::Addr> { |
| &mut self.get_mut().inner.pmtu_state |
| } |
| } |
| |
| impl AsRef<DummyIpSocketContext<$ip>> for $inner { |
| fn as_ref(&self) -> &DummyIpSocketContext<$ip> { |
| &self.inner.socket_ctx |
| } |
| } |
| |
| impl AsMut<DummyIpSocketContext<$ip>> for $inner { |
| fn as_mut(&mut self) -> &mut DummyIpSocketContext<$ip> { |
| &mut self.inner.socket_ctx |
| } |
| } |
| |
| impl StateContext<$state<DummyInstant, DummyIpSocket<<$ip as Ip>::Addr>>> for $outer { |
| fn get_state_with( |
| &self, |
| _id: (), |
| ) -> &$state<DummyInstant, DummyIpSocket<<$ip as Ip>::Addr>> { |
| &self.get_ref().icmp_state |
| } |
| |
| fn get_state_mut_with( |
| &mut self, |
| _id: (), |
| ) -> &mut $state<DummyInstant, DummyIpSocket<<$ip as Ip>::Addr>> { |
| &mut self.get_mut().icmp_state |
| } |
| } |
| |
| impl IcmpSocketContext<$ip> for $outer { |
| fn receive_icmp_error( |
| &mut self, |
| conn: IcmpConnId<$ip>, |
| seq_num: u16, |
| err: <$ip as IcmpIpExt>::ErrorCode, |
| ) { |
| self.increment_counter("IcmpSocketContext::receive_icmp_error"); |
| self.get_mut() |
| .inner |
| .receive_icmp_socket_error |
| .push(ReceiveIcmpSocketErrorArgs { conn, seq_num, err }); |
| } |
| |
| fn close_icmp_connection(&mut self, conn: IcmpConnId<$ip>, err: NoRouteError) { |
| self.get_mut() |
| .inner |
| .close_icmp_connection |
| .push(CloseIcmpConnectionArgs { conn, err }); |
| } |
| } |
| |
| impl<B: BufferMut> BufferIcmpSocketContext<$ip, B> for $outer { |
| fn receive_icmp_echo_reply( |
| &mut self, |
| conn: IcmpConnId<$ip>, |
| seq_num: u16, |
| data: B, |
| ) { |
| self.get_mut().inner.receive_icmp_echo_reply.push(ReceiveIcmpEchoReply { |
| conn, |
| seq_num, |
| data: data.as_ref().to_vec(), |
| }); |
| } |
| } |
| |
| impl IcmpContext<$ip> for $outer { |
| fn receive_icmp_error( |
| &mut self, |
| original_src_ip: Option<SpecifiedAddr<<$ip as Ip>::Addr>>, |
| original_dst_ip: SpecifiedAddr<<$ip as Ip>::Addr>, |
| original_proto: IpProto, |
| original_body: &[u8], |
| err: <$ip as IcmpIpExt>::ErrorCode, |
| ) { |
| self.increment_counter("IcmpContext::receive_icmp_error"); |
| self.get_mut().inner.receive_icmp_error.push(err); |
| if original_proto == <$ip as crate::wire::icmp::IcmpIpExt>::ICMP_IP_PROTO { |
| <IcmpIpTransportContext as IpTransportContext<$ip, _>>::receive_icmp_error( |
| self, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ); |
| } |
| } |
| } |
| |
| impl<B: BufferMut> BufferIcmpContext<$ip, B> for $outer { |
| // TODO(rheacock): remove the `allow(unreachable_code)` once this is implemented. |
| #[allow(unreachable_code)] |
| fn send_icmp_reply< |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<<$ip as Ip>::Addr>) -> S, |
| >( |
| &mut self, |
| device: Option<DummyDeviceId>, |
| src_ip: SpecifiedAddr<<$ip as Ip>::Addr>, |
| dst_ip: SpecifiedAddr<<$ip as Ip>::Addr>, |
| _get_body: F, |
| ) -> Result<(), S> { |
| self.get_mut().inner.send_icmp_reply.push(SendIcmpReplyArgs { |
| device, |
| src_ip, |
| dst_ip, |
| body: unimplemented!(), |
| }); |
| Ok(()) |
| } |
| |
| fn send_icmp_error_message< |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<<$ip as Ip>::Addr>) -> S, |
| >( |
| &mut self, |
| _device: DummyDeviceId, |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<<$ip as Ip>::Addr>, |
| dst_ip: <$ip as Ip>::Addr, |
| get_body: F, |
| ip_mtu: Option<u32>, |
| allow_dst_multicast: bool, |
| ) -> Result<(), S> { |
| self.increment_counter("IcmpContext::send_icmp_error_message"); |
| let sent = $should_send(frame_dst, src_ip, dst_ip, allow_dst_multicast); |
| self.get_mut().inner.send_icmp_error_message.push(SendIcmpErrorMessageArgs { |
| frame_dst, |
| src_ip, |
| dst_ip, |
| // Use `unwrap_or_else` like this rather than `unwrap` |
| // because the error variant of the return value of |
| // `serialize_vec_outer` doesn't implement `Debug`, |
| // which is required for `unwrap`. |
| body: get_body(SpecifiedAddr::new(dst_ip).unwrap()) |
| .serialize_vec_outer() |
| .unwrap_or_else(|_| panic!("failed to serialize body")) |
| .as_ref() |
| .to_vec(), |
| ip_mtu, |
| allow_dst_multicast, |
| sent, |
| }); |
| Ok(()) |
| } |
| } |
| }; |
| } |
| |
| impl_context_traits!(Ipv4, DummyIcmpv4Context, Dummyv4Context, Icmpv4State, |f, s, d, _| { |
| should_send_icmpv4_error(f, s, d) |
| }); |
| impl_context_traits!(Ipv6, DummyIcmpv6Context, Dummyv6Context, Icmpv6State, |f, s, d, a| { |
| should_send_icmpv6_error(f, s, d, a) |
| }); |
| |
| impl NdpPacketHandler<DummyDeviceId> for Dummyv6Context { |
| fn receive_ndp_packet<B: ByteSlice>( |
| &mut self, |
| _device: DummyDeviceId, |
| _src_ip: Ipv6Addr, |
| _dst_ip: SpecifiedAddr<Ipv6Addr>, |
| _packet: NdpPacket<B>, |
| ) { |
| unimplemented!() |
| } |
| } |
| |
| impl MldPacketHandler<(), DummyDeviceId> for Dummyv6Context { |
| fn receive_mld_packet<B: ByteSlice>( |
| &mut self, |
| _device: DummyDeviceId, |
| _src_ip: Ipv6Addr, |
| _dst_ip: SpecifiedAddr<Ipv6Addr>, |
| _packet: MldPacket<B>, |
| ) { |
| unimplemented!() |
| } |
| } |
| |
| #[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 `DUMMY_CONFIG_V4.remote_ip` to |
| /// `DUMMY_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 `IcmpConnId` |
| /// 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: for<'a> IcmpMessage<Ipv4, &'a [u8], Code = C> + Debug, |
| F: Fn(&Dummyv4Context), |
| >( |
| original_packet: &mut [u8], |
| code: C, |
| msg: M, |
| assert_counters: &[(&str, usize)], |
| f: F, |
| ) { |
| crate::testutil::set_logger_for_test(); |
| |
| let mut ctx = Dummyv4Context::default(); |
| // NOTE: This assertion is not a correctness requirement. It's just |
| // that the rest of this test assumes that the new connection has ID |
| // 0. If this assertion fails in the future, that isn't necessarily |
| // evidence of a bug; we may just have to update this test to |
| // accomodate whatever new ID allocation scheme is being used. |
| assert_eq!( |
| new_icmpv4_connection_inner( |
| &mut ctx, |
| Some(DUMMY_CONFIG_V4.local_ip), |
| DUMMY_CONFIG_V4.remote_ip, |
| ICMP_ID |
| ) |
| .unwrap(), |
| IcmpConnId::new(0) |
| ); |
| |
| <IcmpIpTransportContext as BufferIpTransportContext<Ipv4, _, _>>::receive_ip_packet( |
| &mut ctx, |
| Some(DummyDeviceId), |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| DUMMY_CONFIG_V4.local_ip, |
| Buf::new(original_packet, ..) |
| .encapsulate(IcmpPacketBuilder::new( |
| DUMMY_CONFIG_V4.remote_ip, |
| DUMMY_CONFIG_V4.local_ip, |
| code, |
| msg, |
| )) |
| .serialize_vec_outer() |
| .unwrap(), |
| ) |
| .unwrap(); |
| |
| for (ctr, count) in assert_counters { |
| assert_eq!(ctx.get_counter(ctr), *count, "wrong count for counter {}", 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 mock 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, &[u8], _>::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(ICMP_ID, SEQ_NUM), |
| )) |
| .encapsulate(<Ipv4 as IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| 64, |
| IpProto::Icmp, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::DestUnreachable( |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| err |
| }] |
| ); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4TimeExceededCode::TtlExpired, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| err |
| }] |
| ); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| Icmpv4ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::ParameterProblem( |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| 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 |
| // `IcmpSocketContext::receive_icmp_error`. |
| |
| let mut buffer = Buf::new(&mut [], ..) |
| .encapsulate(<Ipv4 as IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| 64, |
| IpProto::Icmp, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::DestUnreachable( |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4TimeExceededCode::TtlExpired, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| Icmpv4ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::ParameterProblem( |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| // 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 IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| 64, |
| IpProto::Udp, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::DestUnreachable( |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4TimeExceededCode::TtlExpired, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::TimeExceeded(Icmpv4TimeExceededCode::TtlExpired); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv4_error_helper( |
| buffer.as_mut(), |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| Icmpv4ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv4ErrorCode::ParameterProblem( |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| } |
| |
| #[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 `DUMMY_CONFIG_V6.remote_ip` to |
| /// `DUMMY_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 `IcmpConnId` |
| /// 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: for<'a> IcmpMessage<Ipv6, &'a [u8], Code = C> + Debug, |
| F: Fn(&Dummyv6Context), |
| >( |
| original_packet: &mut [u8], |
| code: C, |
| msg: M, |
| assert_counters: &[(&str, usize)], |
| f: F, |
| ) { |
| crate::testutil::set_logger_for_test(); |
| |
| let mut ctx = Dummyv6Context::default(); |
| // NOTE: This assertion is not a correctness requirement. It's just |
| // that the rest of this test assumes that the new connection has ID |
| // 0. If this assertion fails in the future, that isn't necessarily |
| // evidence of a bug; we may just have to update this test to |
| // accomodate whatever new ID allocation scheme is being used. |
| assert_eq!( |
| new_icmpv6_connection_inner( |
| &mut ctx, |
| Some(DUMMY_CONFIG_V6.local_ip), |
| DUMMY_CONFIG_V6.remote_ip, |
| ICMP_ID |
| ) |
| .unwrap(), |
| IcmpConnId::new(0) |
| ); |
| |
| <IcmpIpTransportContext as BufferIpTransportContext<Ipv6, _, _>>::receive_ip_packet( |
| &mut ctx, |
| Some(DummyDeviceId), |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| DUMMY_CONFIG_V6.local_ip, |
| Buf::new(original_packet, ..) |
| .encapsulate(IcmpPacketBuilder::new( |
| DUMMY_CONFIG_V6.remote_ip, |
| DUMMY_CONFIG_V6.local_ip, |
| code, |
| msg, |
| )) |
| .serialize_vec_outer() |
| .unwrap(), |
| ) |
| .unwrap(); |
| |
| for (ctr, count) in assert_counters { |
| assert_eq!(ctx.get_counter(ctr), *count, "wrong count for counter {}", ctr); |
| } |
| 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 mock 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, &[u8], _>::new( |
| DUMMY_CONFIG_V6.local_ip, |
| DUMMY_CONFIG_V6.remote_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(ICMP_ID, SEQ_NUM), |
| )) |
| .encapsulate(<Ipv6 as IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V6.local_ip, |
| DUMMY_CONFIG_V6.remote_ip, |
| 64, |
| IpProto::Icmpv6, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6DestUnreachableCode::NoRoute, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| err |
| }] |
| ); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6TimeExceededCode::HopLimitExceeded, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| err |
| }] |
| ); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| Icmpv6ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 1), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::ParameterProblem( |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!( |
| ctx.get_ref().inner.receive_icmp_socket_error, |
| [ReceiveIcmpSocketErrorArgs { |
| conn: IcmpConnId::new(0), |
| seq_num: SEQ_NUM, |
| 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 |
| // `IcmpSocketContext::receive_icmp_error`. |
| |
| let mut buffer = Buf::new(&mut [], ..) |
| .encapsulate(<Ipv6 as IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V6.local_ip, |
| DUMMY_CONFIG_V6.remote_ip, |
| 64, |
| IpProto::Icmpv6, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6DestUnreachableCode::NoRoute, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6TimeExceededCode::HopLimitExceeded, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| Icmpv6ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 1), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::ParameterProblem( |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| // 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 IpExt>::PacketBuilder::new( |
| DUMMY_CONFIG_V6.local_ip, |
| DUMMY_CONFIG_V6.remote_ip, |
| 64, |
| IpProto::Udp, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6DestUnreachableCode::NoRoute, |
| IcmpDestUnreachable::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::NoRoute); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6TimeExceededCode::HopLimitExceeded, |
| IcmpTimeExceeded::default(), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::TimeExceeded(Icmpv6TimeExceededCode::HopLimitExceeded); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| |
| test_receive_icmpv6_error_helper( |
| buffer.as_mut(), |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| Icmpv6ParameterProblem::new(0), |
| &[ |
| ("IcmpContext::receive_icmp_error", 1), |
| ("IcmpIpTransportContext::receive_icmp_error", 0), |
| ("IcmpSocketContext::receive_icmp_error", 0), |
| ], |
| |ctx| { |
| let err = Icmpv6ErrorCode::ParameterProblem( |
| Icmpv6ParameterProblemCode::UnrecognizedNextHeaderType, |
| ); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_error, [err]); |
| assert_eq!(ctx.get_ref().inner.receive_icmp_socket_error, []); |
| }, |
| ); |
| } |
| |
| #[test] |
| fn test_error_rate_limit() { |
| crate::testutil::set_logger_for_test(); |
| |
| /// Call `send_icmpv4_ttl_expired` with dummy values. |
| fn send_icmpv4_ttl_expired_helper(ctx: &mut Dummyv4Context) { |
| send_icmpv4_ttl_expired( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| DUMMY_CONFIG_V4.local_ip.get(), |
| IpProto::Udp, |
| Buf::new(&mut [], ..), |
| 0, |
| ); |
| } |
| |
| /// Call `send_icmpv4_parameter_problem` with dummy values. |
| fn send_icmpv4_parameter_problem_helper(ctx: &mut Dummyv4Context) { |
| send_icmpv4_parameter_problem( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| DUMMY_CONFIG_V4.local_ip.get(), |
| Icmpv4ParameterProblemCode::PointerIndicatesError, |
| Icmpv4ParameterProblem::new(0), |
| Buf::new(&mut [], ..), |
| 0, |
| ); |
| } |
| |
| /// Call `send_icmpv4_dest_unreachable` with dummy values. |
| fn send_icmpv4_dest_unreachable_helper(ctx: &mut Dummyv4Context) { |
| send_icmpv4_dest_unreachable( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V4.remote_ip.get(), |
| DUMMY_CONFIG_V4.local_ip.get(), |
| Icmpv4DestUnreachableCode::DestNetworkUnreachable, |
| Buf::new(&mut [], ..), |
| 0, |
| ); |
| } |
| |
| /// Call `send_icmpv6_ttl_expired` with dummy values. |
| fn send_icmpv6_ttl_expired_helper(ctx: &mut Dummyv6Context) { |
| send_icmpv6_ttl_expired( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| DUMMY_CONFIG_V6.local_ip.get(), |
| IpProto::Udp, |
| Buf::new(&mut [], ..), |
| 0, |
| ); |
| } |
| |
| /// Call `send_icmpv6_packet_too_big` with dummy values. |
| fn send_icmpv6_packet_too_big_helper(ctx: &mut Dummyv6Context) { |
| send_icmpv6_packet_too_big( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| DUMMY_CONFIG_V6.local_ip.get(), |
| IpProto::Udp, |
| 0, |
| Buf::new(&mut [], ..), |
| 0, |
| ); |
| } |
| |
| /// Call `send_icmpv6_parameter_problem` with dummy values. |
| fn send_icmpv6_parameter_problem_helper(ctx: &mut Dummyv6Context) { |
| send_icmpv6_parameter_problem( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| DUMMY_CONFIG_V6.local_ip.get(), |
| Icmpv6ParameterProblemCode::ErroneousHeaderField, |
| Icmpv6ParameterProblem::new(0), |
| Buf::new(&mut [], ..), |
| false, |
| ); |
| } |
| |
| /// Call `send_icmpv6_dest_unreachable` with dummy values. |
| fn send_icmpv6_dest_unreachable_helper(ctx: &mut Dummyv6Context) { |
| send_icmpv6_dest_unreachable( |
| ctx, |
| DummyDeviceId, |
| FrameDestination::Unicast, |
| DUMMY_CONFIG_V6.remote_ip.get(), |
| DUMMY_CONFIG_V6.local_ip.get(), |
| 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<C, W: Fn(u64) -> DummyContext<C>, S: Fn(&mut DummyContext<C>)>( |
| 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.get_counter("IcmpContext::send_icmp_error_message"), i as usize + 1); |
| } |
| |
| assert_eq!( |
| ctx.get_counter("IcmpContext::send_icmp_error_message"), |
| ERRORS_PER_SECOND as usize |
| ); |
| send(&mut ctx); |
| assert_eq!( |
| ctx.get_counter("IcmpContext::send_icmp_error_message"), |
| ERRORS_PER_SECOND as usize |
| ); |
| |
| // 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.get_counter("IcmpContext::send_icmp_error_message"), 0); |
| ctx.sleep_skip_timers(Duration::from_secs(1)); |
| send(&mut ctx); |
| assert_eq!(ctx.get_counter("IcmpContext::send_icmp_error_message"), 0); |
| ctx.sleep_skip_timers(Duration::from_secs(1)); |
| send(&mut ctx); |
| assert_eq!(ctx.get_counter("IcmpContext::send_icmp_error_message"), 0); |
| } |
| |
| fn with_errors_per_second_v4(errors_per_second: u64) -> Dummyv4Context { |
| Dummyv4Context::with_state(DummyIcmpv4Context::with_errors_per_second( |
| errors_per_second, |
| )) |
| } |
| run_test(with_errors_per_second_v4, send_icmpv4_ttl_expired_helper); |
| run_test(with_errors_per_second_v4, send_icmpv4_parameter_problem_helper); |
| run_test(with_errors_per_second_v4, send_icmpv4_dest_unreachable_helper); |
| |
| fn with_errors_per_second_v6(errors_per_second: u64) -> Dummyv6Context { |
| Dummyv6Context::with_state(DummyIcmpv6Context::with_errors_per_second( |
| errors_per_second, |
| )) |
| } |
| |
| run_test(with_errors_per_second_v6, send_icmpv6_ttl_expired_helper); |
| run_test(with_errors_per_second_v6, send_icmpv6_packet_too_big_helper); |
| run_test(with_errors_per_second_v6, send_icmpv6_parameter_problem_helper); |
| run_test(with_errors_per_second_v6, send_icmpv6_dest_unreachable_helper); |
| } |
| } |