blob: 79d61ae1316664067f8d575602e485b892f6a1d0 [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.
//! Parsing and serialization of Internet Control Message Protocol (ICMP) packets.
#[macro_use]
mod macros;
mod common;
mod icmpv4;
mod icmpv6;
mod ndp;
#[cfg(test)]
mod testdata;
pub use self::common::*;
pub use self::icmpv4::*;
pub use self::icmpv6::*;
use std::cmp;
use std::convert::TryFrom;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use byteorder::{ByteOrder, NetworkEndian};
use packet::{BufferView, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer};
use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
use crate::error::ParseError;
use crate::ip::{Ip, IpAddr, IpProto, Ipv4, Ipv6};
use crate::wire::ipv4;
use crate::wire::util::{fits_in_u32, Checksum, OptionImpl, Options};
// Header has the same memory layout (thanks to repr(C, packed)) as an ICMP
// header. Thus, we can simply reinterpret the bytes of the ICMP header as a
// Header and then safely access its fields.
// Note the following caveats:
// - We cannot make any guarantees about the alignment of an instance of this
// struct in memory or of any of its fields. This is true both because
// repr(packed) removes the padding that would be used to ensure the alignment
// of individual fields, but also because we are given no guarantees about
// where within a given memory buffer a particular packet (and thus its
// header) will be located.
// - Individual fields are all either u8 or [u8; N] rather than u16, u32, etc.
// This is for two reasons:
// - u16 and larger have larger-than-1 alignments, which are forbidden as
// described above
// - We are not guaranteed that the local platform has the same endianness as
// network byte order (big endian), so simply treating a sequence of bytes
// as a u16 or other multi-byte number would not necessarily be correct.
// Instead, we use the NetworkEndian type and its reader and writer methods
// to correctly access these fields.
#[derive(Default, Debug)]
#[repr(C, packed)]
struct Header {
msg_type: u8,
code: u8,
checksum: [u8; 2],
/* NOTE: The "Rest of Header" field is stored in message types rather than
* in the Header. This helps consolidate how callers access data about the
* packet, and is consistent with ICMPv6, which treats the field as part of
* messages rather than the header. */
}
unsafe impl FromBytes for Header {}
unsafe impl AsBytes for Header {}
unsafe impl Unaligned for Header {}
impl Header {
fn set_msg_type<T: Into<u8>>(&mut self, msg_type: T) {
self.msg_type = msg_type.into();
}
fn checksum(&self) -> u16 {
NetworkEndian::read_u16(&self.checksum)
}
fn set_checksum(&mut self, checksum: u16) {
NetworkEndian::write_u16(&mut self.checksum[..], checksum);
}
}
/// Peek at an ICMP header to see what message type is present.
///
/// Since `IcmpPacket` is statically typed with the message type expected, this
/// type must be known ahead of time before calling `parse`. If multiple
/// different types are valid in a given parsing context, and so the caller
/// cannot know ahead of time which type to use, `peek_message_type` can be used
/// to peek at the header first to figure out which static type should be used
/// in a subsequent call to `parse`.
///
/// Note that `peek_message_type` only inspects certain fields in the header,
/// and so `peek_message_type` succeeding does not guarantee that a subsequent
/// call to `parse` will also succeed.
pub fn peek_message_type<MessageType: TryFrom<u8>>(
bytes: &[u8],
) -> Result<MessageType, ParseError> {
let (header, _) = LayoutVerified::<_, Header>::new_unaligned_from_prefix(bytes).ok_or_else(
debug_err_fn!(ParseError::Format, "too few bytes for header"),
)?;
MessageType::try_from(header.msg_type).or_else(|_| {
Err(debug_err!(
ParseError::NotSupported,
"unrecognized message type: {:x}",
header.msg_type,
))
})
}
/// An extension trait adding ICMP-related functionality to `Ipv4` and `Ipv6`.
pub trait IcmpIpExt: Ip {
/// The type of ICMP messages.
///
/// For `Ipv4`, this is `icmpv4::MessageType`, and for `Ipv6`, this is
/// `icmpv6::MessageType`.
type IcmpMessageType: Into<u8> + Copy;
/// Compute the length of the header of the packet prefix stored in `bytes`.
///
/// Given the prefix of a packet stored in `bytes`, compute the length of
/// the header of that packet, or `bytes.len()` if `bytes` does not contain
/// the entire header. If the version is IPv6, the returned length should
/// include all extension headers.
fn header_len(bytes: &[u8]) -> usize;
}
// A default implementation for any I: Ip. This is to convince the Rust compiler
// that, given an I: Ip, it's guaranteed to implement IcmpIpExt. We humans know
// that Ipv4 and Ipv6 are the only types implementing Ip and so, since we
// implement IcmpIpExt for both of these types, this is fine. The compiler isn't
// so smart. This implementation should never actually be used. See the
// specialize_ip! and specialize_ip_addr! macros for other examples of this
// pattern.
impl<I: Ip> IcmpIpExt for I {
default type IcmpMessageType = !;
default fn header_len(bytes: &[u8]) -> usize {
unreachable!()
}
}
impl IcmpIpExt for Ipv4 {
type IcmpMessageType = icmpv4::MessageType;
fn header_len(bytes: &[u8]) -> usize {
if bytes.len() < ipv4::MIN_HEADER_BYTES {
return bytes.len();
}
let (header_prefix, _) =
LayoutVerified::<_, ipv4::HeaderPrefix>::new_unaligned_from_prefix(bytes).unwrap();
cmp::min(header_prefix.ihl() as usize * 4, bytes.len())
}
}
impl IcmpIpExt for Ipv6 {
type IcmpMessageType = icmpv6::MessageType;
fn header_len(bytes: &[u8]) -> usize {
// NOTE: We panic here rather than doing log_unimplemented! because
// there's no sane default value for this function. If it's called, it
// doesn't make sense for the program to continue executing; if we did,
// it would cause bugs in the caller.
unimplemented!()
}
}
// TODO(joshlf): Once we have generic associated types, refactor this so that we
// don't have to bind B ahead of time. Removing that requirement would make some
// APIs (in particular, IcmpPacketBuilder) simpler by removing the B parameter
// from them as well.
/// `MessageBody` represents the parsed body of the ICMP packet.
///
/// - For messages that expect no body, the `MessageBody` is of type `()`.
/// - For NDP messages, the `MessageBody` is of the type `ndp::Options`.
/// - For all other messages, the `MessageBody` will be of the type
/// `OriginalPacket`, which is a thin wrapper around `B`.
pub trait MessageBody<B>: Sized {
const EXPECTS_BODY: bool = true;
/// Parse the MessageBody from the provided bytes.
fn parse(bytes: B) -> Result<Self, ParseError>
where
B: ByteSlice;
/// The length of the underlying buffer.
fn len(&self) -> usize
where
B: ByteSlice;
/// Return the underlying bytes.
fn bytes(&self) -> &[u8]
where
B: Deref<Target = [u8]>;
}
impl<B> MessageBody<B> for () {
const EXPECTS_BODY: bool = false;
fn parse(bytes: B) -> Result<(), ParseError>
where
B: ByteSlice,
{
if !bytes.is_empty() {
return debug_err!(Err(ParseError::Format), "unexpected message body");
}
Ok(())
}
fn len(&self) -> usize {
0
}
fn bytes(&self) -> &[u8] {
&[]
}
}
/// A thin wrapper around B which implements `MessageBody`.
#[derive(Debug)]
pub struct OriginalPacket<B>(B);
impl<B: Deref<Target = [u8]>> OriginalPacket<B> {
pub fn body<I: IcmpIpExt>(&self) -> &[u8] {
let header_len = I::header_len(&self.0);
debug_assert!(header_len <= self.0.len());
debug_assert!(I::VERSION.is_v6() || self.0.len() - header_len == 8);
&self.0[header_len..]
}
}
impl<B> MessageBody<B> for OriginalPacket<B> {
fn parse(bytes: B) -> Result<OriginalPacket<B>, ParseError> {
return Ok(OriginalPacket(bytes));
}
fn len(&self) -> usize
where
B: ByteSlice,
{
self.0.len()
}
fn bytes(&self) -> &[u8]
where
B: Deref<Target = [u8]>,
{
&self.0
}
}
impl<B, O: for<'a> OptionImpl<'a>> MessageBody<B> for Options<B, O> {
fn parse(bytes: B) -> Result<Options<B, O>, ParseError>
where
B: ByteSlice,
{
Self::parse(bytes).map_err(|e| debug_err!(ParseError::Format, "unable to parse options"))
}
fn len(&self) -> usize
where
B: ByteSlice,
{
self.bytes().len()
}
fn bytes(&self) -> &[u8]
where
B: Deref<Target = [u8]>,
{
self.bytes()
}
}
/// An ICMP message.
pub trait IcmpMessage<I: IcmpIpExt, B>: Sized + Copy + FromBytes + AsBytes + Unaligned {
/// The type of codes used with this message.
///
/// The ICMP header includes an 8-bit "code" field. For a given message
/// type, different values of this field carry different meanings. Not all
/// code values are used - some may be invalid. This type representents a
/// parsed code. For example, for TODO, it is the TODO type.
type Code: Into<u8> + Copy;
type Body: MessageBody<B>;
/// The type corresponding to this message type.
///
/// The value of the "type" field in the ICMP header corresponding to
/// messages of this type.
const TYPE: I::IcmpMessageType;
/// Parse a `Code` from an 8-bit number.
///
/// Parse a `Code` from the 8-bit "code" field in the ICMP header. Not all
/// values for this field are valid. If an invalid value is passed,
/// `code_from_u8` returns `None`.
fn code_from_u8(code: u8) -> Option<Self::Code>;
}
/// An ICMP packet.
///
/// An `IcmpPacket` shares its underlying memory with the byte slice it was
/// parsed from, meaning that no copying or extra allocation is necessary.
pub struct IcmpPacket<I: IcmpIpExt, B, M: IcmpMessage<I, B>> {
header: LayoutVerified<B, Header>,
message: LayoutVerified<B, M>,
message_body: M::Body,
_marker: PhantomData<I>,
}
impl<
I: IcmpIpExt,
B: ByteSlice,
MB: fmt::Debug + MessageBody<B>,
M: IcmpMessage<I, B, Body = MB> + fmt::Debug,
> fmt::Debug for IcmpPacket<I, B, M>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IcmpPacket")
.field("header", &self.header)
.field("message", &self.message)
.field("message_body", &self.message_body)
.finish()
}
}
/// Arguments required to parse an ICMP packet.
pub struct IcmpParseArgs<A: IpAddr> {
src_ip: A,
dst_ip: A,
}
impl<A: IpAddr> IcmpParseArgs<A> {
/// Construct a new `IcmpParseArgs`.
pub fn new(src_ip: A, dst_ip: A) -> IcmpParseArgs<A> {
IcmpParseArgs { src_ip, dst_ip }
}
}
impl<B: ByteSlice, I: IcmpIpExt, M: IcmpMessage<I, B>> ParsablePacket<B, IcmpParseArgs<I::Addr>>
for IcmpPacket<I, B, M>
{
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
let header_len = self.header.bytes().len() + self.message.bytes().len();
ParseMetadata::from_packet(header_len, self.message_body.len(), 0)
}
fn parse<BV: BufferView<B>>(
mut buffer: BV, args: IcmpParseArgs<I::Addr>,
) -> Result<Self, ParseError> {
let header = buffer.take_obj_front::<Header>().ok_or_else(debug_err_fn!(
ParseError::Format,
"too few bytes for header"
))?;
let message = buffer.take_obj_front::<M>().ok_or_else(debug_err_fn!(
ParseError::Format,
"too few bytes for packet"
))?;
let message_body = buffer.into_rest();
if !M::Body::EXPECTS_BODY && !message_body.is_empty() {
return debug_err!(Err(ParseError::Format), "unexpected message body");
}
if header.msg_type != M::TYPE.into() {
return debug_err!(Err(ParseError::NotExpected), "unexpected message type");
}
M::code_from_u8(header.code).ok_or_else(debug_err_fn!(
ParseError::Format,
"unrecognized code: {}",
header.code
))?;
if header.checksum()
!= Self::compute_checksum(
&header,
message.bytes(),
&message_body,
args.src_ip,
args.dst_ip,
)
.ok_or_else(debug_err_fn!(ParseError::Format, "packet too large"))?
{
return debug_err!(Err(ParseError::Checksum), "invalid checksum");
}
let message_body = M::Body::parse(message_body)?;
Ok(IcmpPacket {
header,
message,
message_body,
_marker: PhantomData,
})
}
}
impl<I: IcmpIpExt, B: ByteSlice, M: IcmpMessage<I, B>> IcmpPacket<I, B, M> {
/// Get the ICMP message.
pub fn message(&self) -> &M {
&self.message
}
/// Get the ICMP message code.
///
/// The code provides extra details about the message. Each message type has
/// its own set of codes that are allowed.
pub fn code(&self) -> M::Code {
// infallible since it was validated in parse
M::code_from_u8(self.header.code).unwrap()
}
/// Construct a builder with the same contents as this packet.
pub fn builder(&self, src_ip: I::Addr, dst_ip: I::Addr) -> IcmpPacketBuilder<I, B, M> {
IcmpPacketBuilder {
src_ip,
dst_ip,
code: self.code(),
msg: *self.message(),
}
}
}
impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> IcmpPacket<I, B, M> {
/// Compute the checksum, skipping the checksum field itself.
///
/// `compute_checksum` returns `None` if the version is IPv6 and the total
/// ICMP packet length overflows a u32.
fn compute_checksum(
header: &Header, message: &[u8], message_body: &[u8], src_ip: I::Addr, dst_ip: I::Addr,
) -> Option<u16> {
let mut c = Checksum::new();
if I::VERSION.is_v6() {
c.add_bytes(src_ip.bytes());
c.add_bytes(dst_ip.bytes());
let icmpv6_len = mem::size_of::<Header>() + message.len() + message_body.len();
// For IPv6, the "ICMPv6 length" field in the pseudo-header is 32 bits.
if !fits_in_u32(icmpv6_len) {
return None;
}
let mut len_bytes = [0; 4];
NetworkEndian::write_u32(&mut len_bytes, icmpv6_len as u32);
c.add_bytes(&len_bytes[..]);
c.add_bytes(&[0, 0, 0]);
c.add_bytes(&[IpProto::Icmpv6 as u8]);
}
c.add_bytes(&[header.msg_type, header.code]);
c.add_bytes(message);
c.add_bytes(message_body);
Some(c.checksum())
}
}
impl<I: IcmpIpExt, B: ByteSlice, M: IcmpMessage<I, B, Body = OriginalPacket<B>>>
IcmpPacket<I, B, M>
{
/// Get the body of the packet that caused this ICMP message.
///
/// This ICMP message contains some of the bytes of the packet that caused
/// this message to be emitted. `original_packet_body` returns as much of
/// the body of that packet as is contained in this message. For IPv4, this
/// is guaranteed to be 8 bytes. For IPv6, there are no guarantees about the
/// length.
pub fn original_packet_body(&self) -> &[u8] {
self.message_body.body::<I>()
}
pub fn original_packet(&self) -> &OriginalPacket<B> {
&self.message_body
}
}
impl<I: IcmpIpExt, B: ByteSlice, M: IcmpMessage<I, B, Body = ndp::Options<B>>> IcmpPacket<I, B, M> {
/// Get the pared list of NDP options from the ICMP message.
pub fn ndp_options(&self) -> &ndp::Options<B> {
&self.message_body
}
}
/// A builder for ICMP packets.
pub struct IcmpPacketBuilder<I: IcmpIpExt, B, M: IcmpMessage<I, B>> {
src_ip: I::Addr,
dst_ip: I::Addr,
code: M::Code,
msg: M,
}
impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> IcmpPacketBuilder<I, B, M> {
/// Construct a new `IcmpPacketBuilder`.
pub fn new(
src_ip: I::Addr, dst_ip: I::Addr, code: M::Code, msg: M,
) -> IcmpPacketBuilder<I, B, M> {
IcmpPacketBuilder {
src_ip,
dst_ip,
code,
msg,
}
}
}
// TODO(joshlf): Figure out a way to split body and non-body message types by
// trait and implement PacketBuilder for some and InnerPacketBuilder for others.
impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> PacketBuilder for IcmpPacketBuilder<I, B, M> {
fn header_len(&self) -> usize {
mem::size_of::<Header>() + mem::size_of::<M>()
}
fn min_body_len(&self) -> usize {
0
}
fn footer_len(&self) -> usize {
0
}
fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
use packet::BufferViewMut;
let (mut prefix, message_body, _) = buffer.parts();
// implements BufferViewMut, giving us take_obj_xxx_zero methods
let mut prefix = &mut prefix;
assert!(
M::Body::EXPECTS_BODY || message_body.len() == 0,
"body provided for message that doesn't take a body"
);
// SECURITY: Use _zero constructors to ensure we zero memory to prevent
// leaking information from packets previously stored in this buffer.
let mut header = prefix
.take_obj_front_zero::<Header>()
.expect("too few bytes for ICMP message");
let mut message = prefix
.take_obj_front_zero::<M>()
.expect("too few bytes for ICMP message");
*message = self.msg;
header.set_msg_type(M::TYPE);
header.code = self.code.into();
let checksum = IcmpPacket::<I, B, M>::compute_checksum(
&header,
message.bytes(),
message_body,
self.src_ip,
self.dst_ip,
)
.unwrap_or_else(|| {
panic!(
"total ICMP packet length of {} overflows 32-bit length field of pseudo-header",
header.bytes().len() + message.bytes().len() + message_body.len(),
)
});
header.set_checksum(checksum);
}
}
/// The type of ICMP codes that are unused.
///
/// Some ICMP messages do not use codes. In Rust, the `IcmpMessage::Code` type
/// associated with these messages is `IcmpUnusedCode`. The only valid numerical
/// value for this code is 0.
#[derive(Copy, Clone, Debug)]
pub struct IcmpUnusedCode;
impl Into<u8> for IcmpUnusedCode {
fn into(self) -> u8 {
0
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
struct IdAndSeq {
id: [u8; 2],
seq: [u8; 2],
}
impl IdAndSeq {
fn id(&self) -> u16 {
NetworkEndian::read_u16(&self.id)
}
fn set_id(&mut self, id: u16) {
NetworkEndian::write_u16(&mut self.id, id);
}
fn seq(&self) -> u16 {
NetworkEndian::read_u16(&self.seq)
}
fn set_seq(&mut self, seq: u16) {
NetworkEndian::write_u16(&mut self.seq, seq);
}
}
impl_from_bytes_as_bytes_unaligned!(IdAndSeq);