blob: 6902bf83ef804fbe94ece330e1a0c375d9cb088b [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 std::ops::Range;
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, 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 Packet<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 Packet<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Packet::*;
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> Packet<B> {
/// Parse an ICMP packet.
///
/// `parse` parses `bytes` as an ICMP packet and validates the header fields
/// and checksum. It returns the byte range corresponding to the message
/// body within `bytes`. This can be useful when extracting the encapsulated
/// body to send to another layer of the stack. If the message type has no
/// body, then the range is meaningless and should be ignored.
pub fn parse(
bytes: B, src_ip: Ipv6Addr, dst_ip: Ipv6Addr,
) -> Result<(Packet<B>, Range<usize>), ParseError> {
macro_rules! mtch {
($bytes:expr, $src_ip:expr, $dst_ip:expr, $($variant:ident => $type:ty,)*) => {
match peek_message_type(&$bytes)? {
$(MessageType::$variant => {
let (packet, range) = IcmpPacket::<Ipv6, B, $type>::parse($bytes, $src_ip, $dst_ip)?;
(Packet::$variant(packet), range)
})*
}
}
}
Ok(mtch!(
bytes,
src_ip,
dst_ip,
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 super::*;
use crate::wire::icmp::{IcmpMessage, IcmpPacket, MessageBody};
use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketSerializer};
use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketSerializer};
use crate::wire::util::{BufferAndRange, PacketSerializer, SerializationRequest};
fn serialize_to_bytes<B: ByteSlice, M: IcmpMessage<Ipv6, B>>(
src_ip: Ipv6Addr, dst_ip: Ipv6Addr, icmp: &IcmpPacket<Ipv6, B, M>,
serializer: Ipv6PacketSerializer,
) -> Vec<u8> {
let icmp_serializer = icmp.serializer(src_ip, dst_ip);
let mut data = vec![0; icmp_serializer.max_header_bytes() + icmp.message_body.len()];
let body_offset = data.len() - icmp.message_body.len();
(&mut data[body_offset..]).copy_from_slice(icmp.message_body.bytes());
BufferAndRange::new_from(&mut data[..], body_offset..)
.encapsulate(icmp_serializer)
.encapsulate(serializer)
.serialize_outer()
.as_ref()
.to_vec()
}
#[test]
fn test_parse_and_serialize_echo_request_ipv6() {
use crate::wire::testdata::icmp_echo_v6::*;
let (ip, _) = Ipv6Packet::parse(REQUEST_IP_PACKET_BYTES).unwrap();
let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
// TODO: Check range
let (icmp, _) =
IcmpPacket::<_, _, IcmpEchoRequest>::parse(ip.body(), src_ip, dst_ip).unwrap();
assert_eq!(icmp.original_packet().bytes(), ECHO_DATA);
assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
assert_eq!(&data[..], REQUEST_IP_PACKET_BYTES);
}
}