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