Skip to main content

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_error_or_redirect(self) -> bool {
301        match self {
302            Icmpv6MessageType::DestUnreachable
303            | Icmpv6MessageType::PacketTooBig
304            | Icmpv6MessageType::TimeExceeded
305            | Icmpv6MessageType::ParameterProblem
306            | Icmpv6MessageType::Redirect => true,
307            _ => false,
308        }
309    }
310}
311
312create_protocol_enum!(
313    #[allow(missing_docs)]
314    #[derive(Copy, Clone, PartialEq, Eq)]
315    pub enum Icmpv6DestUnreachableCode: u8 {
316        NoRoute, 0, "No Route";
317        CommAdministrativelyProhibited, 1, "Comm Administratively Prohibited";
318        BeyondScope, 2, "Beyond Scope";
319        AddrUnreachable, 3, "Address Unreachable";
320        PortUnreachable, 4, "Port Unreachable";
321        SrcAddrFailedPolicy, 5, "Source Address Failed Policy";
322        RejectRoute, 6, "Reject Route";
323    }
324);
325
326impl_icmp_message!(
327    Ipv6,
328    IcmpDestUnreachable,
329    DestUnreachable,
330    Icmpv6DestUnreachableCode,
331    OriginalPacket<B>
332);
333
334/// An ICMPv6 Packet Too Big message.
335#[derive(
336    Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, PartialEq,
337)]
338#[repr(C)]
339pub struct Icmpv6PacketTooBig {
340    mtu: U32,
341}
342
343impl Icmpv6PacketTooBig {
344    /// Returns a new `Icmpv6PacketTooBig` with the given MTU value.
345    pub fn new(mtu: u32) -> Icmpv6PacketTooBig {
346        Icmpv6PacketTooBig { mtu: U32::new(mtu) }
347    }
348
349    /// Get the mtu value.
350    pub fn mtu(&self) -> u32 {
351        self.mtu.get()
352    }
353}
354
355impl_icmp_message!(Ipv6, Icmpv6PacketTooBig, PacketTooBig, IcmpZeroCode, OriginalPacket<B>);
356
357create_protocol_enum!(
358    #[allow(missing_docs)]
359    #[derive(Copy, Clone, PartialEq, Eq)]
360    pub enum Icmpv6TimeExceededCode: u8 {
361        HopLimitExceeded, 0, "Hop Limit Exceeded";
362        FragmentReassemblyTimeExceeded, 1, "Fragment Reassembly Time Exceeded";
363    }
364);
365
366impl_icmp_message!(Ipv6, IcmpTimeExceeded, TimeExceeded, Icmpv6TimeExceededCode, OriginalPacket<B>);
367
368create_protocol_enum!(
369    #[allow(missing_docs)]
370    #[derive(Copy, Clone, PartialEq, Eq)]
371    pub enum Icmpv6ParameterProblemCode: u8 {
372        ErroneousHeaderField, 0, "Erroneous Header Field";
373        UnrecognizedNextHeaderType, 1, "Unrecognized Next Header Type";
374        UnrecognizedIpv6Option, 2, "Unrecognized IPv6 Option";
375    }
376);
377
378/// An ICMPv6 Parameter Problem message.
379#[derive(
380    Copy, Clone, Debug, Eq, PartialEq, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned,
381)]
382#[repr(C)]
383pub struct Icmpv6ParameterProblem {
384    pointer: U32,
385}
386
387impl Icmpv6ParameterProblem {
388    /// Returns a new `Icmpv6ParameterProblem` with the given pointer.
389    pub fn new(pointer: u32) -> Icmpv6ParameterProblem {
390        Icmpv6ParameterProblem { pointer: U32::new(pointer) }
391    }
392
393    /// Gets the pointer of the ICMPv6 Parameter Problem message.
394    pub fn pointer(self) -> u32 {
395        self.pointer.get()
396    }
397}
398
399impl_icmp_message!(
400    Ipv6,
401    Icmpv6ParameterProblem,
402    ParameterProblem,
403    Icmpv6ParameterProblemCode,
404    OriginalPacket<B>
405);
406
407#[cfg(test)]
408mod tests {
409    use core::fmt::Debug;
410    use packet::{InnerPacketBuilder, ParseBuffer, Serializer};
411
412    use super::*;
413    use crate::icmp::{IcmpMessage, MessageBody};
414    use crate::ipv6::{Ipv6Header, Ipv6Packet, Ipv6PacketBuilder};
415
416    fn serialize_to_bytes<B: SplitByteSlice + Debug, M: IcmpMessage<Ipv6> + Debug>(
417        src_ip: Ipv6Addr,
418        dst_ip: Ipv6Addr,
419        icmp: &IcmpPacket<Ipv6, B, M>,
420        builder: Ipv6PacketBuilder,
421    ) -> Vec<u8> {
422        let (header, body) = icmp.message_body.bytes();
423        let body = if let Some(b) = body { b } else { &[] };
424        let complete_msg = &[header, body].concat();
425
426        complete_msg
427            .into_serializer()
428            .wrap_in(icmp.builder(src_ip, dst_ip))
429            .wrap_in(builder)
430            .serialize_vec_outer()
431            .unwrap()
432            .as_ref()
433            .to_vec()
434    }
435
436    fn test_parse_and_serialize<
437        M: IcmpMessage<Ipv6> + Debug,
438        F: for<'a> FnOnce(&IcmpPacket<Ipv6, &'a [u8], M>),
439    >(
440        mut req: &[u8],
441        check: F,
442    ) {
443        let orig_req = req;
444
445        let ip = req.parse::<Ipv6Packet<_>>().unwrap();
446        let mut body = ip.body();
447        let icmp = body
448            .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
449            .unwrap();
450        check(&icmp);
451
452        let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
453        assert_eq!(&data[..], orig_req);
454    }
455
456    #[test]
457    fn test_parse_and_serialize_echo_request() {
458        use crate::testdata::icmp_echo_v6::*;
459        test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
460            let (inner_header, inner_body) = icmp.message_body.bytes();
461            assert!(inner_body.is_none());
462            let complete_msg = inner_header;
463            assert_eq!(complete_msg, ECHO_DATA);
464            assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
465            assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
466        });
467    }
468}