packet_formats/icmp/
icmpv6.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! ICMPv6
6
7use core::fmt;
8
9use net_types::ip::{GenericOverIp, Ipv6, Ipv6Addr};
10use packet::{BufferView, ParsablePacket, ParseMetadata};
11use zerocopy::byteorder::network_endian::U32;
12use zerocopy::{
13    FromBytes, Immutable, IntoBytes, KnownLayout, SplitByteSlice, SplitByteSliceMut, Unaligned,
14};
15
16use crate::error::{ParseError, ParseResult};
17
18use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
19use super::{
20    HeaderPrefix, IcmpIpExt, IcmpMessageType, IcmpPacket, IcmpPacketRaw, IcmpParseArgs,
21    IcmpZeroCode, OriginalPacket, mld, ndp, peek_message_type,
22};
23
24/// Dispatches expressions to the type-safe variants of [`Icmpv6Packet`] or
25/// [`Icmpv6PacketRaw`].
26///
27/// # Usage
28///
29/// ```
30/// icmpv6_dispatch!(packet, p => p.message_mut().foo());
31///
32/// icmpv6_dispatch!(packet: raw, p => p.message_mut().foo());
33/// ```
34#[macro_export]
35macro_rules! icmpv6_dispatch {
36    (__internal__, $x:ident, $variable:pat => $expression:expr, $($typ:ty),+) => {
37        {
38            $(
39                use $typ::*;
40            )+
41
42            match $x {
43                DestUnreachable($variable) => $expression,
44                PacketTooBig($variable) => $expression,
45                TimeExceeded($variable) => $expression,
46                ParameterProblem($variable) => $expression,
47                EchoRequest($variable) => $expression,
48                EchoReply($variable) => $expression,
49                Ndp(RouterSolicitation($variable)) => $expression,
50                Ndp(RouterAdvertisement($variable)) => $expression,
51                Ndp(NeighborSolicitation($variable)) => $expression,
52                Ndp(NeighborAdvertisement($variable)) => $expression,
53                Ndp(Redirect($variable)) => $expression,
54                Mld(MulticastListenerQuery($variable)) => $expression,
55                Mld(MulticastListenerReport($variable)) => $expression,
56                Mld(MulticastListenerDone($variable)) => $expression,
57                Mld(MulticastListenerQueryV2($variable)) => $expression,
58                Mld(MulticastListenerReportV2($variable)) => $expression,
59            }
60        }
61    };
62    ($x:ident : raw, $variable:pat => $expression:expr) => {
63        $crate::icmpv6_dispatch!(__internal__, $x,
64            $variable => $expression,
65            $crate::icmp::Icmpv6PacketRaw,
66            $crate::icmp::mld::MldPacketRaw,
67            $crate::icmp::ndp::NdpPacketRaw)
68    };
69    ($x:ident, $variable:pat => $expression:expr) => {
70        $crate::icmpv6_dispatch!(__internal__, $x,
71            $variable => $expression,
72            $crate::icmp::Icmpv6Packet,
73            $crate::icmp::mld::MldPacket,
74            $crate::icmp::ndp::NdpPacket)
75    };
76}
77
78/// An ICMPv6 packet with a dynamic message type.
79///
80/// Unlike `IcmpPacket`, `Packet` only supports ICMPv6, and does not
81/// require a static message type. Each enum variant contains an `IcmpPacket` of
82/// the appropriate static type, making it easier to call `parse` without
83/// knowing the message type ahead of time while still getting the benefits of a
84/// statically-typed packet struct after parsing is complete.
85#[allow(missing_docs)]
86pub enum Icmpv6Packet<B: SplitByteSlice> {
87    DestUnreachable(IcmpPacket<Ipv6, B, IcmpDestUnreachable>),
88    PacketTooBig(IcmpPacket<Ipv6, B, Icmpv6PacketTooBig>),
89    TimeExceeded(IcmpPacket<Ipv6, B, IcmpTimeExceeded>),
90    ParameterProblem(IcmpPacket<Ipv6, B, Icmpv6ParameterProblem>),
91    EchoRequest(IcmpPacket<Ipv6, B, IcmpEchoRequest>),
92    EchoReply(IcmpPacket<Ipv6, B, IcmpEchoReply>),
93    Ndp(ndp::NdpPacket<B>),
94    Mld(mld::MldPacket<B>),
95}
96
97impl<B: SplitByteSlice + fmt::Debug> fmt::Debug for Icmpv6Packet<B> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        use self::Icmpv6Packet::*;
100        use mld::MldPacket::*;
101        use ndp::NdpPacket::*;
102        match self {
103            DestUnreachable(p) => f.debug_tuple("DestUnreachable").field(p).finish(),
104            PacketTooBig(p) => f.debug_tuple("PacketTooBig").field(p).finish(),
105            TimeExceeded(p) => f.debug_tuple("TimeExceeded").field(p).finish(),
106            ParameterProblem(p) => f.debug_tuple("ParameterProblem").field(p).finish(),
107            EchoRequest(p) => f.debug_tuple("EchoRequest").field(p).finish(),
108            EchoReply(p) => f.debug_tuple("EchoReply").field(p).finish(),
109            Ndp(RouterSolicitation(p)) => f.debug_tuple("RouterSolicitation").field(p).finish(),
110            Ndp(RouterAdvertisement(p)) => f.debug_tuple("RouterAdvertisement").field(p).finish(),
111            Ndp(NeighborSolicitation(p)) => f.debug_tuple("NeighborSolicitation").field(p).finish(),
112            Ndp(NeighborAdvertisement(p)) => {
113                f.debug_tuple("NeighborAdvertisement").field(p).finish()
114            }
115            Ndp(Redirect(p)) => f.debug_tuple("Redirect").field(p).finish(),
116            Mld(MulticastListenerQuery(p)) => {
117                f.debug_tuple("MulticastListenerQuery").field(p).finish()
118            }
119            Mld(MulticastListenerReport(p)) => {
120                f.debug_tuple("MulticastListenerReport").field(p).finish()
121            }
122            Mld(MulticastListenerDone(p)) => {
123                f.debug_tuple("MulticastListenerDone").field(p).finish()
124            }
125            Mld(MulticastListenerQueryV2(p)) => {
126                f.debug_tuple("MulticastListenerQueryV2").field(p).finish()
127            }
128            Mld(MulticastListenerReportV2(p)) => {
129                f.debug_tuple("MulticastListenerReportV2").field(p).finish()
130            }
131        }
132    }
133}
134
135impl<B: SplitByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv6Addr>> for Icmpv6Packet<B> {
136    type Error = ParseError;
137
138    fn parse_metadata(&self) -> ParseMetadata {
139        icmpv6_dispatch!(self, p => p.parse_metadata())
140    }
141
142    fn parse<BV: BufferView<B>>(buffer: BV, args: IcmpParseArgs<Ipv6Addr>) -> ParseResult<Self> {
143        use self::Icmpv6Packet::*;
144        use mld::MldPacket::*;
145        use ndp::NdpPacket::*;
146
147        macro_rules! mtch {
148            ($buffer:expr, $args:expr, $pkt_name:ident, $( ($msg_variant:ident, $len_pat:pat) => $pkt_variant:expr => $type:ty,)*) => {
149                match ( peek_message_type($buffer.as_ref())?, $buffer.len() ) {
150                    $( (Icmpv6MessageType::$msg_variant, $len_pat) => {
151                        let $pkt_name = <IcmpPacket<Ipv6, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
152                        $pkt_variant
153                    })*
154                }
155            }
156        }
157
158        Ok(mtch!(
159            buffer,
160            args,
161            packet,
162            (DestUnreachable, ..)               => DestUnreachable(packet)                => IcmpDestUnreachable,
163            (PacketTooBig, ..)                  => PacketTooBig(packet)                   => Icmpv6PacketTooBig,
164            (TimeExceeded, ..)                  => TimeExceeded(packet)                   => IcmpTimeExceeded,
165            (ParameterProblem, ..)              => ParameterProblem(packet)               => Icmpv6ParameterProblem,
166            (EchoRequest, ..)                   => EchoRequest(packet)                    => IcmpEchoRequest,
167            (EchoReply, ..)                     => EchoReply(packet)                      => IcmpEchoReply,
168            (RouterSolicitation, ..)            => Ndp(RouterSolicitation(packet))        => ndp::RouterSolicitation,
169            (RouterAdvertisement, ..)           => Ndp(RouterAdvertisement(packet))       => ndp::RouterAdvertisement,
170            (NeighborSolicitation, ..)          => Ndp(NeighborSolicitation(packet))      => ndp::NeighborSolicitation,
171            (NeighborAdvertisement, ..)         => Ndp(NeighborAdvertisement(packet))     => ndp::NeighborAdvertisement,
172            (Redirect, ..)                      => Ndp(Redirect(packet))                  => ndp::Redirect,
173            (MulticastListenerQuery, 0..=27 )   => Mld(MulticastListenerQuery(packet))    => mld::MulticastListenerQuery,
174            (MulticastListenerQuery, 28..)      => Mld(MulticastListenerQueryV2(packet))  => mld::MulticastListenerQueryV2,
175            (MulticastListenerReport, ..)       => Mld(MulticastListenerReport(packet))   => mld::MulticastListenerReport,
176            (MulticastListenerReportV2, ..)     => Mld(MulticastListenerReportV2(packet)) => mld::MulticastListenerReportV2,
177            (MulticastListenerDone, ..)         => Mld(MulticastListenerDone(packet))     => mld::MulticastListenerDone,
178        ))
179    }
180}
181
182/// A raw ICMPv6 packet with a dynamic message type.
183///
184/// Unlike `IcmpPacketRaw`, `Packet` only supports ICMPv6, and does not
185/// require a static message type. Each enum variant contains an `IcmpPacketRaw`
186/// of the appropriate static type, making it easier to call `parse` without
187/// knowing the message type ahead of time while still getting the benefits of a
188/// statically-typed packet struct after parsing is complete.
189#[allow(missing_docs)]
190pub enum Icmpv6PacketRaw<B: SplitByteSlice> {
191    DestUnreachable(IcmpPacketRaw<Ipv6, B, IcmpDestUnreachable>),
192    PacketTooBig(IcmpPacketRaw<Ipv6, B, Icmpv6PacketTooBig>),
193    TimeExceeded(IcmpPacketRaw<Ipv6, B, IcmpTimeExceeded>),
194    ParameterProblem(IcmpPacketRaw<Ipv6, B, Icmpv6ParameterProblem>),
195    EchoRequest(IcmpPacketRaw<Ipv6, B, IcmpEchoRequest>),
196    EchoReply(IcmpPacketRaw<Ipv6, B, IcmpEchoReply>),
197    Ndp(ndp::NdpPacketRaw<B>),
198    Mld(mld::MldPacketRaw<B>),
199}
200
201impl<B: SplitByteSliceMut> Icmpv6PacketRaw<B> {
202    pub(super) fn header_prefix_mut(&mut self) -> &mut HeaderPrefix {
203        icmpv6_dispatch!(self: raw, p => &mut p.header.prefix)
204    }
205
206    /// Overwrites the current checksum with `checksum`, returning the original.
207    pub(crate) fn overwrite_checksum(&mut self, checksum: [u8; 2]) -> [u8; 2] {
208        core::mem::replace(&mut self.header_prefix_mut().checksum, checksum)
209    }
210
211    /// Attempts to calculate and write a Checksum for this [`Icmpv6PacketRaw`].
212    ///
213    /// Returns whether the checksum was successfully calculated & written. In
214    /// the false case, self is left unmodified.
215    pub fn try_write_checksum(&mut self, src_ip: Ipv6Addr, dst_ip: Ipv6Addr) -> bool {
216        icmpv6_dispatch!(self: raw, p => p.try_write_checksum(src_ip, dst_ip))
217    }
218}
219
220impl<B: SplitByteSlice> ParsablePacket<B, ()> for Icmpv6PacketRaw<B> {
221    type Error = ParseError;
222
223    fn parse_metadata(&self) -> ParseMetadata {
224        icmpv6_dispatch!(self: raw, p => p.parse_metadata())
225    }
226
227    fn parse<BV: BufferView<B>>(buffer: BV, _args: ()) -> ParseResult<Self> {
228        use self::Icmpv6PacketRaw::*;
229        use mld::MldPacketRaw::*;
230        use ndp::NdpPacketRaw::*;
231
232        macro_rules! mtch {
233            ($buffer:expr, $pkt_name:ident, $( ($msg_variant:ident, $len_pat:pat) => $pkt_variant:expr => $type:ty,)*) => {
234                match ( peek_message_type($buffer.as_ref())?, $buffer.len() ) {
235                    $( (Icmpv6MessageType::$msg_variant, $len_pat) => {
236                        let $pkt_name = <IcmpPacketRaw<Ipv6, B, $type> as ParsablePacket<_, _>>::parse($buffer, ())?;
237                        $pkt_variant
238                    })*
239                }
240            }
241        }
242
243        Ok(mtch!(
244            buffer,
245            packet,
246            (DestUnreachable, ..)               => DestUnreachable(packet)                => IcmpDestUnreachable,
247            (PacketTooBig, ..)                  => PacketTooBig(packet)                   => Icmpv6PacketTooBig,
248            (TimeExceeded, ..)                  => TimeExceeded(packet)                   => IcmpTimeExceeded,
249            (ParameterProblem, ..)              => ParameterProblem(packet)               => Icmpv6ParameterProblem,
250            (EchoRequest, ..)                   => EchoRequest(packet)                    => IcmpEchoRequest,
251            (EchoReply, ..)                     => EchoReply(packet)                      => IcmpEchoReply,
252            (RouterSolicitation, ..)            => Ndp(RouterSolicitation(packet))        => ndp::RouterSolicitation,
253            (RouterAdvertisement, ..)           => Ndp(RouterAdvertisement(packet))       => ndp::RouterAdvertisement,
254            (NeighborSolicitation, ..)          => Ndp(NeighborSolicitation(packet))      => ndp::NeighborSolicitation,
255            (NeighborAdvertisement, ..)         => Ndp(NeighborAdvertisement(packet))     => ndp::NeighborAdvertisement,
256            (Redirect, ..)                      => Ndp(Redirect(packet))                  => ndp::Redirect,
257            (MulticastListenerQuery, 0..=27 )   => Mld(MulticastListenerQuery(packet))    => mld::MulticastListenerQuery,
258            (MulticastListenerQuery, 28..)      => Mld(MulticastListenerQueryV2(packet))  => mld::MulticastListenerQueryV2,
259            (MulticastListenerReport, ..)       => Mld(MulticastListenerReport(packet))   => mld::MulticastListenerReport,
260            (MulticastListenerReportV2, ..)     => Mld(MulticastListenerReportV2(packet)) => mld::MulticastListenerReportV2,
261            (MulticastListenerDone, ..)         => Mld(MulticastListenerDone(packet))     => mld::MulticastListenerDone,
262        ))
263    }
264}
265
266create_protocol_enum!(
267    #[allow(missing_docs)]
268    #[derive(Copy, Clone, PartialEq, Eq)]
269    pub enum Icmpv6MessageType: u8 {
270        DestUnreachable, 1, "Destination Unreachable";
271        PacketTooBig, 2, "Packet Too Big";
272        TimeExceeded, 3, "Time Exceeded";
273        ParameterProblem, 4, "Parameter Problem";
274        EchoRequest, 128, "Echo Request";
275        EchoReply, 129, "Echo Reply";
276
277        // NDP messages
278        RouterSolicitation, 133, "Router Solicitation";
279        RouterAdvertisement, 134, "Router Advertisement";
280        NeighborSolicitation, 135, "Neighbor Solicitation";
281        NeighborAdvertisement, 136, "Neighbor Advertisement";
282        Redirect, 137, "Redirect";
283
284        // MLDv1 messages
285        // This is used for both QueryV1 and QueryV2
286        MulticastListenerQuery, 130, "Multicast Listener Query";
287        MulticastListenerReport, 131, "Multicast Listener Report";
288        MulticastListenerDone, 132, "Multicast Listener Done";
289
290        // MLDv2 messages
291        MulticastListenerReportV2, 143, "Multicast Listener Report V2";
292    }
293);
294
295impl<I: IcmpIpExt> GenericOverIp<I> for Icmpv6MessageType {
296    type Type = I::IcmpMessageType;
297}
298
299impl IcmpMessageType for Icmpv6MessageType {
300    fn is_err(self) -> bool {
301        use Icmpv6MessageType::*;
302        [DestUnreachable, PacketTooBig, TimeExceeded, ParameterProblem].contains(&self)
303    }
304}
305
306create_protocol_enum!(
307    #[allow(missing_docs)]
308    #[derive(Copy, Clone, PartialEq, Eq)]
309    pub enum Icmpv6DestUnreachableCode: u8 {
310        NoRoute, 0, "No Route";
311        CommAdministrativelyProhibited, 1, "Comm Administratively Prohibited";
312        BeyondScope, 2, "Beyond Scope";
313        AddrUnreachable, 3, "Address Unreachable";
314        PortUnreachable, 4, "Port Unreachable";
315        SrcAddrFailedPolicy, 5, "Source Address Failed Policy";
316        RejectRoute, 6, "Reject Route";
317    }
318);
319
320impl_icmp_message!(
321    Ipv6,
322    IcmpDestUnreachable,
323    DestUnreachable,
324    Icmpv6DestUnreachableCode,
325    OriginalPacket<B>
326);
327
328/// An ICMPv6 Packet Too Big message.
329#[derive(
330    Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, PartialEq,
331)]
332#[repr(C)]
333pub struct Icmpv6PacketTooBig {
334    mtu: U32,
335}
336
337impl Icmpv6PacketTooBig {
338    /// Returns a new `Icmpv6PacketTooBig` with the given MTU value.
339    pub fn new(mtu: u32) -> Icmpv6PacketTooBig {
340        Icmpv6PacketTooBig { mtu: U32::new(mtu) }
341    }
342
343    /// Get the mtu value.
344    pub fn mtu(&self) -> u32 {
345        self.mtu.get()
346    }
347}
348
349impl_icmp_message!(Ipv6, Icmpv6PacketTooBig, PacketTooBig, IcmpZeroCode, OriginalPacket<B>);
350
351create_protocol_enum!(
352    #[allow(missing_docs)]
353    #[derive(Copy, Clone, PartialEq, Eq)]
354    pub enum Icmpv6TimeExceededCode: u8 {
355        HopLimitExceeded, 0, "Hop Limit Exceeded";
356        FragmentReassemblyTimeExceeded, 1, "Fragment Reassembly Time Exceeded";
357    }
358);
359
360impl_icmp_message!(Ipv6, IcmpTimeExceeded, TimeExceeded, Icmpv6TimeExceededCode, OriginalPacket<B>);
361
362create_protocol_enum!(
363    #[allow(missing_docs)]
364    #[derive(Copy, Clone, PartialEq, Eq)]
365    pub enum Icmpv6ParameterProblemCode: u8 {
366        ErroneousHeaderField, 0, "Erroneous Header Field";
367        UnrecognizedNextHeaderType, 1, "Unrecognized Next Header Type";
368        UnrecognizedIpv6Option, 2, "Unrecognized IPv6 Option";
369    }
370);
371
372/// An ICMPv6 Parameter Problem message.
373#[derive(
374    Copy, Clone, Debug, Eq, PartialEq, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned,
375)]
376#[repr(C)]
377pub struct Icmpv6ParameterProblem {
378    pointer: U32,
379}
380
381impl Icmpv6ParameterProblem {
382    /// Returns a new `Icmpv6ParameterProblem` with the given pointer.
383    pub fn new(pointer: u32) -> Icmpv6ParameterProblem {
384        Icmpv6ParameterProblem { pointer: U32::new(pointer) }
385    }
386
387    /// Gets the pointer of the ICMPv6 Parameter Problem message.
388    pub fn pointer(self) -> u32 {
389        self.pointer.get()
390    }
391}
392
393impl_icmp_message!(
394    Ipv6,
395    Icmpv6ParameterProblem,
396    ParameterProblem,
397    Icmpv6ParameterProblemCode,
398    OriginalPacket<B>
399);
400
401#[cfg(test)]
402mod tests {
403    use core::fmt::Debug;
404    use packet::{InnerPacketBuilder, ParseBuffer, Serializer};
405
406    use super::*;
407    use crate::icmp::{IcmpMessage, MessageBody};
408    use crate::ipv6::{Ipv6Header, Ipv6Packet, Ipv6PacketBuilder};
409
410    fn serialize_to_bytes<B: SplitByteSlice + Debug, M: IcmpMessage<Ipv6> + Debug>(
411        src_ip: Ipv6Addr,
412        dst_ip: Ipv6Addr,
413        icmp: &IcmpPacket<Ipv6, B, M>,
414        builder: Ipv6PacketBuilder,
415    ) -> Vec<u8> {
416        let (header, body) = icmp.message_body.bytes();
417        let body = if let Some(b) = body { b } else { &[] };
418        let complete_msg = &[header, body].concat();
419
420        complete_msg
421            .into_serializer()
422            .wrap_in(icmp.builder(src_ip, dst_ip))
423            .wrap_in(builder)
424            .serialize_vec_outer()
425            .unwrap()
426            .as_ref()
427            .to_vec()
428    }
429
430    fn test_parse_and_serialize<
431        M: IcmpMessage<Ipv6> + Debug,
432        F: for<'a> FnOnce(&IcmpPacket<Ipv6, &'a [u8], M>),
433    >(
434        mut req: &[u8],
435        check: F,
436    ) {
437        let orig_req = req;
438
439        let ip = req.parse::<Ipv6Packet<_>>().unwrap();
440        let mut body = ip.body();
441        let icmp = body
442            .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
443            .unwrap();
444        check(&icmp);
445
446        let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
447        assert_eq!(&data[..], orig_req);
448    }
449
450    #[test]
451    fn test_parse_and_serialize_echo_request() {
452        use crate::testdata::icmp_echo_v6::*;
453        test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
454            let (inner_header, inner_body) = icmp.message_body.bytes();
455            assert!(inner_body.is_none());
456            let complete_msg = inner_header;
457            assert_eq!(complete_msg, ECHO_DATA);
458            assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
459            assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
460        });
461    }
462}