blob: 0c18da18a0d8e2d685604a857868087abb7bdfb9 [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.
//! ICMPv4
use std::fmt;
use byteorder::{ByteOrder, NetworkEndian};
use packet::{BufferView, ParsablePacket, ParseMetadata};
use zerocopy::ByteSlice;
use crate::error::ParseError;
use crate::ip::{Ipv4, Ipv4Addr};
use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
use super::{
peek_message_type, IcmpIpExt, IcmpPacket, IcmpParseArgs, IcmpUnusedCode, IdAndSeq,
OriginalPacket,
};
/// An ICMPv4 packet with a dynamic message type.
///
/// Unlike `IcmpPacket`, `Packet` only supports ICMPv4, and does not
/// require a static message type. Each enum variant contains an `IcmpPacket` of
/// the appropriate static type, making it easier to call `parse` without
/// knowing the message type ahead of time while still getting the benefits of a
/// statically-typed packet struct after parsing is complete.
#[allow(missing_docs)]
pub enum Icmpv4Packet<B> {
EchoReply(IcmpPacket<Ipv4, B, IcmpEchoReply>),
DestUnreachable(IcmpPacket<Ipv4, B, IcmpDestUnreachable>),
Redirect(IcmpPacket<Ipv4, B, Icmpv4Redirect>),
EchoRequest(IcmpPacket<Ipv4, B, IcmpEchoRequest>),
TimeExceeded(IcmpPacket<Ipv4, B, IcmpTimeExceeded>),
ParameterProblem(IcmpPacket<Ipv4, B, Icmpv4ParameterProblem>),
TimestampRequest(IcmpPacket<Ipv4, B, Icmpv4TimestampRequest>),
TimestampReply(IcmpPacket<Ipv4, B, Icmpv4TimestampReply>),
}
impl<B: ByteSlice + fmt::Debug> fmt::Debug for Icmpv4Packet<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Icmpv4Packet::*;
match self {
DestUnreachable(ref p) => f.debug_tuple("DestUnreachable").field(p).finish(),
EchoReply(ref p) => f.debug_tuple("EchoReply").field(p).finish(),
EchoRequest(ref p) => f.debug_tuple("EchoRequest").field(p).finish(),
ParameterProblem(ref p) => f.debug_tuple("ParameterProblem").field(p).finish(),
Redirect(ref p) => f.debug_tuple("Redirect").field(p).finish(),
TimeExceeded(ref p) => f.debug_tuple("TimeExceeded").field(p).finish(),
TimestampReply(ref p) => f.debug_tuple("TimestampReply").field(p).finish(),
TimestampRequest(ref p) => f.debug_tuple("TimestampRequest").field(p).finish(),
}
}
}
impl<B: ByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv4Addr>> for Icmpv4Packet<B> {
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
use self::Icmpv4Packet::*;
match self {
EchoReply(p) => p.parse_metadata(),
DestUnreachable(p) => p.parse_metadata(),
Redirect(p) => p.parse_metadata(),
EchoRequest(p) => p.parse_metadata(),
TimeExceeded(p) => p.parse_metadata(),
ParameterProblem(p) => p.parse_metadata(),
TimestampRequest(p) => p.parse_metadata(),
TimestampReply(p) => p.parse_metadata(),
}
}
fn parse<BV: BufferView<B>>(
mut buffer: BV, args: IcmpParseArgs<Ipv4Addr>,
) -> Result<Self, ParseError> {
macro_rules! mtch {
($buffer:expr, $args:expr, $($variant:ident => $type:ty,)*) => {
match peek_message_type($buffer.as_ref())? {
$(MessageType::$variant => {
let packet = <IcmpPacket<Ipv4, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
Icmpv4Packet::$variant(packet)
})*
}
}
}
Ok(mtch!(
buffer,
args,
EchoReply => IcmpEchoReply,
DestUnreachable => IcmpDestUnreachable,
Redirect => Icmpv4Redirect,
EchoRequest => IcmpEchoRequest,
TimeExceeded => IcmpTimeExceeded,
ParameterProblem => Icmpv4ParameterProblem,
TimestampRequest => Icmpv4TimestampRequest,
TimestampReply => Icmpv4TimestampReply,
))
}
}
create_net_enum! {
MessageType,
EchoReply: ECHO_REPLY = 0,
DestUnreachable: DEST_UNREACHABLE = 3,
Redirect: REDIRECT = 5,
EchoRequest: ECHO_REQUEST = 8,
TimeExceeded: TIME_EXCEEDED = 11,
ParameterProblem: PARAMETER_PROBLEM = 12,
TimestampRequest: TIMESTAMP_REQUEST = 13,
TimestampReply: TIMESTAMP_REPLY = 14,
}
create_net_enum! {
Icmpv4DestUnreachableCode,
DestNetworkUnreachable: DEST_NETWORK_UNREACHABLE = 0,
DestHostUnreachable: DEST_HOST_UNREACHABLE = 1,
DestProtocolUnreachable: DEST_PROTOCOL_UNREACHABLE = 2,
DestPortUnreachable: DEST_PORT_UNREACHABLE = 3,
FragmentationRequired: FRAGMENTATION_REQUIRED = 4,
SourceRouteFailed: SOURCE_ROUTE_FAILED = 5,
DestNetworkUnknown: DEST_NETWORK_UNKNOWN = 6,
DestHostUnknown: DEST_HOST_UNKNOWN = 7,
SourceHostIsolated: SOURCE_HOST_ISOLATED = 8,
NetworkAdministrativelyProhibited: NETWORK_ADMINISTRATIVELY_PROHIBITED = 9,
HostAdministrativelyProhibited: HOST_ADMINISTRATIVELY_PROHIBITED = 10,
NetworkUnreachableForToS: NETWORK_UNREACHABLE_FOR_TOS = 11,
HostUnreachableForToS: HOST_UNREACHABLE_FOR_TOS = 12,
CommAdministrativelyProhibited: COMM_ADMINISTRATIVELY_PROHIBITED = 13,
HostPrecedenceViolation: HOST_PRECEDENCE_VIOLATION = 14,
PrecedenceCutoffInEffect: PRECEDENCE_CUTOFF_IN_EFFECT = 15,
}
impl_icmp_message!(
Ipv4,
IcmpDestUnreachable,
DestUnreachable,
Icmpv4DestUnreachableCode,
OriginalPacket<B>
);
impl_icmp_message!(
Ipv4,
IcmpEchoRequest,
EchoRequest,
IcmpUnusedCode,
OriginalPacket<B>
);
impl_icmp_message!(
Ipv4,
IcmpEchoReply,
EchoReply,
IcmpUnusedCode,
OriginalPacket<B>
);
create_net_enum! {
Icmpv4RedirectCode,
RedirectForNetwork: REDIRECT_FOR_NETWORK = 0,
RedirectForHost: REDIRECT_FOR_HOST = 1,
RedirectForToSNetwork: REDIRECT_FOR_TOS_NETWORK = 2,
RedirectForToSHost: REDIRECT_FOR_TOS_HOST = 3,
}
/// An ICMPv4 Redirect Message.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Icmpv4Redirect {
gateway: Ipv4Addr,
}
impl_from_bytes_as_bytes_unaligned!(Icmpv4Redirect);
impl_icmp_message!(
Ipv4,
Icmpv4Redirect,
Redirect,
Icmpv4RedirectCode,
OriginalPacket<B>
);
create_net_enum! {
Icmpv4TimeExceededCode,
TTLExpired: TTL_EXPIRED = 0,
FragmentReassemblyTimeExceeded: FRAGMENT_REASSEMBLY_TIME_EXCEEDED = 1,
}
impl_icmp_message!(
Ipv4,
IcmpTimeExceeded,
TimeExceeded,
Icmpv4TimeExceededCode,
OriginalPacket<B>
);
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
struct IcmpTimestampData {
origin_timestamp: [u8; 4],
recv_timestamp: [u8; 4],
tx_timestamp: [u8; 4],
}
impl IcmpTimestampData {
fn origin_timestamp(&self) -> u32 {
NetworkEndian::read_u32(&self.origin_timestamp)
}
fn recv_timestamp(&self) -> u32 {
NetworkEndian::read_u32(&self.recv_timestamp)
}
fn tx_timestamp(&self) -> u32 {
NetworkEndian::read_u32(&self.tx_timestamp)
}
fn set_origin_timestamp(&mut self, timestamp: u32) {
NetworkEndian::write_u32(&mut self.origin_timestamp, timestamp)
}
fn set_recv_timestamp(&mut self, timestamp: u32) {
NetworkEndian::write_u32(&mut self.recv_timestamp, timestamp)
}
fn set_tx_timestamp(&mut self, timestamp: u32) {
NetworkEndian::write_u32(&mut self.tx_timestamp, timestamp)
}
}
impl_from_bytes_as_bytes_unaligned!(IcmpTimestampData);
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
struct Timestamp {
id_seq: IdAndSeq,
timestamps: IcmpTimestampData,
}
/// An ICMPv4 Timestamp Request message.
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct Icmpv4TimestampRequest(Timestamp);
/// An ICMPv4 Timestamp Reply message.
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct Icmpv4TimestampReply(Timestamp);
impl_from_bytes_as_bytes_unaligned!(Icmpv4TimestampRequest);
impl_from_bytes_as_bytes_unaligned!(Icmpv4TimestampReply);
impl_icmp_message!(
Ipv4,
Icmpv4TimestampRequest,
TimestampRequest,
IcmpUnusedCode
);
impl_icmp_message!(Ipv4, Icmpv4TimestampReply, TimestampReply, IcmpUnusedCode);
create_net_enum! {
Icmpv4ParameterProblemCode,
PointerIndicatesError: POINTER_INDICATES_ERROR = 0,
MissingRequiredOption: MISSING_REQUIRED_OPTION = 1,
BadLength: BAD_LENGTH = 2,
}
/// An ICMPv4 Parameter Problem message.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Icmpv4ParameterProblem {
pointer: u8,
_unused: [u8; 3],
/* The rest of Icmpv4ParameterProblem is variable-length, so is stored in
* the message_body field in IcmpPacket */
}
impl_from_bytes_as_bytes_unaligned!(Icmpv4ParameterProblem);
impl_icmp_message!(
Ipv4,
Icmpv4ParameterProblem,
ParameterProblem,
Icmpv4ParameterProblemCode,
OriginalPacket<B>
);
#[cfg(test)]
mod tests {
use packet::{ParseBuffer, Serializer};
use super::*;
use crate::wire::icmp::{IcmpMessage, MessageBody};
use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder};
fn serialize_to_bytes<B: ByteSlice, M: IcmpMessage<Ipv4, B>>(
src_ip: Ipv4Addr, dst_ip: Ipv4Addr, icmp: &IcmpPacket<Ipv4, B, M>,
builder: Ipv4PacketBuilder,
) -> Vec<u8> {
icmp.message_body
.bytes()
.encapsulate(icmp.builder(src_ip, dst_ip))
.encapsulate(builder)
.serialize_outer()
.as_ref()
.to_vec()
}
fn test_parse_and_serialize<
M: for<'a> IcmpMessage<Ipv4, &'a [u8]>,
F: for<'a> FnOnce(&IcmpPacket<Ipv4, &'a [u8], M>),
>(
mut req: &[u8], check: F,
) {
let orig_req = &req[..];
let ip = req.parse::<Ipv4Packet<_>>().unwrap();
let mut body = ip.body();
let icmp = body
.parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
.unwrap();
check(&icmp);
let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
assert_eq!(&data[..], orig_req);
}
#[test]
fn test_parse_and_serialize_echo_request() {
use crate::wire::testdata::icmp_echo::*;
test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
assert_eq!(icmp.message_body.bytes(), ECHO_DATA);
assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
});
}
#[test]
fn test_parse_and_serialize_echo_response() {
use crate::wire::testdata::icmp_echo::*;
test_parse_and_serialize::<IcmpEchoReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
assert_eq!(icmp.message_body.bytes(), ECHO_DATA);
assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
});
}
#[test]
fn test_parse_and_serialize_timestamp_request() {
use crate::wire::testdata::icmp_timestamp::*;
test_parse_and_serialize::<Icmpv4TimestampRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
assert_eq!(
icmp.message().0.timestamps.origin_timestamp(),
ORIGIN_TIMESTAMP
);
assert_eq!(icmp.message().0.timestamps.tx_timestamp(), RX_TX_TIMESTAMP);
assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
});
}
#[test]
fn test_parse_and_serialize_timestamp_reply() {
use crate::wire::testdata::icmp_timestamp::*;
test_parse_and_serialize::<Icmpv4TimestampReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
assert_eq!(
icmp.message().0.timestamps.origin_timestamp(),
ORIGIN_TIMESTAMP
);
// TODO: Assert other values here?
// TODO: Check value of recv_timestamp and tx_timestamp
assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
});
}
#[test]
fn test_parse_and_serialize_dest_unreachable() {
use crate::wire::testdata::icmp_dest_unreachable::*;
test_parse_and_serialize::<IcmpDestUnreachable, _>(IP_PACKET_BYTES, |icmp| {
assert_eq!(icmp.code(), Icmpv4DestUnreachableCode::DestHostUnreachable);
assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
});
}
#[test]
fn test_parse_and_serialize_redirect() {
use crate::wire::testdata::icmp_redirect::*;
test_parse_and_serialize::<Icmpv4Redirect, _>(IP_PACKET_BYTES, |icmp| {
assert_eq!(icmp.code(), Icmpv4RedirectCode::RedirectForHost);
assert_eq!(icmp.message().gateway, GATEWAY_ADDR);
});
}
#[test]
fn test_parse_and_serialize_time_exceeded() {
use crate::wire::testdata::icmp_time_exceeded::*;
test_parse_and_serialize::<IcmpTimeExceeded, _>(IP_PACKET_BYTES, |icmp| {
assert_eq!(icmp.code(), Icmpv4TimeExceededCode::TTLExpired);
assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
});
}
}