blob: 0705f6ee2f1cbb87ae39b8c5ebfb9242cad99ee6 [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.
//! ICMPv6
use std::fmt;
use packet::{BufferView, ParsablePacket, ParseMetadata};
use zerocopy::ByteSlice;
use crate::error::ParseError;
use crate::ip::{Ipv6, Ipv6Addr};
use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
use super::{
ndp, peek_message_type, IcmpIpExt, IcmpPacket, IcmpParseArgs, IcmpUnusedCode, OriginalPacket,
};
/// An ICMPv6 packet with a dynamic message type.
///
/// Unlike `IcmpPacket`, `Packet` only supports ICMPv6, 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 Icmpv6Packet<B> {
DestUnreachable(IcmpPacket<Ipv6, B, IcmpDestUnreachable>),
PacketTooBig(IcmpPacket<Ipv6, B, Icmpv6PacketTooBig>),
TimeExceeded(IcmpPacket<Ipv6, B, IcmpTimeExceeded>),
ParameterProblem(IcmpPacket<Ipv6, B, Icmpv6ParameterProblem>),
EchoRequest(IcmpPacket<Ipv6, B, IcmpEchoRequest>),
EchoReply(IcmpPacket<Ipv6, B, IcmpEchoReply>),
RouterSolicitation(IcmpPacket<Ipv6, B, ndp::RouterSolicitation>),
RouterAdvertisment(IcmpPacket<Ipv6, B, ndp::RouterAdvertisment>),
NeighborSolicitation(IcmpPacket<Ipv6, B, ndp::NeighborSolicitation>),
NeighborAdvertisment(IcmpPacket<Ipv6, B, ndp::NeighborAdvertisment>),
Redirect(IcmpPacket<Ipv6, B, ndp::Redirect>),
}
impl<B: ByteSlice + fmt::Debug> fmt::Debug for Icmpv6Packet<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Icmpv6Packet::*;
match self {
DestUnreachable(ref p) => f.debug_tuple("DestUnreachable").field(p).finish(),
PacketTooBig(ref p) => f.debug_tuple("PacketTooBig").field(p).finish(),
TimeExceeded(ref p) => f.debug_tuple("TimeExceeded").field(p).finish(),
ParameterProblem(ref p) => f.debug_tuple("ParameterProblem").field(p).finish(),
EchoRequest(ref p) => f.debug_tuple("EchoRequest").field(p).finish(),
EchoReply(ref p) => f.debug_tuple("EchoReply").field(p).finish(),
RouterSolicitation(ref p) => f.debug_tuple("RouterSolicitation").field(p).finish(),
RouterAdvertisment(ref p) => f.debug_tuple("RouterAdvertisment").field(p).finish(),
NeighborSolicitation(ref p) => f.debug_tuple("NeighborSolicitation").field(p).finish(),
NeighborAdvertisment(ref p) => f.debug_tuple("NeighborAdvertisment").field(p).finish(),
Redirect(ref p) => f.debug_tuple("Redirect").field(p).finish(),
}
}
}
impl<B: ByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv6Addr>> for Icmpv6Packet<B> {
type Error = ParseError;
fn parse_metadata(&self) -> ParseMetadata {
use self::Icmpv6Packet::*;
match self {
DestUnreachable(p) => p.parse_metadata(),
PacketTooBig(p) => p.parse_metadata(),
TimeExceeded(p) => p.parse_metadata(),
ParameterProblem(p) => p.parse_metadata(),
EchoRequest(p) => p.parse_metadata(),
EchoReply(p) => p.parse_metadata(),
RouterSolicitation(p) => p.parse_metadata(),
RouterAdvertisment(p) => p.parse_metadata(),
NeighborSolicitation(p) => p.parse_metadata(),
NeighborAdvertisment(p) => p.parse_metadata(),
Redirect(p) => p.parse_metadata(),
}
}
fn parse<BV: BufferView<B>>(
mut buffer: BV, args: IcmpParseArgs<Ipv6Addr>,
) -> 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<Ipv6, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
Icmpv6Packet::$variant(packet)
})*
}
}
}
Ok(mtch!(
buffer,
args,
DestUnreachable => IcmpDestUnreachable,
PacketTooBig => Icmpv6PacketTooBig,
TimeExceeded => IcmpTimeExceeded,
ParameterProblem => Icmpv6ParameterProblem,
EchoRequest => IcmpEchoRequest,
EchoReply => IcmpEchoReply,
RouterSolicitation => ndp::RouterSolicitation,
RouterAdvertisment => ndp::RouterAdvertisment,
NeighborSolicitation => ndp::NeighborSolicitation,
NeighborAdvertisment => ndp::NeighborAdvertisment,
Redirect => ndp::Redirect,
))
}
}
create_net_enum! {
MessageType,
DestUnreachable: DEST_UNREACHABLE = 1,
PacketTooBig: PACKET_TOO_BIG = 2,
TimeExceeded: TIME_EXCEEDED = 3,
ParameterProblem: PARAMETER_PROBLEM = 4,
EchoRequest: ECHO_REQUEST = 128,
EchoReply: ECHO_REPLY = 129,
// NDP messages
RouterSolicitation: ROUTER_SOLICITATION = 133,
RouterAdvertisment: ROUTER_ADVERTISMENT = 134,
NeighborSolicitation: NEIGHBOR_SOLICITATION = 135,
NeighborAdvertisment: NEIGHBOR_ADVERTISMENT = 136,
Redirect: REDIRECT = 137,
}
impl_icmp_message!(
Ipv6,
IcmpEchoRequest,
EchoRequest,
IcmpUnusedCode,
OriginalPacket<B>
);
impl_icmp_message!(
Ipv6,
IcmpEchoReply,
EchoReply,
IcmpUnusedCode,
OriginalPacket<B>
);
create_net_enum! {
Icmpv6DestUnreachableCode,
NoRoute: NO_ROUTE = 0,
CommAdministrativelyProhibited: COMM_ADMINISTRATIVELY_PROHIBITED = 1,
BeyondScope: BEYOND_SCOPE = 2,
AddrUnreachable: ADDR_UNREACHABLE = 3,
PortUnreachable: PORT_UNREACHABLE = 4,
SrcAddrFailedPolicy: SRC_ADDR_FAILED_POLICY = 5,
RejectRoute: REJECT_ROUTE = 6,
}
impl_icmp_message!(
Ipv6,
IcmpDestUnreachable,
DestUnreachable,
Icmpv6DestUnreachableCode,
OriginalPacket<B>
);
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Icmpv6PacketTooBig {
mtu: [u8; 4],
}
impl_from_bytes_as_bytes_unaligned!(Icmpv6PacketTooBig);
impl_icmp_message!(
Ipv6,
Icmpv6PacketTooBig,
PacketTooBig,
IcmpUnusedCode,
OriginalPacket<B>
);
create_net_enum! {
Icmpv6TimeExceededCode,
HopLimitExceeded: HOP_LIMIT_EXCEEDED = 0,
FragmentReassemblyTimeExceeded: FRAGMENT_REASSEMBLY_TIME_EXCEEDED = 1,
}
impl_icmp_message!(
Ipv6,
IcmpTimeExceeded,
TimeExceeded,
Icmpv6TimeExceededCode,
OriginalPacket<B>
);
create_net_enum! {
Icmpv6ParameterProblemCode,
ErroneousHeaderField: ERRONEOUS_HEADER_FIELD = 0,
UnrecognizedNextHeaderType: UNRECOGNIZED_NEXT_HEADER_TYPE = 1,
UnrecognizedIpv6Option: UNRECOGNIZED_IPV6_OPTION = 2,
}
/// An ICMPv6 Parameter Problem message.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Icmpv6ParameterProblem {
pointer: [u8; 4],
}
impl_from_bytes_as_bytes_unaligned!(Icmpv6ParameterProblem);
impl_icmp_message!(
Ipv6,
Icmpv6ParameterProblem,
ParameterProblem,
Icmpv6ParameterProblemCode,
OriginalPacket<B>
);
#[cfg(test)]
mod tests {
use packet::{ParseBuffer, Serializer};
use super::*;
use crate::wire::icmp::{IcmpMessage, IcmpPacket, MessageBody};
use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
fn serialize_to_bytes<B: ByteSlice, M: IcmpMessage<Ipv6, B>>(
src_ip: Ipv6Addr, dst_ip: Ipv6Addr, icmp: &IcmpPacket<Ipv6, B, M>,
builder: Ipv6PacketBuilder,
) -> 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<Ipv6, &'a [u8]>,
F: for<'a> FnOnce(&IcmpPacket<Ipv6, &'a [u8], M>),
>(
mut req: &[u8], check: F,
) {
let orig_req = &req[..];
let ip = req.parse::<Ipv6Packet<_>>().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_v6::*;
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);
});
}
}