nex_packet/
icmp.rs

1//! An ICMP packet abstraction.
2use crate::ipv4::IPV4_HEADER_LEN;
3use crate::{ethernet::ETHERNET_HEADER_LEN, packet::Packet};
4
5use bytes::{BufMut, Bytes, BytesMut};
6use nex_core::bitfield::u16be;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// ICMP Common Header Length.
11pub const ICMP_COMMON_HEADER_LEN: usize = 4;
12/// ICMPv4 Header Length. Including the common header (4 bytes) and the type specific header (4 bytes).
13pub const ICMPV4_HEADER_LEN: usize = 8;
14/// ICMPv4 Minimum Packet Length.
15pub const ICMPV4_PACKET_LEN: usize = ETHERNET_HEADER_LEN + IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
16/// ICMPv4 IP Packet Length.
17pub const ICMPV4_IP_PACKET_LEN: usize = IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
18
19/// Represents the "ICMP type" header field.
20#[repr(u8)]
21#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub enum IcmpType {
24    EchoReply,
25    DestinationUnreachable,
26    SourceQuench,
27    RedirectMessage,
28    EchoRequest,
29    RouterAdvertisement,
30    RouterSolicitation,
31    TimeExceeded,
32    ParameterProblem,
33    TimestampRequest,
34    TimestampReply,
35    InformationRequest,
36    InformationReply,
37    AddressMaskRequest,
38    AddressMaskReply,
39    Traceroute,
40    DatagramConversionError,
41    MobileHostRedirect,
42    IPv6WhereAreYou,
43    IPv6IAmHere,
44    MobileRegistrationRequest,
45    MobileRegistrationReply,
46    DomainNameRequest,
47    DomainNameReply,
48    SKIP,
49    Photuris,
50    Unknown(u8),
51}
52
53impl IcmpType {
54    /// Create a new `IcmpType` instance.
55    pub fn new(val: u8) -> IcmpType {
56        match val {
57            0 => IcmpType::EchoReply,
58            3 => IcmpType::DestinationUnreachable,
59            4 => IcmpType::SourceQuench,
60            5 => IcmpType::RedirectMessage,
61            8 => IcmpType::EchoRequest,
62            9 => IcmpType::RouterAdvertisement,
63            10 => IcmpType::RouterSolicitation,
64            11 => IcmpType::TimeExceeded,
65            12 => IcmpType::ParameterProblem,
66            13 => IcmpType::TimestampRequest,
67            14 => IcmpType::TimestampReply,
68            15 => IcmpType::InformationRequest,
69            16 => IcmpType::InformationReply,
70            17 => IcmpType::AddressMaskRequest,
71            18 => IcmpType::AddressMaskReply,
72            30 => IcmpType::Traceroute,
73            31 => IcmpType::DatagramConversionError,
74            32 => IcmpType::MobileHostRedirect,
75            33 => IcmpType::IPv6WhereAreYou,
76            34 => IcmpType::IPv6IAmHere,
77            35 => IcmpType::MobileRegistrationRequest,
78            36 => IcmpType::MobileRegistrationReply,
79            37 => IcmpType::DomainNameRequest,
80            38 => IcmpType::DomainNameReply,
81            39 => IcmpType::SKIP,
82            40 => IcmpType::Photuris,
83            n => IcmpType::Unknown(n),
84        }
85    }
86    /// Get the name of the ICMP type
87    pub fn name(&self) -> &'static str {
88        match *self {
89            IcmpType::EchoReply => "Echo Reply",
90            IcmpType::DestinationUnreachable => "Destination Unreachable",
91            IcmpType::SourceQuench => "Source Quench",
92            IcmpType::RedirectMessage => "Redirect Message",
93            IcmpType::EchoRequest => "Echo Request",
94            IcmpType::RouterAdvertisement => "Router Advertisement",
95            IcmpType::RouterSolicitation => "Router Solicitation",
96            IcmpType::TimeExceeded => "Time Exceeded",
97            IcmpType::ParameterProblem => "Parameter Problem",
98            IcmpType::TimestampRequest => "Timestamp Request",
99            IcmpType::TimestampReply => "Timestamp Reply",
100            IcmpType::InformationRequest => "Information Request",
101            IcmpType::InformationReply => "Information Reply",
102            IcmpType::AddressMaskRequest => "Address Mask Request",
103            IcmpType::AddressMaskReply => "Address Mask Reply",
104            IcmpType::Traceroute => "Traceroute",
105            IcmpType::DatagramConversionError => "Datagram Conversion Error",
106            IcmpType::MobileHostRedirect => "Mobile Host Redirect",
107            IcmpType::IPv6WhereAreYou => "IPv6 Where Are You",
108            IcmpType::IPv6IAmHere => "IPv6 I Am Here",
109            IcmpType::MobileRegistrationRequest => "Mobile Registration Request",
110            IcmpType::MobileRegistrationReply => "Mobile Registration Reply",
111            IcmpType::DomainNameRequest => "Domain Name Request",
112            IcmpType::DomainNameReply => "Domain Name Reply",
113            IcmpType::SKIP => "SKIP",
114            IcmpType::Photuris => "Photuris",
115            IcmpType::Unknown(_) => "Unknown",
116        }
117    }
118    pub fn value(&self) -> u8 {
119        match *self {
120            IcmpType::EchoReply => 0,
121            IcmpType::DestinationUnreachable => 3,
122            IcmpType::SourceQuench => 4,
123            IcmpType::RedirectMessage => 5,
124            IcmpType::EchoRequest => 8,
125            IcmpType::RouterAdvertisement => 9,
126            IcmpType::RouterSolicitation => 10,
127            IcmpType::TimeExceeded => 11,
128            IcmpType::ParameterProblem => 12,
129            IcmpType::TimestampRequest => 13,
130            IcmpType::TimestampReply => 14,
131            IcmpType::InformationRequest => 15,
132            IcmpType::InformationReply => 16,
133            IcmpType::AddressMaskRequest => 17,
134            IcmpType::AddressMaskReply => 18,
135            IcmpType::Traceroute => 30,
136            IcmpType::DatagramConversionError => 31,
137            IcmpType::MobileHostRedirect => 32,
138            IcmpType::IPv6WhereAreYou => 33,
139            IcmpType::IPv6IAmHere => 34,
140            IcmpType::MobileRegistrationRequest => 35,
141            IcmpType::MobileRegistrationReply => 36,
142            IcmpType::DomainNameRequest => 37,
143            IcmpType::DomainNameReply => 38,
144            IcmpType::SKIP => 39,
145            IcmpType::Photuris => 40,
146            IcmpType::Unknown(n) => n,
147        }
148    }
149}
150
151/// Represents the "ICMP code" header field.
152#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
153#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
154pub struct IcmpCode(pub u8);
155
156impl IcmpCode {
157    /// Create a new `IcmpCode` instance.
158    pub fn new(val: u8) -> IcmpCode {
159        IcmpCode(val)
160    }
161    pub fn value(&self) -> u8 {
162        self.0
163    }
164}
165
166#[derive(Clone, Debug, PartialEq, Eq)]
167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
168pub struct IcmpHeader {
169    pub icmp_type: IcmpType,
170    pub icmp_code: IcmpCode,
171    pub checksum: u16,
172}
173
174/// ICMP packet representation
175#[derive(Clone, Debug, PartialEq, Eq)]
176pub struct IcmpPacket {
177    pub header: IcmpHeader,
178    pub payload: Bytes,
179}
180
181impl Packet for IcmpPacket {
182    type Header = IcmpHeader;
183
184    fn from_buf(bytes: &[u8]) -> Option<Self> {
185        if bytes.len() < ICMPV4_HEADER_LEN {
186            return None;
187        }
188        let icmp_type = IcmpType::new(bytes[0]);
189        let icmp_code = IcmpCode::new(bytes[1]);
190        let checksum = u16::from_be_bytes([bytes[2], bytes[3]]);
191        let payload = Bytes::copy_from_slice(&bytes[ICMP_COMMON_HEADER_LEN..]);
192        Some(IcmpPacket {
193            header: IcmpHeader {
194                icmp_type,
195                icmp_code,
196                checksum,
197            },
198            payload,
199        })
200    }
201    fn from_bytes(bytes: Bytes) -> Option<Self> {
202        Self::from_buf(&bytes)
203    }
204
205    fn to_bytes(&self) -> Bytes {
206        let mut buf = BytesMut::with_capacity(ICMP_COMMON_HEADER_LEN + self.payload.len());
207        buf.put_u8(self.header.icmp_type.value());
208        buf.put_u8(self.header.icmp_code.value());
209        buf.put_u16(self.header.checksum);
210        buf.extend_from_slice(&self.payload);
211        buf.freeze()
212    }
213
214    fn header(&self) -> Bytes {
215        self.to_bytes().slice(..self.header_len())
216    }
217
218    fn payload(&self) -> Bytes {
219        self.payload.clone()
220    }
221
222    fn header_len(&self) -> usize {
223        ICMP_COMMON_HEADER_LEN
224    }
225
226    fn payload_len(&self) -> usize {
227        self.payload.len()
228    }
229
230    fn total_len(&self) -> usize {
231        self.header_len() + self.payload_len()
232    }
233
234    fn into_parts(self) -> (Self::Header, Bytes) {
235        (self.header, self.payload)
236    }
237}
238
239impl IcmpPacket {
240    pub fn with_computed_checksum(&self) -> Self {
241        let mut pkt = self.clone();
242        pkt.header.checksum = checksum(&pkt).into();
243        pkt
244    }
245}
246
247/// Calculates a checksum of an ICMP packet.
248pub fn checksum(packet: &IcmpPacket) -> u16be {
249    use crate::util;
250    util::checksum(&packet.to_bytes(), 1)
251}
252
253pub mod echo_request {
254    use bytes::Bytes;
255
256    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
257
258    /// Represents the identifier field.
259    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
260    pub struct Identifier(pub u16);
261
262    impl Identifier {
263        /// Create a new `Identifier` instance.
264        pub fn new(val: u16) -> Identifier {
265            Identifier(val)
266        }
267        pub fn value(&self) -> u16 {
268            self.0
269        }
270    }
271
272    /// Represents the sequence number field.
273    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
274    pub struct SequenceNumber(pub u16);
275
276    impl SequenceNumber {
277        /// Create a new `SequenceNumber` instance.
278        pub fn new(val: u16) -> SequenceNumber {
279            SequenceNumber(val)
280        }
281        pub fn value(&self) -> u16 {
282            self.0
283        }
284    }
285
286    /// Enumeration of available ICMP codes for "echo reply" ICMP packets. There is actually only
287    /// one, since the only valid ICMP code is 0.
288    #[allow(non_snake_case)]
289    #[allow(non_upper_case_globals)]
290    pub mod IcmpCodes {
291        use crate::icmp::IcmpCode;
292        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
293        pub const NoCode: IcmpCode = IcmpCode(0);
294    }
295
296    /// Represents an "echo request" ICMP packet.
297    #[derive(Clone, Debug, PartialEq, Eq)]
298    pub struct EchoRequestPacket {
299        pub header: IcmpHeader,
300        pub identifier: u16,
301        pub sequence_number: u16,
302        pub payload: Bytes,
303    }
304
305    impl TryFrom<IcmpPacket> for EchoRequestPacket {
306        type Error = &'static str;
307
308        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
309            if pkt.header.icmp_type != IcmpType::EchoRequest {
310                return Err("Not an Echo Request");
311            }
312            if pkt.payload.len() < 4 {
313                return Err("Payload too short for Echo Request");
314            }
315
316            Ok(Self {
317                header: pkt.header,
318                identifier: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]),
319                sequence_number: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]),
320                payload: pkt.payload.slice(4..),
321            })
322        }
323    }
324}
325
326pub mod echo_reply {
327    use bytes::Bytes;
328
329    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
330
331    /// Represent the "identifier" field of the ICMP echo replay header.
332    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
333    pub struct Identifier(pub u16);
334
335    impl Identifier {
336        /// Create a new `Identifier` instance.
337        pub fn new(val: u16) -> Identifier {
338            Identifier(val)
339        }
340        pub fn value(&self) -> u16 {
341            self.0
342        }
343    }
344
345    /// Represent the "sequence number" field of the ICMP echo replay header.
346    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
347    pub struct SequenceNumber(pub u16);
348
349    impl SequenceNumber {
350        /// Create a new `SequenceNumber` instance.
351        pub fn new(val: u16) -> SequenceNumber {
352            SequenceNumber(val)
353        }
354        pub fn value(&self) -> u16 {
355            self.0
356        }
357    }
358
359    /// Enumeration of available ICMP codes for ICMP echo replay packets. There is actually only
360    /// one, since the only valid ICMP code is 0.
361    #[allow(non_snake_case)]
362    #[allow(non_upper_case_globals)]
363    pub mod IcmpCodes {
364        use crate::icmp::IcmpCode;
365        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
366        pub const NoCode: IcmpCode = IcmpCode(0);
367    }
368
369    /// Represents an ICMP echo reply packet.
370    #[derive(Clone, Debug, PartialEq, Eq)]
371    pub struct EchoReplyPacket {
372        pub header: IcmpHeader,
373        pub identifier: u16,
374        pub sequence_number: u16,
375        pub payload: Bytes,
376    }
377
378    impl TryFrom<IcmpPacket> for EchoReplyPacket {
379        type Error = &'static str;
380
381        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
382            if pkt.header.icmp_type != IcmpType::EchoReply {
383                return Err("Not an Echo Reply");
384            }
385            if pkt.payload.len() < 4 {
386                return Err("Payload too short for Echo Reply");
387            }
388
389            Ok(Self {
390                header: pkt.header,
391                identifier: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]).into(),
392                sequence_number: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]).into(),
393                payload: pkt.payload.slice(4..),
394            })
395        }
396    }
397}
398
399pub mod destination_unreachable {
400    use bytes::Bytes;
401
402    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
403
404    /// Enumeration of the recognized ICMP codes for "destination unreachable" ICMP packets.
405    #[allow(non_snake_case)]
406    #[allow(non_upper_case_globals)]
407    pub mod IcmpCodes {
408        use crate::icmp::IcmpCode;
409        /// ICMP code for "destination network unreachable" packet.
410        pub const DestinationNetworkUnreachable: IcmpCode = IcmpCode(0);
411        /// ICMP code for "destination host unreachable" packet.
412        pub const DestinationHostUnreachable: IcmpCode = IcmpCode(1);
413        /// ICMP code for "destination protocol unreachable" packet.
414        pub const DestinationProtocolUnreachable: IcmpCode = IcmpCode(2);
415        /// ICMP code for "destination port unreachable" packet.
416        pub const DestinationPortUnreachable: IcmpCode = IcmpCode(3);
417        /// ICMP code for "fragmentation required and DFF flag set" packet.
418        pub const FragmentationRequiredAndDFFlagSet: IcmpCode = IcmpCode(4);
419        /// ICMP code for "source route failed" packet.
420        pub const SourceRouteFailed: IcmpCode = IcmpCode(5);
421        /// ICMP code for "destination network unknown" packet.
422        pub const DestinationNetworkUnknown: IcmpCode = IcmpCode(6);
423        /// ICMP code for "destination host unknown" packet.
424        pub const DestinationHostUnknown: IcmpCode = IcmpCode(7);
425        /// ICMP code for "source host isolated" packet.
426        pub const SourceHostIsolated: IcmpCode = IcmpCode(8);
427        /// ICMP code for "network administrative prohibited" packet.
428        pub const NetworkAdministrativelyProhibited: IcmpCode = IcmpCode(9);
429        /// ICMP code for "host administrative prohibited" packet.
430        pub const HostAdministrativelyProhibited: IcmpCode = IcmpCode(10);
431        /// ICMP code for "network unreachable for this Type Of Service" packet.
432        pub const NetworkUnreachableForTOS: IcmpCode = IcmpCode(11);
433        /// ICMP code for "host unreachable for this Type Of Service" packet.
434        pub const HostUnreachableForTOS: IcmpCode = IcmpCode(12);
435        /// ICMP code for "communication administratively prohibited" packet.
436        pub const CommunicationAdministrativelyProhibited: IcmpCode = IcmpCode(13);
437        /// ICMP code for "host precedence violation" packet.
438        pub const HostPrecedenceViolation: IcmpCode = IcmpCode(14);
439        /// ICMP code for "precedence cut off in effect" packet.
440        pub const PrecedenceCutoffInEffect: IcmpCode = IcmpCode(15);
441    }
442
443    /// Represents an "echo request" ICMP packet.
444    #[derive(Clone, Debug, PartialEq, Eq)]
445    pub struct DestinationUnreachablePacket {
446        pub header: IcmpHeader,
447        pub unused: u16,
448        pub next_hop_mtu: u16,
449        pub payload: Bytes,
450    }
451
452    impl TryFrom<IcmpPacket> for DestinationUnreachablePacket {
453        type Error = &'static str;
454
455        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
456            if pkt.header.icmp_type != IcmpType::DestinationUnreachable {
457                return Err("Not a Destination Unreachable");
458            }
459            if pkt.payload.len() < 4 {
460                return Err("Payload too short for Destination Unreachable");
461            }
462
463            Ok(Self {
464                header: pkt.header,
465                unused: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]).into(),
466                next_hop_mtu: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]).into(),
467                payload: pkt.payload.slice(4..),
468            })
469        }
470    }
471}
472
473pub mod time_exceeded {
474    use bytes::Bytes;
475
476    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
477
478    /// Enumeration of the recognized ICMP codes for "time exceeded" ICMP packets.
479    #[allow(non_snake_case)]
480    #[allow(non_upper_case_globals)]
481    pub mod IcmpCodes {
482        use crate::icmp::IcmpCode;
483        /// ICMP code for "time to live exceeded in transit" packet.
484        pub const TimeToLiveExceededInTransit: IcmpCode = IcmpCode(0);
485        /// ICMP code for "fragment reassembly time exceeded" packet.
486        pub const FragmentReasemblyTimeExceeded: IcmpCode = IcmpCode(1);
487    }
488    /// Represents an "echo request" ICMP packet.
489    #[derive(Clone, Debug, PartialEq, Eq)]
490    pub struct TimeExceededPacket {
491        pub header: IcmpHeader,
492        pub unused: u32,
493        pub payload: Bytes,
494    }
495
496    impl TryFrom<IcmpPacket> for TimeExceededPacket {
497        type Error = &'static str;
498
499        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
500            if pkt.header.icmp_type != IcmpType::TimeExceeded {
501                return Err("Not a Time Exceeded");
502            }
503            if pkt.payload.len() < 4 {
504                return Err("Payload too short for Time Exceeded");
505            }
506
507            Ok(Self {
508                header: pkt.header,
509                unused: u32::from_be_bytes([
510                    pkt.payload[0],
511                    pkt.payload[1],
512                    pkt.payload[2],
513                    pkt.payload[3],
514                ])
515                .into(),
516                payload: pkt.payload.slice(4..),
517            })
518        }
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    #[test]
527    fn test_echo_request_from_bytes() {
528        let raw_bytes = Bytes::from_static(&[
529            8, 0, 0x3a, 0xbc, // Type = 8 (Echo Request), Code = 0, Checksum = 0x3abc
530            0x04, 0xd2, // Identifier = 0x04d2 (1234)
531            0x00, 0x2a, // Sequence = 0x002a (42)
532            b'p', b'i', b'n', b'g',
533        ]);
534
535        let parsed = IcmpPacket::from_bytes(raw_bytes.clone()).expect("Failed to parse ICMP");
536        let echo = echo_request::EchoRequestPacket::try_from(parsed).expect("Failed to downcast");
537
538        assert_eq!(echo.header.icmp_type, IcmpType::EchoRequest);
539        assert_eq!(echo.header.icmp_code, IcmpCode(0));
540        assert_eq!(echo.header.checksum, 0x3abc);
541        assert_eq!(echo.identifier, 1234);
542        assert_eq!(echo.sequence_number, 42);
543        assert_eq!(echo.payload, Bytes::from_static(b"ping"));
544    }
545
546    #[test]
547    fn test_echo_reply_roundtrip() {
548        let identifier: u16 = 5678;
549        let sequence: u16 = 99;
550        let payload = Bytes::from_static(b"pong");
551
552        let header = IcmpHeader {
553            icmp_type: IcmpType::EchoReply,
554            icmp_code: IcmpCode(0),
555            checksum: 0,
556        };
557
558        let mut buf = BytesMut::with_capacity(4 + payload.len());
559        buf.put_u16(identifier);
560        buf.put_u16(sequence);
561        buf.extend_from_slice(&payload);
562
563        let pkt = IcmpPacket {
564            header,
565            payload: buf.freeze(),
566        }
567        .with_computed_checksum();
568        let bytes = pkt.to_bytes();
569
570        let parsed = IcmpPacket::from_bytes(bytes.clone()).expect("Failed to parse ICMP");
571        let echo = echo_reply::EchoReplyPacket::try_from(parsed).expect("Failed to downcast");
572
573        assert_eq!(echo.identifier, identifier);
574        assert_eq!(echo.sequence_number, sequence);
575        assert_eq!(echo.payload, payload);
576    }
577
578    #[test]
579    fn test_destination_unreachable() {
580        let unused: u16 = 0;
581        let mtu: u16 = 1500;
582        let payload = Bytes::from_static(b"bad ip");
583
584        let header = IcmpHeader {
585            icmp_type: IcmpType::DestinationUnreachable,
586            icmp_code: IcmpCode(3), // Port unreachable
587            checksum: 0,
588        };
589
590        let mut buf = BytesMut::with_capacity(4 + payload.len());
591        buf.put_u16(unused);
592        buf.put_u16(mtu);
593        buf.extend_from_slice(&payload);
594
595        let pkt = IcmpPacket {
596            header,
597            payload: buf.freeze(),
598        }
599        .with_computed_checksum();
600        let parsed = IcmpPacket::from_bytes(pkt.to_bytes()).unwrap();
601        let unreachable =
602            destination_unreachable::DestinationUnreachablePacket::try_from(parsed).unwrap();
603
604        assert_eq!(unreachable.next_hop_mtu, mtu);
605        assert_eq!(unreachable.payload, payload);
606    }
607
608    #[test]
609    fn test_time_exceeded() {
610        let unused: u32 = 0xdeadbeef;
611        let payload = Bytes::from_static(b"timeout");
612
613        let header = IcmpHeader {
614            icmp_type: IcmpType::TimeExceeded,
615            icmp_code: IcmpCode(0), // TTL exceeded
616            checksum: 0,
617        };
618
619        let mut buf = BytesMut::with_capacity(4 + payload.len());
620        buf.put_u32(unused);
621        buf.extend_from_slice(&payload);
622
623        let pkt = IcmpPacket {
624            header,
625            payload: buf.freeze(),
626        }
627        .with_computed_checksum();
628        let parsed = IcmpPacket::from_bytes(pkt.to_bytes()).unwrap();
629        let exceeded = time_exceeded::TimeExceededPacket::try_from(parsed).unwrap();
630
631        assert_eq!(exceeded.unused, unused);
632        assert_eq!(exceeded.payload, payload);
633    }
634}