smoltcp/wire/
icmpv6.rs

1use byteorder::{ByteOrder, NetworkEndian};
2use core::{cmp, fmt};
3
4use super::{Error, Result};
5use crate::phy::ChecksumCapabilities;
6use crate::wire::ip::checksum;
7use crate::wire::MldRepr;
8#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
9use crate::wire::NdiscRepr;
10#[cfg(feature = "proto-rpl")]
11use crate::wire::RplRepr;
12use crate::wire::{IpProtocol, Ipv6Address, Ipv6Packet, Ipv6Repr};
13use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU};
14
15/// Error packets must not exceed min MTU
16const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN;
17
18enum_with_unknown! {
19    /// Internet protocol control message type.
20    pub enum Message(u8) {
21        /// Destination Unreachable.
22        DstUnreachable  = 0x01,
23        /// Packet Too Big.
24        PktTooBig       = 0x02,
25        /// Time Exceeded.
26        TimeExceeded    = 0x03,
27        /// Parameter Problem.
28        ParamProblem    = 0x04,
29        /// Echo Request
30        EchoRequest     = 0x80,
31        /// Echo Reply
32        EchoReply       = 0x81,
33        /// Multicast Listener Query
34        MldQuery        = 0x82,
35        /// Router Solicitation
36        RouterSolicit   = 0x85,
37        /// Router Advertisement
38        RouterAdvert    = 0x86,
39        /// Neighbor Solicitation
40        NeighborSolicit = 0x87,
41        /// Neighbor Advertisement
42        NeighborAdvert  = 0x88,
43        /// Redirect
44        Redirect        = 0x89,
45        /// Multicast Listener Report
46        MldReport       = 0x8f,
47        /// RPL Control Message
48        RplControl      = 0x9b,
49    }
50}
51
52impl Message {
53    /// Per [RFC 4443 § 2.1] ICMPv6 message types with the highest order
54    /// bit set are informational messages while message types without
55    /// the highest order bit set are error messages.
56    ///
57    /// [RFC 4443 § 2.1]: https://tools.ietf.org/html/rfc4443#section-2.1
58    pub fn is_error(&self) -> bool {
59        (u8::from(*self) & 0x80) != 0x80
60    }
61
62    /// Return a boolean value indicating if the given message type
63    /// is an [NDISC] message type.
64    ///
65    /// [NDISC]: https://tools.ietf.org/html/rfc4861
66    pub const fn is_ndisc(&self) -> bool {
67        match *self {
68            Message::RouterSolicit
69            | Message::RouterAdvert
70            | Message::NeighborSolicit
71            | Message::NeighborAdvert
72            | Message::Redirect => true,
73            _ => false,
74        }
75    }
76
77    /// Return a boolean value indicating if the given message type
78    /// is an [MLD] message type.
79    ///
80    /// [MLD]: https://tools.ietf.org/html/rfc3810
81    pub const fn is_mld(&self) -> bool {
82        match *self {
83            Message::MldQuery | Message::MldReport => true,
84            _ => false,
85        }
86    }
87}
88
89impl fmt::Display for Message {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        match *self {
92            Message::DstUnreachable => write!(f, "destination unreachable"),
93            Message::PktTooBig => write!(f, "packet too big"),
94            Message::TimeExceeded => write!(f, "time exceeded"),
95            Message::ParamProblem => write!(f, "parameter problem"),
96            Message::EchoReply => write!(f, "echo reply"),
97            Message::EchoRequest => write!(f, "echo request"),
98            Message::RouterSolicit => write!(f, "router solicitation"),
99            Message::RouterAdvert => write!(f, "router advertisement"),
100            Message::NeighborSolicit => write!(f, "neighbor solicitation"),
101            Message::NeighborAdvert => write!(f, "neighbor advert"),
102            Message::Redirect => write!(f, "redirect"),
103            Message::MldQuery => write!(f, "multicast listener query"),
104            Message::MldReport => write!(f, "multicast listener report"),
105            Message::RplControl => write!(f, "RPL control message"),
106            Message::Unknown(id) => write!(f, "{id}"),
107        }
108    }
109}
110
111enum_with_unknown! {
112    /// Internet protocol control message subtype for type "Destination Unreachable".
113    pub enum DstUnreachable(u8) {
114        /// No Route to destination.
115        NoRoute         = 0,
116        /// Communication with destination administratively prohibited.
117        AdminProhibit   = 1,
118        /// Beyond scope of source address.
119        BeyondScope     = 2,
120        /// Address unreachable.
121        AddrUnreachable = 3,
122        /// Port unreachable.
123        PortUnreachable = 4,
124        /// Source address failed ingress/egress policy.
125        FailedPolicy    = 5,
126        /// Reject route to destination.
127        RejectRoute     = 6
128    }
129}
130
131impl fmt::Display for DstUnreachable {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        match *self {
134            DstUnreachable::NoRoute => write!(f, "no route to destination"),
135            DstUnreachable::AdminProhibit => write!(
136                f,
137                "communication with destination administratively prohibited"
138            ),
139            DstUnreachable::BeyondScope => write!(f, "beyond scope of source address"),
140            DstUnreachable::AddrUnreachable => write!(f, "address unreachable"),
141            DstUnreachable::PortUnreachable => write!(f, "port unreachable"),
142            DstUnreachable::FailedPolicy => {
143                write!(f, "source address failed ingress/egress policy")
144            }
145            DstUnreachable::RejectRoute => write!(f, "reject route to destination"),
146            DstUnreachable::Unknown(id) => write!(f, "{id}"),
147        }
148    }
149}
150
151enum_with_unknown! {
152    /// Internet protocol control message subtype for the type "Parameter Problem".
153    pub enum ParamProblem(u8) {
154        /// Erroneous header field encountered.
155        ErroneousHdrField  = 0,
156        /// Unrecognized Next Header type encountered.
157        UnrecognizedNxtHdr = 1,
158        /// Unrecognized IPv6 option encountered.
159        UnrecognizedOption = 2
160    }
161}
162
163impl fmt::Display for ParamProblem {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        match *self {
166            ParamProblem::ErroneousHdrField => write!(f, "erroneous header field."),
167            ParamProblem::UnrecognizedNxtHdr => write!(f, "unrecognized next header type."),
168            ParamProblem::UnrecognizedOption => write!(f, "unrecognized IPv6 option."),
169            ParamProblem::Unknown(id) => write!(f, "{id}"),
170        }
171    }
172}
173
174enum_with_unknown! {
175    /// Internet protocol control message subtype for the type "Time Exceeded".
176    pub enum TimeExceeded(u8) {
177        /// Hop limit exceeded in transit.
178        HopLimitExceeded    = 0,
179        /// Fragment reassembly time exceeded.
180        FragReassemExceeded = 1
181    }
182}
183
184impl fmt::Display for TimeExceeded {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        match *self {
187            TimeExceeded::HopLimitExceeded => write!(f, "hop limit exceeded in transit"),
188            TimeExceeded::FragReassemExceeded => write!(f, "fragment reassembly time exceeded"),
189            TimeExceeded::Unknown(id) => write!(f, "{id}"),
190        }
191    }
192}
193
194/// A read/write wrapper around an Internet Control Message Protocol version 6 packet buffer.
195#[derive(Debug, PartialEq, Eq, Clone)]
196#[cfg_attr(feature = "defmt", derive(defmt::Format))]
197pub struct Packet<T: AsRef<[u8]>> {
198    pub(super) buffer: T,
199}
200
201// Ranges and constants describing key boundaries in the ICMPv6 header.
202pub(super) mod field {
203    use crate::wire::field::*;
204
205    // ICMPv6: See https://tools.ietf.org/html/rfc4443
206    pub const TYPE: usize = 0;
207    pub const CODE: usize = 1;
208    pub const CHECKSUM: Field = 2..4;
209
210    pub const UNUSED: Field = 4..8;
211    pub const MTU: Field = 4..8;
212    pub const POINTER: Field = 4..8;
213    pub const ECHO_IDENT: Field = 4..6;
214    pub const ECHO_SEQNO: Field = 6..8;
215
216    pub const HEADER_END: usize = 8;
217
218    // NDISC: See https://tools.ietf.org/html/rfc4861
219    // Router Advertisement message offsets
220    pub const CUR_HOP_LIMIT: usize = 4;
221    pub const ROUTER_FLAGS: usize = 5;
222    pub const ROUTER_LT: Field = 6..8;
223    pub const REACHABLE_TM: Field = 8..12;
224    pub const RETRANS_TM: Field = 12..16;
225
226    // Neighbor Solicitation message offsets
227    pub const TARGET_ADDR: Field = 8..24;
228
229    // Neighbor Advertisement message offsets
230    pub const NEIGH_FLAGS: usize = 4;
231
232    // Redirected Header message offsets
233    pub const DEST_ADDR: Field = 24..40;
234
235    // MLD:
236    //   - https://tools.ietf.org/html/rfc3810
237    //   - https://tools.ietf.org/html/rfc3810
238    // Multicast Listener Query message
239    pub const MAX_RESP_CODE: Field = 4..6;
240    pub const QUERY_RESV: Field = 6..8;
241    pub const QUERY_MCAST_ADDR: Field = 8..24;
242    pub const SQRV: usize = 24;
243    pub const QQIC: usize = 25;
244    pub const QUERY_NUM_SRCS: Field = 26..28;
245
246    // Multicast Listener Report Message
247    pub const RECORD_RESV: Field = 4..6;
248    pub const NR_MCAST_RCRDS: Field = 6..8;
249
250    // Multicast Address Record Offsets
251    pub const RECORD_TYPE: usize = 0;
252    pub const AUX_DATA_LEN: usize = 1;
253    pub const RECORD_NUM_SRCS: Field = 2..4;
254    pub const RECORD_MCAST_ADDR: Field = 4..20;
255}
256
257impl<T: AsRef<[u8]>> Packet<T> {
258    /// Imbue a raw octet buffer with ICMPv6 packet structure.
259    pub const fn new_unchecked(buffer: T) -> Packet<T> {
260        Packet { buffer }
261    }
262
263    /// Shorthand for a combination of [new_unchecked] and [check_len].
264    ///
265    /// [new_unchecked]: #method.new_unchecked
266    /// [check_len]: #method.check_len
267    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
268        let packet = Self::new_unchecked(buffer);
269        packet.check_len()?;
270        Ok(packet)
271    }
272
273    /// Ensure that no accessor method will panic if called.
274    /// Returns `Err(Error)` if the buffer is too short.
275    pub fn check_len(&self) -> Result<()> {
276        let len = self.buffer.as_ref().len();
277
278        if len < 4 {
279            return Err(Error);
280        }
281
282        match self.msg_type() {
283            Message::DstUnreachable
284            | Message::PktTooBig
285            | Message::TimeExceeded
286            | Message::ParamProblem
287            | Message::EchoRequest
288            | Message::EchoReply
289            | Message::MldQuery
290            | Message::RouterSolicit
291            | Message::RouterAdvert
292            | Message::NeighborSolicit
293            | Message::NeighborAdvert
294            | Message::Redirect
295            | Message::MldReport => {
296                if len < field::HEADER_END || len < self.header_len() {
297                    return Err(Error);
298                }
299            }
300            #[cfg(feature = "proto-rpl")]
301            Message::RplControl => match super::rpl::RplControlMessage::from(self.msg_code()) {
302                super::rpl::RplControlMessage::DodagInformationSolicitation => {
303                    // TODO(thvdveld): replace magic number
304                    if len < 6 {
305                        return Err(Error);
306                    }
307                }
308                super::rpl::RplControlMessage::DodagInformationObject => {
309                    // TODO(thvdveld): replace magic number
310                    if len < 28 {
311                        return Err(Error);
312                    }
313                }
314                super::rpl::RplControlMessage::DestinationAdvertisementObject => {
315                    // TODO(thvdveld): replace magic number
316                    if len < 8 || (self.dao_dodag_id_present() && len < 24) {
317                        return Err(Error);
318                    }
319                }
320                super::rpl::RplControlMessage::DestinationAdvertisementObjectAck => {
321                    // TODO(thvdveld): replace magic number
322                    if len < 8 || (self.dao_dodag_id_present() && len < 24) {
323                        return Err(Error);
324                    }
325                }
326                super::rpl::RplControlMessage::SecureDodagInformationSolicitation
327                | super::rpl::RplControlMessage::SecureDodagInformationObject
328                | super::rpl::RplControlMessage::SecureDestinationAdvertisementObject
329                | super::rpl::RplControlMessage::SecureDestinationAdvertisementObjectAck
330                | super::rpl::RplControlMessage::ConsistencyCheck => return Err(Error),
331                super::rpl::RplControlMessage::Unknown(_) => return Err(Error),
332            },
333            #[cfg(not(feature = "proto-rpl"))]
334            Message::RplControl => return Err(Error),
335            Message::Unknown(_) => return Err(Error),
336        }
337
338        Ok(())
339    }
340
341    /// Consume the packet, returning the underlying buffer.
342    pub fn into_inner(self) -> T {
343        self.buffer
344    }
345
346    /// Return the message type field.
347    #[inline]
348    pub fn msg_type(&self) -> Message {
349        let data = self.buffer.as_ref();
350        Message::from(data[field::TYPE])
351    }
352
353    /// Return the message code field.
354    #[inline]
355    pub fn msg_code(&self) -> u8 {
356        let data = self.buffer.as_ref();
357        data[field::CODE]
358    }
359
360    /// Return the checksum field.
361    #[inline]
362    pub fn checksum(&self) -> u16 {
363        let data = self.buffer.as_ref();
364        NetworkEndian::read_u16(&data[field::CHECKSUM])
365    }
366
367    /// Return the identifier field (for echo request and reply packets).
368    #[inline]
369    pub fn echo_ident(&self) -> u16 {
370        let data = self.buffer.as_ref();
371        NetworkEndian::read_u16(&data[field::ECHO_IDENT])
372    }
373
374    /// Return the sequence number field (for echo request and reply packets).
375    #[inline]
376    pub fn echo_seq_no(&self) -> u16 {
377        let data = self.buffer.as_ref();
378        NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
379    }
380
381    /// Return the MTU field (for packet too big messages).
382    #[inline]
383    pub fn pkt_too_big_mtu(&self) -> u32 {
384        let data = self.buffer.as_ref();
385        NetworkEndian::read_u32(&data[field::MTU])
386    }
387
388    /// Return the pointer field (for parameter problem messages).
389    #[inline]
390    pub fn param_problem_ptr(&self) -> u32 {
391        let data = self.buffer.as_ref();
392        NetworkEndian::read_u32(&data[field::POINTER])
393    }
394
395    /// Return the header length. The result depends on the value of
396    /// the message type field.
397    pub fn header_len(&self) -> usize {
398        match self.msg_type() {
399            Message::DstUnreachable => field::UNUSED.end,
400            Message::PktTooBig => field::MTU.end,
401            Message::TimeExceeded => field::UNUSED.end,
402            Message::ParamProblem => field::POINTER.end,
403            Message::EchoRequest => field::ECHO_SEQNO.end,
404            Message::EchoReply => field::ECHO_SEQNO.end,
405            Message::RouterSolicit => field::UNUSED.end,
406            Message::RouterAdvert => field::RETRANS_TM.end,
407            Message::NeighborSolicit => field::TARGET_ADDR.end,
408            Message::NeighborAdvert => field::TARGET_ADDR.end,
409            Message::Redirect => field::DEST_ADDR.end,
410            Message::MldQuery => field::QUERY_NUM_SRCS.end,
411            Message::MldReport => field::NR_MCAST_RCRDS.end,
412            // For packets that are not included in RFC 4443, do not
413            // include the last 32 bits of the ICMPv6 header in
414            // `header_bytes`. This must be done so that these bytes
415            // can be accessed in the `payload`.
416            _ => field::CHECKSUM.end,
417        }
418    }
419
420    /// Validate the header checksum.
421    ///
422    /// # Fuzzing
423    /// This function always returns `true` when fuzzing.
424    pub fn verify_checksum(&self, src_addr: &Ipv6Address, dst_addr: &Ipv6Address) -> bool {
425        if cfg!(fuzzing) {
426            return true;
427        }
428
429        let data = self.buffer.as_ref();
430        checksum::combine(&[
431            checksum::pseudo_header_v6(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
432            checksum::data(data),
433        ]) == !0
434    }
435}
436
437impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
438    /// Return a pointer to the type-specific data.
439    #[inline]
440    pub fn payload(&self) -> &'a [u8] {
441        let data = self.buffer.as_ref();
442        &data[self.header_len()..]
443    }
444}
445
446impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
447    /// Set the message type field.
448    #[inline]
449    pub fn set_msg_type(&mut self, value: Message) {
450        let data = self.buffer.as_mut();
451        data[field::TYPE] = value.into()
452    }
453
454    /// Set the message code field.
455    #[inline]
456    pub fn set_msg_code(&mut self, value: u8) {
457        let data = self.buffer.as_mut();
458        data[field::CODE] = value
459    }
460
461    /// Clear any reserved fields in the message header.
462    ///
463    /// # Panics
464    /// This function panics if the message type has not been set.
465    /// See [set_msg_type].
466    ///
467    /// [set_msg_type]: #method.set_msg_type
468    #[inline]
469    pub fn clear_reserved(&mut self) {
470        match self.msg_type() {
471            Message::RouterSolicit
472            | Message::NeighborSolicit
473            | Message::NeighborAdvert
474            | Message::Redirect => {
475                let data = self.buffer.as_mut();
476                NetworkEndian::write_u32(&mut data[field::UNUSED], 0);
477            }
478            Message::MldQuery => {
479                let data = self.buffer.as_mut();
480                NetworkEndian::write_u16(&mut data[field::QUERY_RESV], 0);
481                data[field::SQRV] &= 0xf;
482            }
483            Message::MldReport => {
484                let data = self.buffer.as_mut();
485                NetworkEndian::write_u16(&mut data[field::RECORD_RESV], 0);
486            }
487            ty => panic!("Message type `{ty}` does not have any reserved fields."),
488        }
489    }
490
491    #[inline]
492    pub fn set_checksum(&mut self, value: u16) {
493        let data = self.buffer.as_mut();
494        NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
495    }
496
497    /// Set the identifier field (for echo request and reply packets).
498    ///
499    /// # Panics
500    /// This function may panic if this packet is not an echo request or reply packet.
501    #[inline]
502    pub fn set_echo_ident(&mut self, value: u16) {
503        let data = self.buffer.as_mut();
504        NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
505    }
506
507    /// Set the sequence number field (for echo request and reply packets).
508    ///
509    /// # Panics
510    /// This function may panic if this packet is not an echo request or reply packet.
511    #[inline]
512    pub fn set_echo_seq_no(&mut self, value: u16) {
513        let data = self.buffer.as_mut();
514        NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
515    }
516
517    /// Set the MTU field (for packet too big messages).
518    ///
519    /// # Panics
520    /// This function may panic if this packet is not an packet too big packet.
521    #[inline]
522    pub fn set_pkt_too_big_mtu(&mut self, value: u32) {
523        let data = self.buffer.as_mut();
524        NetworkEndian::write_u32(&mut data[field::MTU], value)
525    }
526
527    /// Set the pointer field (for parameter problem messages).
528    ///
529    /// # Panics
530    /// This function may panic if this packet is not a parameter problem message.
531    #[inline]
532    pub fn set_param_problem_ptr(&mut self, value: u32) {
533        let data = self.buffer.as_mut();
534        NetworkEndian::write_u32(&mut data[field::POINTER], value)
535    }
536
537    /// Compute and fill in the header checksum.
538    pub fn fill_checksum(&mut self, src_addr: &Ipv6Address, dst_addr: &Ipv6Address) {
539        self.set_checksum(0);
540        let checksum = {
541            let data = self.buffer.as_ref();
542            !checksum::combine(&[
543                checksum::pseudo_header_v6(
544                    src_addr,
545                    dst_addr,
546                    IpProtocol::Icmpv6,
547                    data.len() as u32,
548                ),
549                checksum::data(data),
550            ])
551        };
552        self.set_checksum(checksum)
553    }
554
555    /// Return a mutable pointer to the type-specific data.
556    #[inline]
557    pub fn payload_mut(&mut self) -> &mut [u8] {
558        let range = self.header_len()..;
559        let data = self.buffer.as_mut();
560        &mut data[range]
561    }
562}
563
564impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
565    fn as_ref(&self) -> &[u8] {
566        self.buffer.as_ref()
567    }
568}
569
570/// A high-level representation of an Internet Control Message Protocol version 6 packet header.
571#[derive(Debug, PartialEq, Eq, Clone, Copy)]
572#[cfg_attr(feature = "defmt", derive(defmt::Format))]
573#[non_exhaustive]
574pub enum Repr<'a> {
575    DstUnreachable {
576        reason: DstUnreachable,
577        header: Ipv6Repr,
578        data: &'a [u8],
579    },
580    PktTooBig {
581        mtu: u32,
582        header: Ipv6Repr,
583        data: &'a [u8],
584    },
585    TimeExceeded {
586        reason: TimeExceeded,
587        header: Ipv6Repr,
588        data: &'a [u8],
589    },
590    ParamProblem {
591        reason: ParamProblem,
592        pointer: u32,
593        header: Ipv6Repr,
594        data: &'a [u8],
595    },
596    EchoRequest {
597        ident: u16,
598        seq_no: u16,
599        data: &'a [u8],
600    },
601    EchoReply {
602        ident: u16,
603        seq_no: u16,
604        data: &'a [u8],
605    },
606    #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
607    Ndisc(NdiscRepr<'a>),
608    Mld(MldRepr<'a>),
609    #[cfg(feature = "proto-rpl")]
610    Rpl(RplRepr<'a>),
611}
612
613impl<'a> Repr<'a> {
614    /// Parse an Internet Control Message Protocol version 6 packet and return
615    /// a high-level representation.
616    pub fn parse<T>(
617        src_addr: &Ipv6Address,
618        dst_addr: &Ipv6Address,
619        packet: &Packet<&'a T>,
620        checksum_caps: &ChecksumCapabilities,
621    ) -> Result<Repr<'a>>
622    where
623        T: AsRef<[u8]> + ?Sized,
624    {
625        packet.check_len()?;
626
627        fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>) -> Result<(&'a [u8], Ipv6Repr)>
628        where
629            T: AsRef<[u8]> + ?Sized,
630        {
631            // The packet must be truncated to fit the min MTU. Since we don't know the offset of
632            // the ICMPv6 header in the L2 frame, we should only check whether the payload's IPv6
633            // header is present, the rest is allowed to be truncated.
634            let ip_packet = if packet.payload().len() >= IPV6_HEADER_LEN {
635                Ipv6Packet::new_unchecked(packet.payload())
636            } else {
637                return Err(Error);
638            };
639
640            let payload = &packet.payload()[ip_packet.header_len()..];
641            let repr = Ipv6Repr {
642                src_addr: ip_packet.src_addr(),
643                dst_addr: ip_packet.dst_addr(),
644                next_header: ip_packet.next_header(),
645                payload_len: ip_packet.payload_len().into(),
646                hop_limit: ip_packet.hop_limit(),
647            };
648            Ok((payload, repr))
649        }
650        // Valid checksum is expected.
651        if checksum_caps.icmpv6.rx() && !packet.verify_checksum(src_addr, dst_addr) {
652            return Err(Error);
653        }
654
655        match (packet.msg_type(), packet.msg_code()) {
656            (Message::DstUnreachable, code) => {
657                let (payload, repr) = create_packet_from_payload(packet)?;
658                Ok(Repr::DstUnreachable {
659                    reason: DstUnreachable::from(code),
660                    header: repr,
661                    data: payload,
662                })
663            }
664            (Message::PktTooBig, 0) => {
665                let (payload, repr) = create_packet_from_payload(packet)?;
666                Ok(Repr::PktTooBig {
667                    mtu: packet.pkt_too_big_mtu(),
668                    header: repr,
669                    data: payload,
670                })
671            }
672            (Message::TimeExceeded, code) => {
673                let (payload, repr) = create_packet_from_payload(packet)?;
674                Ok(Repr::TimeExceeded {
675                    reason: TimeExceeded::from(code),
676                    header: repr,
677                    data: payload,
678                })
679            }
680            (Message::ParamProblem, code) => {
681                let (payload, repr) = create_packet_from_payload(packet)?;
682                Ok(Repr::ParamProblem {
683                    reason: ParamProblem::from(code),
684                    pointer: packet.param_problem_ptr(),
685                    header: repr,
686                    data: payload,
687                })
688            }
689            (Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
690                ident: packet.echo_ident(),
691                seq_no: packet.echo_seq_no(),
692                data: packet.payload(),
693            }),
694            (Message::EchoReply, 0) => Ok(Repr::EchoReply {
695                ident: packet.echo_ident(),
696                seq_no: packet.echo_seq_no(),
697                data: packet.payload(),
698            }),
699            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
700            (msg_type, 0) if msg_type.is_ndisc() => NdiscRepr::parse(packet).map(Repr::Ndisc),
701            (msg_type, 0) if msg_type.is_mld() => MldRepr::parse(packet).map(Repr::Mld),
702            #[cfg(feature = "proto-rpl")]
703            (Message::RplControl, _) => RplRepr::parse(packet).map(Repr::Rpl),
704            _ => Err(Error),
705        }
706    }
707
708    /// Return the length of a packet that will be emitted from this high-level representation.
709    pub fn buffer_len(&self) -> usize {
710        match self {
711            &Repr::DstUnreachable { header, data, .. }
712            | &Repr::PktTooBig { header, data, .. }
713            | &Repr::TimeExceeded { header, data, .. }
714            | &Repr::ParamProblem { header, data, .. } => cmp::min(
715                field::UNUSED.end + header.buffer_len() + data.len(),
716                MAX_ERROR_PACKET_LEN,
717            ),
718            &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
719                field::ECHO_SEQNO.end + data.len()
720            }
721            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
722            &Repr::Ndisc(ndisc) => ndisc.buffer_len(),
723            &Repr::Mld(mld) => mld.buffer_len(),
724            #[cfg(feature = "proto-rpl")]
725            Repr::Rpl(rpl) => rpl.buffer_len(),
726        }
727    }
728
729    /// Emit a high-level representation into an Internet Control Message Protocol version 6
730    /// packet.
731    pub fn emit<T>(
732        &self,
733        src_addr: &Ipv6Address,
734        dst_addr: &Ipv6Address,
735        packet: &mut Packet<&mut T>,
736        checksum_caps: &ChecksumCapabilities,
737    ) where
738        T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
739    {
740        fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
741        where
742            T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
743        {
744            let icmp_header_len = packet.header_len();
745            let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
746            header.emit(&mut ip_packet);
747            let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
748            // FIXME: this should rather be checked at link level, as we can't know in advance how
749            // much space we have for the packet due to IPv6 options and etc
750            let payload_len = cmp::min(
751                data.len(),
752                MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
753            );
754            payload[..payload_len].copy_from_slice(&data[..payload_len]);
755        }
756
757        match *self {
758            Repr::DstUnreachable {
759                reason,
760                header,
761                data,
762            } => {
763                packet.set_msg_type(Message::DstUnreachable);
764                packet.set_msg_code(reason.into());
765
766                emit_contained_packet(packet, header, data);
767            }
768
769            Repr::PktTooBig { mtu, header, data } => {
770                packet.set_msg_type(Message::PktTooBig);
771                packet.set_msg_code(0);
772                packet.set_pkt_too_big_mtu(mtu);
773
774                emit_contained_packet(packet, header, data);
775            }
776
777            Repr::TimeExceeded {
778                reason,
779                header,
780                data,
781            } => {
782                packet.set_msg_type(Message::TimeExceeded);
783                packet.set_msg_code(reason.into());
784
785                emit_contained_packet(packet, header, data);
786            }
787
788            Repr::ParamProblem {
789                reason,
790                pointer,
791                header,
792                data,
793            } => {
794                packet.set_msg_type(Message::ParamProblem);
795                packet.set_msg_code(reason.into());
796                packet.set_param_problem_ptr(pointer);
797
798                emit_contained_packet(packet, header, data);
799            }
800
801            Repr::EchoRequest {
802                ident,
803                seq_no,
804                data,
805            } => {
806                packet.set_msg_type(Message::EchoRequest);
807                packet.set_msg_code(0);
808                packet.set_echo_ident(ident);
809                packet.set_echo_seq_no(seq_no);
810                let data_len = cmp::min(packet.payload_mut().len(), data.len());
811                packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
812            }
813
814            Repr::EchoReply {
815                ident,
816                seq_no,
817                data,
818            } => {
819                packet.set_msg_type(Message::EchoReply);
820                packet.set_msg_code(0);
821                packet.set_echo_ident(ident);
822                packet.set_echo_seq_no(seq_no);
823                let data_len = cmp::min(packet.payload_mut().len(), data.len());
824                packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
825            }
826
827            #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
828            Repr::Ndisc(ndisc) => ndisc.emit(packet),
829
830            Repr::Mld(mld) => mld.emit(packet),
831
832            #[cfg(feature = "proto-rpl")]
833            Repr::Rpl(ref rpl) => rpl.emit(packet),
834        }
835
836        if checksum_caps.icmpv6.tx() {
837            packet.fill_checksum(src_addr, dst_addr);
838        } else {
839            // make sure we get a consistently zeroed checksum, since implementations might rely on it
840            packet.set_checksum(0);
841        }
842    }
843}
844
845#[cfg(test)]
846mod test {
847    use super::*;
848    use crate::wire::{IpProtocol, Ipv6Address, Ipv6Repr};
849
850    const MOCK_IP_ADDR_1: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
851    const MOCK_IP_ADDR_2: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
852
853    static ECHO_PACKET_BYTES: [u8; 12] = [
854        0x80, 0x00, 0x19, 0xb3, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
855    ];
856
857    static ECHO_PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
858
859    static PKT_TOO_BIG_BYTES: [u8; 60] = [
860        0x02, 0x00, 0x0f, 0xc9, 0x00, 0x00, 0x05, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11,
861        0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
862        0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
863        0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
864    ];
865
866    static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] = [
867        0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
868        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
869        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00,
870        0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
871    ];
872
873    static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] = [
874        0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
875    ];
876
877    fn echo_packet_repr() -> Repr<'static> {
878        Repr::EchoRequest {
879            ident: 0x1234,
880            seq_no: 0xabcd,
881            data: &ECHO_PACKET_PAYLOAD,
882        }
883    }
884
885    fn too_big_packet_repr() -> Repr<'static> {
886        Repr::PktTooBig {
887            mtu: 1500,
888            header: Ipv6Repr {
889                src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
890                dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
891                next_header: IpProtocol::Udp,
892                payload_len: 12,
893                hop_limit: 0x40,
894            },
895            data: &PKT_TOO_BIG_UDP_PAYLOAD,
896        }
897    }
898
899    #[test]
900    fn test_echo_deconstruct() {
901        let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
902        assert_eq!(packet.msg_type(), Message::EchoRequest);
903        assert_eq!(packet.msg_code(), 0);
904        assert_eq!(packet.checksum(), 0x19b3);
905        assert_eq!(packet.echo_ident(), 0x1234);
906        assert_eq!(packet.echo_seq_no(), 0xabcd);
907        assert_eq!(packet.payload(), &ECHO_PACKET_PAYLOAD[..]);
908        assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
909        assert!(!packet.msg_type().is_error());
910    }
911
912    #[test]
913    fn test_echo_construct() {
914        let mut bytes = vec![0xa5; 12];
915        let mut packet = Packet::new_unchecked(&mut bytes);
916        packet.set_msg_type(Message::EchoRequest);
917        packet.set_msg_code(0);
918        packet.set_echo_ident(0x1234);
919        packet.set_echo_seq_no(0xabcd);
920        packet
921            .payload_mut()
922            .copy_from_slice(&ECHO_PACKET_PAYLOAD[..]);
923        packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
924        assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
925    }
926
927    #[test]
928    fn test_echo_repr_parse() {
929        let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
930        let repr = Repr::parse(
931            &MOCK_IP_ADDR_1,
932            &MOCK_IP_ADDR_2,
933            &packet,
934            &ChecksumCapabilities::default(),
935        )
936        .unwrap();
937        assert_eq!(repr, echo_packet_repr());
938    }
939
940    #[test]
941    fn test_echo_emit() {
942        let repr = echo_packet_repr();
943        let mut bytes = vec![0xa5; repr.buffer_len()];
944        let mut packet = Packet::new_unchecked(&mut bytes);
945        repr.emit(
946            &MOCK_IP_ADDR_1,
947            &MOCK_IP_ADDR_2,
948            &mut packet,
949            &ChecksumCapabilities::default(),
950        );
951        assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
952    }
953
954    #[test]
955    fn test_too_big_deconstruct() {
956        let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
957        assert_eq!(packet.msg_type(), Message::PktTooBig);
958        assert_eq!(packet.msg_code(), 0);
959        assert_eq!(packet.checksum(), 0x0fc9);
960        assert_eq!(packet.pkt_too_big_mtu(), 1500);
961        assert_eq!(packet.payload(), &PKT_TOO_BIG_IP_PAYLOAD[..]);
962        assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
963        assert!(packet.msg_type().is_error());
964    }
965
966    #[test]
967    fn test_too_big_construct() {
968        let mut bytes = vec![0xa5; 60];
969        let mut packet = Packet::new_unchecked(&mut bytes);
970        packet.set_msg_type(Message::PktTooBig);
971        packet.set_msg_code(0);
972        packet.set_pkt_too_big_mtu(1500);
973        packet
974            .payload_mut()
975            .copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]);
976        packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
977        assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
978    }
979
980    #[test]
981    fn test_too_big_repr_parse() {
982        let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
983        let repr = Repr::parse(
984            &MOCK_IP_ADDR_1,
985            &MOCK_IP_ADDR_2,
986            &packet,
987            &ChecksumCapabilities::default(),
988        )
989        .unwrap();
990        assert_eq!(repr, too_big_packet_repr());
991    }
992
993    #[test]
994    fn test_too_big_emit() {
995        let repr = too_big_packet_repr();
996        let mut bytes = vec![0xa5; repr.buffer_len()];
997        let mut packet = Packet::new_unchecked(&mut bytes);
998        repr.emit(
999            &MOCK_IP_ADDR_1,
1000            &MOCK_IP_ADDR_2,
1001            &mut packet,
1002            &ChecksumCapabilities::default(),
1003        );
1004        assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
1005    }
1006
1007    #[test]
1008    fn test_buffer_length_is_truncated_to_mtu() {
1009        let repr = Repr::PktTooBig {
1010            mtu: 1280,
1011            header: Ipv6Repr {
1012                src_addr: Ipv6Address::UNSPECIFIED,
1013                dst_addr: Ipv6Address::UNSPECIFIED,
1014                next_header: IpProtocol::Tcp,
1015                hop_limit: 64,
1016                payload_len: 1280,
1017            },
1018            data: &vec![0; 9999],
1019        };
1020        assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN);
1021    }
1022
1023    #[test]
1024    fn test_mtu_truncated_payload_roundtrip() {
1025        let ip_packet_repr = Ipv6Repr {
1026            src_addr: Ipv6Address::UNSPECIFIED,
1027            dst_addr: Ipv6Address::UNSPECIFIED,
1028            next_header: IpProtocol::Tcp,
1029            hop_limit: 64,
1030            payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN,
1031        };
1032        let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]);
1033        ip_packet_repr.emit(&mut ip_packet);
1034
1035        let repr1 = Repr::PktTooBig {
1036            mtu: IPV6_MIN_MTU as u32,
1037            header: ip_packet_repr,
1038            data: &ip_packet.as_ref()[IPV6_HEADER_LEN..],
1039        };
1040        // this is needed to make sure roundtrip gives the same value
1041        // it is not needed for ensuring the correct bytes get emitted
1042        let repr1 = Repr::PktTooBig {
1043            mtu: IPV6_MIN_MTU as u32,
1044            header: ip_packet_repr,
1045            data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end],
1046        };
1047        let mut data = vec![0; MAX_ERROR_PACKET_LEN];
1048        let mut packet = Packet::new_unchecked(&mut data);
1049        repr1.emit(
1050            &MOCK_IP_ADDR_1,
1051            &MOCK_IP_ADDR_2,
1052            &mut packet,
1053            &ChecksumCapabilities::default(),
1054        );
1055
1056        let packet = Packet::new_unchecked(&data);
1057        let repr2 = Repr::parse(
1058            &MOCK_IP_ADDR_1,
1059            &MOCK_IP_ADDR_2,
1060            &packet,
1061            &ChecksumCapabilities::default(),
1062        )
1063        .unwrap();
1064
1065        assert_eq!(repr1, repr2);
1066    }
1067
1068    #[test]
1069    fn test_truncated_payload_ipv6_header_parse_fails() {
1070        let repr = too_big_packet_repr();
1071        let mut bytes = vec![0xa5; repr.buffer_len()];
1072        let mut packet = Packet::new_unchecked(&mut bytes);
1073        repr.emit(
1074            &MOCK_IP_ADDR_1,
1075            &MOCK_IP_ADDR_2,
1076            &mut packet,
1077            &ChecksumCapabilities::default(),
1078        );
1079        let packet = Packet::new_unchecked(&bytes[..field::HEADER_END + IPV6_HEADER_LEN - 1]);
1080        assert!(Repr::parse(
1081            &MOCK_IP_ADDR_1,
1082            &MOCK_IP_ADDR_2,
1083            &packet,
1084            &ChecksumCapabilities::ignored(),
1085        )
1086        .is_err());
1087    }
1088}