blob: a1907d4facf8431d12efef714ede68ad809e81ba [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! The Internet Control Message Protocol (ICMP).
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, Serializer, TruncateDirection, TruncatingSerializer};
use specialize_ip_macro::specialize_ip_address;
use crate::context::{CounterContext, StateContext};
use crate::device::ndp::NdpPacketHandler;
use crate::device::FrameDestination;
use crate::error;
use crate::ip::mld::MldHandler;
use crate::ip::path_mtu::PmtuHandler;
use crate::ip::{IpDeviceIdContext, IpProto, IPV6_MIN_MTU};
use crate::transport::ConnAddrMap;
use crate::wire::icmp::{
peek_message_type, IcmpDestUnreachable, IcmpEchoRequest, IcmpIpExt, IcmpMessageType,
IcmpPacketBuilder, IcmpParseArgs, IcmpTimeExceeded, IcmpUnusedCode, Icmpv4DestUnreachableCode,
Icmpv4Packet, Icmpv4ParameterProblem, Icmpv4ParameterProblemCode, Icmpv4TimeExceededCode,
Icmpv6DestUnreachableCode, Icmpv6Packet, Icmpv6PacketTooBig, Icmpv6ParameterProblem,
Icmpv6ParameterProblemCode, Icmpv6TimeExceededCode, MessageBody,
};
use crate::{BufferDispatcher, Context, EventDispatcher, StackState};
/// A builder for ICMPv4 state.
#[derive(Copy, Clone)]
pub struct Icmpv4StateBuilder {
send_timestamp_reply: bool,
}
impl Default for Icmpv4StateBuilder {
fn default() -> Icmpv4StateBuilder {
Icmpv4StateBuilder { send_timestamp_reply: false }
}
}
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
}
pub(crate) fn build(self) -> Icmpv4State {
Icmpv4State {
conns: ConnAddrMap::default(),
send_timestamp_reply: self.send_timestamp_reply,
}
}
}
/// The state associated with the ICMPv4 protocol.
pub(crate) struct Icmpv4State {
conns: ConnAddrMap<IcmpAddr<Ipv4Addr>>,
send_timestamp_reply: bool,
}
// Used by `receive_icmp_echo_reply`.
impl AsRef<ConnAddrMap<IcmpAddr<Ipv4Addr>>> for Icmpv4State {
fn as_ref(&self) -> &ConnAddrMap<IcmpAddr<Ipv4Addr>> {
&self.conns
}
}
/// The state associated with the ICMPv6 protocol.
#[derive(Default)]
pub(crate) struct Icmpv6State {
conns: ConnAddrMap<IcmpAddr<Ipv6Addr>>,
}
// Used by `receive_icmp_echo_reply`.
impl AsRef<ConnAddrMap<IcmpAddr<Ipv6Addr>>> for Icmpv6State {
fn as_ref(&self) -> &ConnAddrMap<IcmpAddr<Ipv6Addr>> {
&self.conns
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct IcmpAddr<A: IpAddress> {
// TODO(brunodalbo) for now, ICMP connections are only bound to a remote
// address and an icmp_id. This will be improved with the addition of
// sockets_v2
remote_addr: SpecifiedAddr<A>,
icmp_id: u16,
}
/// 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(usize);
impl From<IcmpConnId> for usize {
fn from(id: IcmpConnId) -> usize {
id.0
}
}
/// An event dispatcher for the ICMP layer.
///
/// See the `EventDispatcher` trait in the crate root for more details.
pub trait IcmpEventDispatcher<B: BufferMut> {
/// Receive an ICMP echo reply.
fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId, 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 trait IcmpSocketContext<I: Ip, B: BufferMut> {
/// 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, seq_num: u16, data: B) {
log_unimplemented!((), "IcmpContext::receive_icmp_echo_reply: not implemented");
}
}
impl<I: Ip, B: BufferMut, D: BufferDispatcher<B>> IcmpSocketContext<I, B> for Context<D> {
fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId, seq_num: u16, data: B) {
self.dispatcher_mut().receive_icmp_echo_reply(conn, seq_num, data);
}
}
/// 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, B: BufferMut>:
IpDeviceIdContext + IcmpSocketContext<I, B> + CounterContext
{
/// 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 ICMP(v4).
pub(crate) trait Icmpv4Context<B: BufferMut>:
IcmpContext<Ipv4, B> + StateContext<(), Icmpv4State>
{
}
impl<B: BufferMut, C: IcmpContext<Ipv4, B> + StateContext<(), Icmpv4State>> Icmpv4Context<B> for C {}
/// The execution context for ICMPv6.
pub(crate) trait Icmpv6Context<B: BufferMut>:
IcmpContext<Ipv6, B> + StateContext<(), Icmpv6State>
{
}
impl<B: BufferMut, C: IcmpContext<Ipv6, B> + StateContext<(), Icmpv6State>> Icmpv6Context<B> for C {}
/// Receive an ICMP(v4) packet.
pub(crate) fn receive_icmpv4_packet<B: BufferMut, C: Icmpv4Context<B> + PmtuHandler<Ipv4>>(
ctx: &mut C,
device: Option<C::DeviceId>,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
mut buffer: B,
) {
trace!("receive_icmpv4_packet({}, {})", src_ip, dst_ip);
let packet = match buffer.parse_with::<_, Icmpv4Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) {
Ok(packet) => packet,
Err(err) => return, // TODO(joshlf): Do something else here?
};
match packet {
Icmpv4Packet::EchoRequest(echo_request) => {
ctx.increment_counter("receive_icmpv4_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?
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!("receive_icmpv4_packet: Received echo request with an unspecified source address");
}
}
Icmpv4Packet::EchoReply(echo_reply) => {
ctx.increment_counter("receive_icmpv4_packet::echo_reply");
trace!("receive_icmpv4_packet: Received an EchoReply message");
let id = echo_reply.message().id();
let seq = echo_reply.message().seq();
receive_icmp_echo_reply::<_, _, Icmpv4State, _>(ctx, src_ip, dst_ip, id, seq, buffer);
}
Icmpv4Packet::TimestampRequest(timestamp_request) => {
ctx.increment_counter("receive_icmpv4_packet::timestamp_request");
if let Some(src_ip) = SpecifiedAddr::new(src_ip) {
if StateContext::<(), Icmpv4State>::get_state(ctx, ()).send_timestamp_reply {
trace!("receive_icmpv4_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?
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!("receive_icmpv4_packet: Silently ignoring Timestamp Request message");
}
} else {
trace!("receive_icmpv4_packet: Received timestamp request with an unspecified source address");
}
}
Icmpv4Packet::TimestampReply(_) => {
// TODO(joshlf): Support sending Timestamp Requests and receiving
// Timestamp Replies?
debug!("receive_icmpv4_packet: Received unsolicited Timestamp Reply message");
}
Icmpv4Packet::DestUnreachable(dest_unreachable) => {
ctx.increment_counter("receive_icmpv4_packet::dest_unreachable");
trace!("receive_icmpv4_packet: Received a Destination Unreachable message");
if dest_unreachable.code() == Icmpv4DestUnreachableCode::FragmentationRequired {
let next_hop_mtu = dest_unreachable.message().next_hop_mtu();
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!("receive_icmpv4_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!("receive_icmpv4_packet: Original packet buf is too small to get original packet len so ignoring");
}
}
} else {
log_unimplemented!((), "ip::icmp::receive_icmpv4_packet: Not implemented for this ICMP destination unreachable code {:?}", dest_unreachable.code());
}
}
_ => log_unimplemented!(
(),
"ip::icmp::receive_icmpv4_packet: Not implemented for this packet type"
),
}
}
/// Receive an ICMPv6 packet.
pub(crate) fn receive_icmpv6_packet<
B: BufferMut,
C: Icmpv6Context<B> + PmtuHandler<Ipv6> + MldHandler + NdpPacketHandler,
>(
ctx: &mut C,
device: Option<C::DeviceId>,
src_ip: Ipv6Addr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
mut buffer: B,
) {
trace!("receive_icmpv6_packet({}, {})", src_ip, dst_ip);
let packet = match buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) {
Ok(packet) => packet,
Err(err) => return, // TODO(joshlf): Do something else here?
};
match packet {
Icmpv6Packet::EchoRequest(echo_request) => {
ctx.increment_counter("receive_icmpv6_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?
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!("receive_icmpv6_packet: Received echo request with an unspecified source address");
}
}
Icmpv6Packet::EchoReply(echo_reply) => {
ctx.increment_counter("receive_icmpv6_packet::echo_reply");
trace!("receive_icmpv6_packet: Received an EchoReply message");
let id = echo_reply.message().id();
let seq = echo_reply.message().seq();
receive_icmp_echo_reply::<_, _, Icmpv6State, _>(ctx, src_ip, dst_ip, id, seq, buffer);
}
Icmpv6Packet::RouterSolicitation(_)
| Icmpv6Packet::RouterAdvertisement(_)
| Icmpv6Packet::NeighborSolicitation(_)
| Icmpv6Packet::NeighborAdvertisement(_)
| Icmpv6Packet::Redirect(_) => {
ctx.receive_ndp_packet(device, src_ip, dst_ip, packet);
}
Icmpv6Packet::PacketTooBig(packet_too_big) => {
ctx.increment_counter("receive_icmpv6_packet::packet_too_big");
trace!("receive_icmpv6_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());
}
Icmpv6Packet::MulticastListenerQuery(_)
| Icmpv6Packet::MulticastListenerReport(_)
| Icmpv6Packet::MulticastListenerDone(_) => {
ctx.receive_mld_packet(
device.expect("MLD messages must come from a device"),
src_ip,
dst_ip,
packet,
);
}
_ => log_unimplemented!(
(),
"ip::icmp::receive_icmpv6_packet: Not implemented for this packet type"
),
}
}
/// 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: IcmpContext<Ipv4, B>>(
ctx: &mut C,
device: C::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv4Addr,
dst_ip: SpecifiedAddr<Ipv4Addr>,
proto: IpProto,
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: IcmpContext<Ipv6, B>>(
ctx: &mut C,
device: C::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv6Addr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
proto: IpProto,
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,
header_len,
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: IcmpContext<Ipv4, 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: IcmpContext<Ipv6, 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: IcmpContext<Ipv4, 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: IcmpContext<Ipv6, 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: IcmpContext<Ipv4, 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?
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: IcmpContext<Ipv6, B>>(
ctx: &mut C,
device: C::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
proto: IpProto,
mut 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?
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_MIN_MTU),
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: IcmpContext<Ipv6, 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;
}
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_MIN_MTU),
// 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: IcmpContext<Ipv4, 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?
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: IcmpContext<Ipv6, B>>(
ctx: &mut C,
device: C::DeviceId,
frame_dst: FrameDestination,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
code: Icmpv6ParameterProblemCode,
parameter_problem: Icmpv6ParameterProblem,
original_packet: B,
header_len: usize,
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?
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_MIN_MTU),
allow_multicast_dst,
);
}
}
fn send_icmpv4_dest_unreachable<B: BufferMut, C: IcmpContext<Ipv4, 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?
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: IcmpContext<Ipv6, 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?
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_MIN_MTU),
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::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,
S: AsRef<ConnAddrMap<IcmpAddr<I::Addr>>>,
C: IcmpContext<I, B> + StateContext<(), S>,
>(
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(()).as_ref().get_by_addr(&IcmpAddr { remote_addr: src_ip, icmp_id: id })
{
ctx.receive_icmp_echo_reply(IcmpConnId(conn), seq, body);
} else {
// NOTE(brunodalbo): 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.
}
} 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,
seq_num: u16,
body: B,
) {
let conns = get_conns::<_, Ipv4Addr>(ctx.state_mut());
let IcmpAddr { remote_addr, icmp_id } =
conns.get_by_conn(conn.0).expect("icmp::send_icmpv4_echo_request: no such conn").clone();
let req = IcmpEchoRequest::new(icmp_id, seq_num);
// TODO(brunodalbo) for now, ICMP connections are only bound to remote
// addresses, which allow us to just send the IP packet with whatever src
// ip we resolve the route to. With sockets v2, IcmpAddr will be bound to a
// local address and sending this request out will be done differently.
crate::ip::send_ipv4_packet(ctx, remote_addr, IpProto::Icmp, |a| {
body.encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new(
a.into_addr(),
remote_addr.get(),
IcmpUnusedCode,
req,
))
});
}
/// 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,
seq_num: u16,
body: B,
) {
let conns = get_conns::<_, Ipv6Addr>(ctx.state_mut());
let IcmpAddr { remote_addr, icmp_id } =
conns.get_by_conn(conn.0).expect("icmp::send_icmpv6_echo_request: no such conn").clone();
let req = IcmpEchoRequest::new(icmp_id, seq_num);
// TODO(brunodalbo) for now, ICMP connections are only bound to remote
// addresses, which allow us to just send the IP packet with whatever src
// ip we resolve the route to. With sockets v2, IcmpAddr will be bound to a
// local address and sending this request out will be done differently.
crate::ip::send_ipv6_packet(ctx, remote_addr, IpProto::Icmpv6, |a| {
body.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
a.into_addr(),
remote_addr.get(),
IcmpUnusedCode,
req,
))
});
}
/// Creates a new ICMP connection.
///
/// Creates a new ICMP connection with the provided parameters `local_addr`,
/// `remote_addr` and `icmp_id`, and returns its newly-allocated ID.
///
/// If a connection with the conflicting parameters already exists, the call
/// fails and returns an [`error::NetstackError`].
pub fn new_icmp_connection<D: EventDispatcher, A: IpAddress>(
ctx: &mut Context<D>,
local_addr: SpecifiedAddr<A>,
remote_addr: SpecifiedAddr<A>,
icmp_id: u16,
) -> Result<IcmpConnId, error::NetstackError> {
let conns = get_conns::<_, A>(ctx.state_mut());
// TODO(brunodalbo) ICMP connections are currently only bound to the remote
// address. Sockets API v2 will improve this.
let addr = IcmpAddr { remote_addr, icmp_id };
if conns.get_by_addr(&addr).is_some() {
return Err(error::NetstackError::Exists);
}
Ok(IcmpConnId(conns.insert(addr)))
}
#[specialize_ip_address]
fn get_conns<D: EventDispatcher, A: IpAddress>(
state: &mut StackState<D>,
) -> &mut ConnAddrMap<IcmpAddr<A>> {
#[ipv4addr]
return &mut state.ipv4.icmp.conns;
#[ipv6addr]
return &mut state.ipv6.icmp.conns;
}
#[cfg(test)]
mod tests {
use std::fmt::Debug;
#[cfg(feature = "udp-icmp-port-unreachable")]
use std::num::NonZeroU16;
use net_types::ip::{Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
use packet::{Buf, Serializer};
use specialize_ip_macro::specialize_ip;
use super::*;
use crate::device::{set_routing_enabled, DeviceId, FrameDestination};
use crate::ip::{receive_ipv4_packet, IpExt};
use crate::testutil::{
DummyEventDispatcher, DummyEventDispatcherBuilder, DUMMY_CONFIG_V4, DUMMY_CONFIG_V6,
};
use crate::wire::icmp::{
IcmpEchoRequest, IcmpMessage, IcmpPacket, IcmpUnusedCode, Icmpv4TimestampRequest,
MessageBody,
};
#[cfg(feature = "udp-icmp-port-unreachable")]
use crate::wire::udp::UdpPacketBuilder;
/// Test that receiving a particular IP packet results in a particular ICMP
/// response.
///
/// Test that receiving an IP packet from remote host
/// `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, and exactly one ICMP packet being sent in response with
/// `expect_message` and `expect_code`.
///
/// The state is initialized to `testutil::DUMMY_CONFIG` when testing.
#[allow(clippy::too_many_arguments)]
fn test_receive_ip_packet<
C: PartialEq + Debug,
M: for<'a> IcmpMessage<Ipv4, &'a [u8], Code = C> + PartialEq + Debug,
F: for<'a> Fn(&IcmpPacket<Ipv4, &'a [u8], M>),
>(
body: &mut [u8],
dst_ip: Ipv4Addr,
ttl: u8,
proto: IpProto,
assert_counters: &[&str],
expect_message: M,
expect_code: C,
f: F,
) {
crate::testutil::set_logger_for_test();
let buffer = Buf::new(body, ..)
.encapsulate(<Ipv4 as IpExt>::PacketBuilder::new(
DUMMY_CONFIG_V4.remote_ip,
dst_ip,
ttl,
proto,
))
.serialize_vec_outer()
.unwrap();
let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V4)
.build::<DummyEventDispatcher>();
// currently only used by test_receive_timestamp
ctx.state_mut().ipv4.icmp.send_timestamp_reply = true;
let device = DeviceId::new_ethernet(0);
// currently only used by test_ttl_exceeded
ctx.state_mut().ipv4.inner.forward = true;
set_routing_enabled::<_, Ipv4>(&mut ctx, device, true);
receive_ipv4_packet(&mut ctx, device, FrameDestination::Unicast, buffer);
for counter in assert_counters {
assert!(*ctx.state().test_counters.get(counter) > 0, "counter at zero: {}", counter);
}
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::<Ipv4, _, M, _>(
&ctx.dispatcher().frames_sent()[0].1,
f,
)
.unwrap();
assert_eq!(src_mac, DUMMY_CONFIG_V4.local_mac);
assert_eq!(dst_mac, DUMMY_CONFIG_V4.remote_mac);
assert_eq!(src_ip, DUMMY_CONFIG_V4.local_ip.get());
assert_eq!(dst_ip, DUMMY_CONFIG_V4.remote_ip.get());
assert_eq!(message, expect_message);
assert_eq!(code, expect_code);
}
#[test]
fn test_receive_echo() {
crate::testutil::set_logger_for_test();
let req = IcmpEchoRequest::new(0, 0);
let req_body = &[1, 2, 3, 4];
let mut buffer = Buf::new(req_body.to_vec(), ..)
.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(
buffer.as_mut(),
DUMMY_CONFIG_V4.local_ip.get(),
64,
IpProto::Icmp,
&["receive_icmpv4_packet::echo_request", "send_ipv4_packet"],
req.reply(),
IcmpUnusedCode,
|packet| assert_eq!(packet.original_packet().bytes(), req_body),
);
}
#[test]
fn test_receive_timestamp() {
crate::testutil::set_logger_for_test();
let req = Icmpv4TimestampRequest::new(1, 2, 3);
let mut buffer = Buf::new(vec![], ..)
.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(
buffer.as_mut(),
DUMMY_CONFIG_V4.local_ip.get(),
64,
IpProto::Icmp,
&["receive_icmpv4_packet::timestamp_request", "send_ipv4_packet"],
req.reply(0x80000000, 0x80000000),
IcmpUnusedCode,
|_| {},
);
}
#[test]
fn test_protocol_unreachable() {
// Receive an IP packet for an unreachable protocol (255). Check to make
// sure that we respond with the appropriate ICMP message.
//
// TODO(joshlf): Also perform the check for IPv6 once we support dummy
// IPv6 networks.
test_receive_ip_packet(
&mut [0u8; 128],
DUMMY_CONFIG_V4.local_ip.get(),
64,
IpProto::Other(255),
&["send_icmpv4_protocol_unreachable", "send_icmp_error_message"],
IcmpDestUnreachable::default(),
Icmpv4DestUnreachableCode::DestProtocolUnreachable,
// ensure packet is truncated to the right length
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
}
#[test]
#[cfg(feature = "udp-icmp-port-unreachable")]
fn test_port_unreachable() {
// TODO(joshlf): Use TCP in addition to UDP since UDP only works with
// the udp-icmp-port-unreachable feature enabled.
// Receive an IP packet for an unreachable UDP port (1234). Check to
// make sure that we respond with the appropriate ICMP message.
//
// TODO(joshlf):
// - Also perform the check for IPv6 once we support dummy IPv6
// networks.
// - Perform the same check for TCP once the logic is implemented
let mut buf = [0u8; 128];
let mut buffer = Buf::new(&mut buf[..], ..)
.encapsulate(UdpPacketBuilder::new(
DUMMY_CONFIG_V4.remote_ip.get(),
DUMMY_CONFIG_V4.local_ip.get(),
None,
NonZeroU16::new(1234).unwrap(),
))
.serialize_vec_outer();
test_receive_ip_packet(
buffer.as_mut(),
DUMMY_CONFIG_V4.local_ip,
64,
IpProto::Udp,
&["send_icmpv4_port_unreachable", "send_icmp_error_message"],
IcmpDestUnreachable::default(),
Icmpv4DestUnreachableCode::DestPortUnreachable,
// ensure packet is truncated to the right length
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
}
#[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.
//
// TODO(joshlf): Also perform the check for IPv6 once we support dummy
// IPv6 networks.
test_receive_ip_packet(
&mut [0u8; 128],
Ipv4Addr::new([1, 2, 3, 4]),
64,
IpProto::Udp,
&["send_icmpv4_net_unreachable", "send_icmp_error_message"],
IcmpDestUnreachable::default(),
Icmpv4DestUnreachableCode::DestNetworkUnreachable,
// ensure packet is truncated to the right length
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
}
#[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.
//
// TODO(joshlf): Also perform the check for IPv6 once we support dummy
// IPv6 networks.
test_receive_ip_packet(
&mut [0u8; 128],
DUMMY_CONFIG_V4.remote_ip.get(),
1,
IpProto::Udp,
&["send_icmpv4_ttl_expired", "send_icmp_error_message"],
IcmpTimeExceeded::default(),
Icmpv4TimeExceededCode::TtlExpired,
// ensure packet is truncated to the right length
|packet| assert_eq!(packet.original_packet().bytes().len(), 84),
);
}
#[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]
fn test_icmp_connections<I: Ip>(recv_icmp_packet_name: &str) {
crate::testutil::set_logger_for_test();
let config = crate::testutil::get_dummy_config::<I::Addr>();
let mut net =
crate::testutil::new_dummy_network_from_config("alice", "bob", config.clone());
let icmp_id = 13;
let conn =
new_icmp_connection(net.context("alice"), 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(), ..));
#[ipv6]
send_icmpv6_echo_request(net.context("alice"), conn, 7, Buf::new(echo_body.clone(), ..));
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);
}
#[test]
fn test_icmp_connections_v4() {
test_icmp_connections::<Ipv4>("receive_icmpv4_packet");
}
#[test]
fn test_icmp_connections_v6() {
test_icmp_connections::<Ipv6>("receive_icmpv6_packet");
}
}