nex_packet/
icmp.rs

1//! An ICMP packet abstraction.
2use crate::checksum::{ChecksumMode, ChecksumState};
3use crate::ipv4::IPV4_HEADER_LEN;
4use crate::{
5    ethernet::ETHERNET_HEADER_LEN,
6    packet::{MutablePacket, Packet},
7};
8use bytes::{BufMut, Bytes, BytesMut};
9use nex_core::bitfield::u16be;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// ICMP Common Header Length.
14pub const ICMP_COMMON_HEADER_LEN: usize = 4;
15/// ICMPv4 Header Length. Including the common header (4 bytes) and the type specific header (4 bytes).
16pub const ICMPV4_HEADER_LEN: usize = 8;
17/// ICMPv4 Minimum Packet Length.
18pub const ICMPV4_PACKET_LEN: usize = ETHERNET_HEADER_LEN + IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
19/// ICMPv4 IP Packet Length.
20pub const ICMPV4_IP_PACKET_LEN: usize = IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
21
22/// Represents the "ICMP type" header field.
23#[repr(u8)]
24#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub enum IcmpType {
27    EchoReply,
28    DestinationUnreachable,
29    SourceQuench,
30    RedirectMessage,
31    EchoRequest,
32    RouterAdvertisement,
33    RouterSolicitation,
34    TimeExceeded,
35    ParameterProblem,
36    TimestampRequest,
37    TimestampReply,
38    InformationRequest,
39    InformationReply,
40    AddressMaskRequest,
41    AddressMaskReply,
42    Traceroute,
43    DatagramConversionError,
44    MobileHostRedirect,
45    IPv6WhereAreYou,
46    IPv6IAmHere,
47    MobileRegistrationRequest,
48    MobileRegistrationReply,
49    DomainNameRequest,
50    DomainNameReply,
51    SKIP,
52    Photuris,
53    Unknown(u8),
54}
55
56impl IcmpType {
57    /// Create a new `IcmpType` instance.
58    pub fn new(val: u8) -> IcmpType {
59        match val {
60            0 => IcmpType::EchoReply,
61            3 => IcmpType::DestinationUnreachable,
62            4 => IcmpType::SourceQuench,
63            5 => IcmpType::RedirectMessage,
64            8 => IcmpType::EchoRequest,
65            9 => IcmpType::RouterAdvertisement,
66            10 => IcmpType::RouterSolicitation,
67            11 => IcmpType::TimeExceeded,
68            12 => IcmpType::ParameterProblem,
69            13 => IcmpType::TimestampRequest,
70            14 => IcmpType::TimestampReply,
71            15 => IcmpType::InformationRequest,
72            16 => IcmpType::InformationReply,
73            17 => IcmpType::AddressMaskRequest,
74            18 => IcmpType::AddressMaskReply,
75            30 => IcmpType::Traceroute,
76            31 => IcmpType::DatagramConversionError,
77            32 => IcmpType::MobileHostRedirect,
78            33 => IcmpType::IPv6WhereAreYou,
79            34 => IcmpType::IPv6IAmHere,
80            35 => IcmpType::MobileRegistrationRequest,
81            36 => IcmpType::MobileRegistrationReply,
82            37 => IcmpType::DomainNameRequest,
83            38 => IcmpType::DomainNameReply,
84            39 => IcmpType::SKIP,
85            40 => IcmpType::Photuris,
86            n => IcmpType::Unknown(n),
87        }
88    }
89    /// Get the name of the ICMP type
90    pub fn name(&self) -> &'static str {
91        match *self {
92            IcmpType::EchoReply => "Echo Reply",
93            IcmpType::DestinationUnreachable => "Destination Unreachable",
94            IcmpType::SourceQuench => "Source Quench",
95            IcmpType::RedirectMessage => "Redirect Message",
96            IcmpType::EchoRequest => "Echo Request",
97            IcmpType::RouterAdvertisement => "Router Advertisement",
98            IcmpType::RouterSolicitation => "Router Solicitation",
99            IcmpType::TimeExceeded => "Time Exceeded",
100            IcmpType::ParameterProblem => "Parameter Problem",
101            IcmpType::TimestampRequest => "Timestamp Request",
102            IcmpType::TimestampReply => "Timestamp Reply",
103            IcmpType::InformationRequest => "Information Request",
104            IcmpType::InformationReply => "Information Reply",
105            IcmpType::AddressMaskRequest => "Address Mask Request",
106            IcmpType::AddressMaskReply => "Address Mask Reply",
107            IcmpType::Traceroute => "Traceroute",
108            IcmpType::DatagramConversionError => "Datagram Conversion Error",
109            IcmpType::MobileHostRedirect => "Mobile Host Redirect",
110            IcmpType::IPv6WhereAreYou => "IPv6 Where Are You",
111            IcmpType::IPv6IAmHere => "IPv6 I Am Here",
112            IcmpType::MobileRegistrationRequest => "Mobile Registration Request",
113            IcmpType::MobileRegistrationReply => "Mobile Registration Reply",
114            IcmpType::DomainNameRequest => "Domain Name Request",
115            IcmpType::DomainNameReply => "Domain Name Reply",
116            IcmpType::SKIP => "SKIP",
117            IcmpType::Photuris => "Photuris",
118            IcmpType::Unknown(_) => "Unknown",
119        }
120    }
121    pub fn value(&self) -> u8 {
122        match *self {
123            IcmpType::EchoReply => 0,
124            IcmpType::DestinationUnreachable => 3,
125            IcmpType::SourceQuench => 4,
126            IcmpType::RedirectMessage => 5,
127            IcmpType::EchoRequest => 8,
128            IcmpType::RouterAdvertisement => 9,
129            IcmpType::RouterSolicitation => 10,
130            IcmpType::TimeExceeded => 11,
131            IcmpType::ParameterProblem => 12,
132            IcmpType::TimestampRequest => 13,
133            IcmpType::TimestampReply => 14,
134            IcmpType::InformationRequest => 15,
135            IcmpType::InformationReply => 16,
136            IcmpType::AddressMaskRequest => 17,
137            IcmpType::AddressMaskReply => 18,
138            IcmpType::Traceroute => 30,
139            IcmpType::DatagramConversionError => 31,
140            IcmpType::MobileHostRedirect => 32,
141            IcmpType::IPv6WhereAreYou => 33,
142            IcmpType::IPv6IAmHere => 34,
143            IcmpType::MobileRegistrationRequest => 35,
144            IcmpType::MobileRegistrationReply => 36,
145            IcmpType::DomainNameRequest => 37,
146            IcmpType::DomainNameReply => 38,
147            IcmpType::SKIP => 39,
148            IcmpType::Photuris => 40,
149            IcmpType::Unknown(n) => n,
150        }
151    }
152}
153
154/// Represents the "ICMP code" header field.
155#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
157pub struct IcmpCode(pub u8);
158
159impl IcmpCode {
160    /// Create a new `IcmpCode` instance.
161    pub fn new(val: u8) -> IcmpCode {
162        IcmpCode(val)
163    }
164    pub fn value(&self) -> u8 {
165        self.0
166    }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
170#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
171pub struct IcmpHeader {
172    pub icmp_type: IcmpType,
173    pub icmp_code: IcmpCode,
174    pub checksum: u16,
175}
176
177/// ICMP packet representation
178#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct IcmpPacket {
180    pub header: IcmpHeader,
181    pub payload: Bytes,
182}
183
184impl Packet for IcmpPacket {
185    type Header = IcmpHeader;
186
187    fn from_buf(bytes: &[u8]) -> Option<Self> {
188        if bytes.len() < ICMPV4_HEADER_LEN {
189            return None;
190        }
191        let icmp_type = IcmpType::new(bytes[0]);
192        let icmp_code = IcmpCode::new(bytes[1]);
193        let checksum = u16::from_be_bytes([bytes[2], bytes[3]]);
194        let payload = Bytes::copy_from_slice(&bytes[ICMP_COMMON_HEADER_LEN..]);
195        Some(IcmpPacket {
196            header: IcmpHeader {
197                icmp_type,
198                icmp_code,
199                checksum,
200            },
201            payload,
202        })
203    }
204    fn from_bytes(bytes: Bytes) -> Option<Self> {
205        Self::from_buf(&bytes)
206    }
207
208    fn to_bytes(&self) -> Bytes {
209        let mut buf = BytesMut::with_capacity(ICMP_COMMON_HEADER_LEN + self.payload.len());
210        buf.put_u8(self.header.icmp_type.value());
211        buf.put_u8(self.header.icmp_code.value());
212        buf.put_u16(self.header.checksum);
213        buf.extend_from_slice(&self.payload);
214        buf.freeze()
215    }
216
217    fn header(&self) -> Bytes {
218        self.to_bytes().slice(..self.header_len())
219    }
220
221    fn payload(&self) -> Bytes {
222        self.payload.clone()
223    }
224
225    fn header_len(&self) -> usize {
226        ICMP_COMMON_HEADER_LEN
227    }
228
229    fn payload_len(&self) -> usize {
230        self.payload.len()
231    }
232
233    fn total_len(&self) -> usize {
234        self.header_len() + self.payload_len()
235    }
236
237    fn into_parts(self) -> (Self::Header, Bytes) {
238        (self.header, self.payload)
239    }
240}
241
242impl IcmpPacket {
243    pub fn with_computed_checksum(&self) -> Self {
244        let mut pkt = self.clone();
245        pkt.header.checksum = checksum(&pkt).into();
246        pkt
247    }
248}
249
250/// Represents a mutable ICMP packet.
251pub struct MutableIcmpPacket<'a> {
252    buffer: &'a mut [u8],
253    checksum: ChecksumState,
254}
255
256impl<'a> MutablePacket<'a> for MutableIcmpPacket<'a> {
257    type Packet = IcmpPacket;
258
259    fn new(buffer: &'a mut [u8]) -> Option<Self> {
260        IcmpPacket::from_buf(buffer)?;
261        Some(Self {
262            buffer,
263            checksum: ChecksumState::new(),
264        })
265    }
266
267    fn packet(&self) -> &[u8] {
268        &*self.buffer
269    }
270
271    fn packet_mut(&mut self) -> &mut [u8] {
272        &mut *self.buffer
273    }
274
275    fn header(&self) -> &[u8] {
276        &self.packet()[..ICMP_COMMON_HEADER_LEN]
277    }
278
279    fn header_mut(&mut self) -> &mut [u8] {
280        let (header, _) = (&mut *self.buffer).split_at_mut(ICMP_COMMON_HEADER_LEN);
281        header
282    }
283
284    fn payload(&self) -> &[u8] {
285        &self.packet()[ICMP_COMMON_HEADER_LEN..]
286    }
287
288    fn payload_mut(&mut self) -> &mut [u8] {
289        let (_, payload) = (&mut *self.buffer).split_at_mut(ICMP_COMMON_HEADER_LEN);
290        payload
291    }
292}
293
294impl<'a> MutableIcmpPacket<'a> {
295    /// Create a mutable ICMP packet without performing validation.
296    pub fn new_unchecked(buffer: &'a mut [u8]) -> Self {
297        Self {
298            buffer,
299            checksum: ChecksumState::new(),
300        }
301    }
302
303    fn raw(&self) -> &[u8] {
304        &*self.buffer
305    }
306
307    fn raw_mut(&mut self) -> &mut [u8] {
308        &mut *self.buffer
309    }
310
311    fn after_field_mutation(&mut self) {
312        self.checksum.mark_dirty();
313        if self.checksum.automatic() {
314            let _ = self.recompute_checksum();
315        }
316    }
317
318    fn write_checksum(&mut self, value: u16) {
319        self.raw_mut()[2..4].copy_from_slice(&value.to_be_bytes());
320    }
321
322    /// Returns the checksum recalculation mode.
323    pub fn checksum_mode(&self) -> ChecksumMode {
324        self.checksum.mode()
325    }
326
327    /// Sets how checksum updates should be handled.
328    pub fn set_checksum_mode(&mut self, mode: ChecksumMode) {
329        self.checksum.set_mode(mode);
330        if self.checksum.automatic() && self.checksum.is_dirty() {
331            let _ = self.recompute_checksum();
332        }
333    }
334
335    /// Enables automatic checksum recomputation.
336    pub fn enable_auto_checksum(&mut self) {
337        self.set_checksum_mode(ChecksumMode::Automatic);
338    }
339
340    /// Disables automatic checksum recomputation.
341    pub fn disable_auto_checksum(&mut self) {
342        self.set_checksum_mode(ChecksumMode::Manual);
343    }
344
345    /// Returns true if the checksum needs to be recomputed.
346    pub fn is_checksum_dirty(&self) -> bool {
347        self.checksum.is_dirty()
348    }
349
350    /// Marks the checksum as dirty and recomputes it when automatic mode is enabled.
351    pub fn mark_checksum_dirty(&mut self) {
352        self.checksum.mark_dirty();
353        if self.checksum.automatic() {
354            let _ = self.recompute_checksum();
355        }
356    }
357
358    /// Recomputes the checksum for the current packet contents.
359    pub fn recompute_checksum(&mut self) -> Option<u16> {
360        let checksum = crate::util::checksum(self.raw(), 1) as u16;
361        self.write_checksum(checksum);
362        self.checksum.clear_dirty();
363        Some(checksum)
364    }
365
366    /// Returns the current ICMP type field.
367    pub fn get_type(&self) -> IcmpType {
368        IcmpType::new(self.raw()[0])
369    }
370
371    /// Sets the ICMP type field and marks the checksum as dirty.
372    pub fn set_type(&mut self, icmp_type: IcmpType) {
373        self.raw_mut()[0] = icmp_type.value();
374        self.after_field_mutation();
375    }
376
377    /// Returns the current ICMP code field.
378    pub fn get_code(&self) -> IcmpCode {
379        IcmpCode::new(self.raw()[1])
380    }
381
382    /// Sets the ICMP code field and marks the checksum as dirty.
383    pub fn set_code(&mut self, icmp_code: IcmpCode) {
384        self.raw_mut()[1] = icmp_code.value();
385        self.after_field_mutation();
386    }
387
388    /// Returns the serialized checksum value.
389    pub fn get_checksum(&self) -> u16 {
390        u16::from_be_bytes([self.raw()[2], self.raw()[3]])
391    }
392
393    /// Sets the serialized checksum value and clears the dirty flag.
394    pub fn set_checksum(&mut self, checksum: u16) {
395        self.write_checksum(checksum);
396        self.checksum.clear_dirty();
397    }
398}
399
400/// Calculates a checksum of an ICMP packet.
401pub fn checksum(packet: &IcmpPacket) -> u16be {
402    use crate::util;
403    util::checksum(&packet.to_bytes(), 1)
404}
405
406pub mod echo_request {
407    use bytes::Bytes;
408
409    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
410
411    /// Represents the identifier field.
412    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
413    pub struct Identifier(pub u16);
414
415    impl Identifier {
416        /// Create a new `Identifier` instance.
417        pub fn new(val: u16) -> Identifier {
418            Identifier(val)
419        }
420        pub fn value(&self) -> u16 {
421            self.0
422        }
423    }
424
425    /// Represents the sequence number field.
426    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
427    pub struct SequenceNumber(pub u16);
428
429    impl SequenceNumber {
430        /// Create a new `SequenceNumber` instance.
431        pub fn new(val: u16) -> SequenceNumber {
432            SequenceNumber(val)
433        }
434        pub fn value(&self) -> u16 {
435            self.0
436        }
437    }
438
439    /// Enumeration of available ICMP codes for "echo reply" ICMP packets. There is actually only
440    /// one, since the only valid ICMP code is 0.
441    #[allow(non_snake_case)]
442    #[allow(non_upper_case_globals)]
443    pub mod IcmpCodes {
444        use crate::icmp::IcmpCode;
445        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
446        pub const NoCode: IcmpCode = IcmpCode(0);
447    }
448
449    /// Represents an "echo request" ICMP packet.
450    #[derive(Clone, Debug, PartialEq, Eq)]
451    pub struct EchoRequestPacket {
452        pub header: IcmpHeader,
453        pub identifier: u16,
454        pub sequence_number: u16,
455        pub payload: Bytes,
456    }
457
458    impl TryFrom<IcmpPacket> for EchoRequestPacket {
459        type Error = &'static str;
460
461        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
462            if pkt.header.icmp_type != IcmpType::EchoRequest {
463                return Err("Not an Echo Request");
464            }
465            if pkt.payload.len() < 4 {
466                return Err("Payload too short for Echo Request");
467            }
468
469            Ok(Self {
470                header: pkt.header,
471                identifier: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]),
472                sequence_number: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]),
473                payload: pkt.payload.slice(4..),
474            })
475        }
476    }
477}
478
479pub mod echo_reply {
480    use bytes::Bytes;
481
482    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
483
484    /// Represent the "identifier" field of the ICMP echo replay header.
485    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
486    pub struct Identifier(pub u16);
487
488    impl Identifier {
489        /// Create a new `Identifier` instance.
490        pub fn new(val: u16) -> Identifier {
491            Identifier(val)
492        }
493        pub fn value(&self) -> u16 {
494            self.0
495        }
496    }
497
498    /// Represent the "sequence number" field of the ICMP echo replay header.
499    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
500    pub struct SequenceNumber(pub u16);
501
502    impl SequenceNumber {
503        /// Create a new `SequenceNumber` instance.
504        pub fn new(val: u16) -> SequenceNumber {
505            SequenceNumber(val)
506        }
507        pub fn value(&self) -> u16 {
508            self.0
509        }
510    }
511
512    /// Enumeration of available ICMP codes for ICMP echo replay packets. There is actually only
513    /// one, since the only valid ICMP code is 0.
514    #[allow(non_snake_case)]
515    #[allow(non_upper_case_globals)]
516    pub mod IcmpCodes {
517        use crate::icmp::IcmpCode;
518        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
519        pub const NoCode: IcmpCode = IcmpCode(0);
520    }
521
522    /// Represents an ICMP echo reply packet.
523    #[derive(Clone, Debug, PartialEq, Eq)]
524    pub struct EchoReplyPacket {
525        pub header: IcmpHeader,
526        pub identifier: u16,
527        pub sequence_number: u16,
528        pub payload: Bytes,
529    }
530
531    impl TryFrom<IcmpPacket> for EchoReplyPacket {
532        type Error = &'static str;
533
534        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
535            if pkt.header.icmp_type != IcmpType::EchoReply {
536                return Err("Not an Echo Reply");
537            }
538            if pkt.payload.len() < 4 {
539                return Err("Payload too short for Echo Reply");
540            }
541
542            Ok(Self {
543                header: pkt.header,
544                identifier: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]).into(),
545                sequence_number: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]).into(),
546                payload: pkt.payload.slice(4..),
547            })
548        }
549    }
550}
551
552pub mod destination_unreachable {
553    use bytes::Bytes;
554
555    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
556
557    /// Enumeration of the recognized ICMP codes for "destination unreachable" ICMP packets.
558    #[allow(non_snake_case)]
559    #[allow(non_upper_case_globals)]
560    pub mod IcmpCodes {
561        use crate::icmp::IcmpCode;
562        /// ICMP code for "destination network unreachable" packet.
563        pub const DestinationNetworkUnreachable: IcmpCode = IcmpCode(0);
564        /// ICMP code for "destination host unreachable" packet.
565        pub const DestinationHostUnreachable: IcmpCode = IcmpCode(1);
566        /// ICMP code for "destination protocol unreachable" packet.
567        pub const DestinationProtocolUnreachable: IcmpCode = IcmpCode(2);
568        /// ICMP code for "destination port unreachable" packet.
569        pub const DestinationPortUnreachable: IcmpCode = IcmpCode(3);
570        /// ICMP code for "fragmentation required and DFF flag set" packet.
571        pub const FragmentationRequiredAndDFFlagSet: IcmpCode = IcmpCode(4);
572        /// ICMP code for "source route failed" packet.
573        pub const SourceRouteFailed: IcmpCode = IcmpCode(5);
574        /// ICMP code for "destination network unknown" packet.
575        pub const DestinationNetworkUnknown: IcmpCode = IcmpCode(6);
576        /// ICMP code for "destination host unknown" packet.
577        pub const DestinationHostUnknown: IcmpCode = IcmpCode(7);
578        /// ICMP code for "source host isolated" packet.
579        pub const SourceHostIsolated: IcmpCode = IcmpCode(8);
580        /// ICMP code for "network administrative prohibited" packet.
581        pub const NetworkAdministrativelyProhibited: IcmpCode = IcmpCode(9);
582        /// ICMP code for "host administrative prohibited" packet.
583        pub const HostAdministrativelyProhibited: IcmpCode = IcmpCode(10);
584        /// ICMP code for "network unreachable for this Type Of Service" packet.
585        pub const NetworkUnreachableForTOS: IcmpCode = IcmpCode(11);
586        /// ICMP code for "host unreachable for this Type Of Service" packet.
587        pub const HostUnreachableForTOS: IcmpCode = IcmpCode(12);
588        /// ICMP code for "communication administratively prohibited" packet.
589        pub const CommunicationAdministrativelyProhibited: IcmpCode = IcmpCode(13);
590        /// ICMP code for "host precedence violation" packet.
591        pub const HostPrecedenceViolation: IcmpCode = IcmpCode(14);
592        /// ICMP code for "precedence cut off in effect" packet.
593        pub const PrecedenceCutoffInEffect: IcmpCode = IcmpCode(15);
594    }
595
596    /// Represents an "echo request" ICMP packet.
597    #[derive(Clone, Debug, PartialEq, Eq)]
598    pub struct DestinationUnreachablePacket {
599        pub header: IcmpHeader,
600        pub unused: u16,
601        pub next_hop_mtu: u16,
602        pub payload: Bytes,
603    }
604
605    impl TryFrom<IcmpPacket> for DestinationUnreachablePacket {
606        type Error = &'static str;
607
608        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
609            if pkt.header.icmp_type != IcmpType::DestinationUnreachable {
610                return Err("Not a Destination Unreachable");
611            }
612            if pkt.payload.len() < 4 {
613                return Err("Payload too short for Destination Unreachable");
614            }
615
616            Ok(Self {
617                header: pkt.header,
618                unused: u16::from_be_bytes([pkt.payload[0], pkt.payload[1]]).into(),
619                next_hop_mtu: u16::from_be_bytes([pkt.payload[2], pkt.payload[3]]).into(),
620                payload: pkt.payload.slice(4..),
621            })
622        }
623    }
624}
625
626pub mod time_exceeded {
627    use bytes::Bytes;
628
629    use crate::icmp::{IcmpHeader, IcmpPacket, IcmpType};
630
631    /// Enumeration of the recognized ICMP codes for "time exceeded" ICMP packets.
632    #[allow(non_snake_case)]
633    #[allow(non_upper_case_globals)]
634    pub mod IcmpCodes {
635        use crate::icmp::IcmpCode;
636        /// ICMP code for "time to live exceeded in transit" packet.
637        pub const TimeToLiveExceededInTransit: IcmpCode = IcmpCode(0);
638        /// ICMP code for "fragment reassembly time exceeded" packet.
639        pub const FragmentReasemblyTimeExceeded: IcmpCode = IcmpCode(1);
640    }
641    /// Represents an "echo request" ICMP packet.
642    #[derive(Clone, Debug, PartialEq, Eq)]
643    pub struct TimeExceededPacket {
644        pub header: IcmpHeader,
645        pub unused: u32,
646        pub payload: Bytes,
647    }
648
649    impl TryFrom<IcmpPacket> for TimeExceededPacket {
650        type Error = &'static str;
651
652        fn try_from(pkt: IcmpPacket) -> Result<Self, Self::Error> {
653            if pkt.header.icmp_type != IcmpType::TimeExceeded {
654                return Err("Not a Time Exceeded");
655            }
656            if pkt.payload.len() < 4 {
657                return Err("Payload too short for Time Exceeded");
658            }
659
660            Ok(Self {
661                header: pkt.header,
662                unused: u32::from_be_bytes([
663                    pkt.payload[0],
664                    pkt.payload[1],
665                    pkt.payload[2],
666                    pkt.payload[3],
667                ])
668                .into(),
669                payload: pkt.payload.slice(4..),
670            })
671        }
672    }
673}
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678    use crate::packet::MutablePacket;
679
680    #[test]
681    fn test_echo_request_from_bytes() {
682        let raw_bytes = Bytes::from_static(&[
683            8, 0, 0x3a, 0xbc, // Type = 8 (Echo Request), Code = 0, Checksum = 0x3abc
684            0x04, 0xd2, // Identifier = 0x04d2 (1234)
685            0x00, 0x2a, // Sequence = 0x002a (42)
686            b'p', b'i', b'n', b'g',
687        ]);
688
689        let parsed = IcmpPacket::from_bytes(raw_bytes.clone()).expect("Failed to parse ICMP");
690        let echo = echo_request::EchoRequestPacket::try_from(parsed).expect("Failed to downcast");
691
692        assert_eq!(echo.header.icmp_type, IcmpType::EchoRequest);
693        assert_eq!(echo.header.icmp_code, IcmpCode(0));
694        assert_eq!(echo.header.checksum, 0x3abc);
695        assert_eq!(echo.identifier, 1234);
696        assert_eq!(echo.sequence_number, 42);
697        assert_eq!(echo.payload, Bytes::from_static(b"ping"));
698    }
699
700    #[test]
701    fn test_echo_reply_roundtrip() {
702        let identifier: u16 = 5678;
703        let sequence: u16 = 99;
704        let payload = Bytes::from_static(b"pong");
705
706        let header = IcmpHeader {
707            icmp_type: IcmpType::EchoReply,
708            icmp_code: IcmpCode(0),
709            checksum: 0,
710        };
711
712        let mut buf = BytesMut::with_capacity(4 + payload.len());
713        buf.put_u16(identifier);
714        buf.put_u16(sequence);
715        buf.extend_from_slice(&payload);
716
717        let pkt = IcmpPacket {
718            header,
719            payload: buf.freeze(),
720        }
721        .with_computed_checksum();
722        let bytes = pkt.to_bytes();
723
724        let parsed = IcmpPacket::from_bytes(bytes.clone()).expect("Failed to parse ICMP");
725        let echo = echo_reply::EchoReplyPacket::try_from(parsed).expect("Failed to downcast");
726
727        assert_eq!(echo.identifier, identifier);
728        assert_eq!(echo.sequence_number, sequence);
729        assert_eq!(echo.payload, payload);
730    }
731
732    #[test]
733    fn test_destination_unreachable() {
734        let unused: u16 = 0;
735        let mtu: u16 = 1500;
736        let payload = Bytes::from_static(b"bad ip");
737
738        let header = IcmpHeader {
739            icmp_type: IcmpType::DestinationUnreachable,
740            icmp_code: IcmpCode(3), // Port unreachable
741            checksum: 0,
742        };
743
744        let mut buf = BytesMut::with_capacity(4 + payload.len());
745        buf.put_u16(unused);
746        buf.put_u16(mtu);
747        buf.extend_from_slice(&payload);
748
749        let pkt = IcmpPacket {
750            header,
751            payload: buf.freeze(),
752        }
753        .with_computed_checksum();
754        let parsed = IcmpPacket::from_bytes(pkt.to_bytes()).unwrap();
755        let unreachable =
756            destination_unreachable::DestinationUnreachablePacket::try_from(parsed).unwrap();
757
758        assert_eq!(unreachable.next_hop_mtu, mtu);
759        assert_eq!(unreachable.payload, payload);
760    }
761
762    #[test]
763    fn test_time_exceeded() {
764        let unused: u32 = 0xdeadbeef;
765        let payload = Bytes::from_static(b"timeout");
766
767        let header = IcmpHeader {
768            icmp_type: IcmpType::TimeExceeded,
769            icmp_code: IcmpCode(0), // TTL exceeded
770            checksum: 0,
771        };
772
773        let mut buf = BytesMut::with_capacity(4 + payload.len());
774        buf.put_u32(unused);
775        buf.extend_from_slice(&payload);
776
777        let pkt = IcmpPacket {
778            header,
779            payload: buf.freeze(),
780        }
781        .with_computed_checksum();
782        let parsed = IcmpPacket::from_bytes(pkt.to_bytes()).unwrap();
783        let exceeded = time_exceeded::TimeExceededPacket::try_from(parsed).unwrap();
784
785        assert_eq!(exceeded.unused, unused);
786        assert_eq!(exceeded.payload, payload);
787    }
788
789    #[test]
790    fn test_mutable_icmp_packet_manual_checksum() {
791        let mut raw = [
792            8, 0, 0, 0, // type, code, checksum
793            0, 1, 0, 1, // identifier, sequence
794            b'p', b'i',
795        ];
796
797        let mut packet = MutableIcmpPacket::new(&mut raw).expect("mutable icmp");
798        packet.set_type(IcmpType::EchoReply);
799        assert!(packet.is_checksum_dirty());
800
801        let updated = packet.recompute_checksum().expect("checksum");
802        assert_eq!(packet.get_checksum(), updated);
803
804        let frozen = packet.freeze().expect("freeze");
805        let expected: u16 = checksum(&frozen).into();
806        assert_eq!(packet.get_checksum(), expected);
807    }
808
809    #[test]
810    fn test_mutable_icmp_packet_auto_checksum() {
811        let mut raw = [
812            8, 0, 0, 0, // type, code, checksum
813            0, 1, 0, 1, // identifier, sequence
814            b'p', b'i',
815        ];
816
817        let mut packet = MutableIcmpPacket::new(&mut raw).expect("mutable icmp");
818        let baseline = packet.recompute_checksum().expect("checksum");
819        packet.enable_auto_checksum();
820        packet.set_code(IcmpCode::new(1));
821
822        assert!(!packet.is_checksum_dirty());
823
824        let frozen = packet.freeze().expect("freeze");
825        let expected: u16 = checksum(&frozen).into();
826        assert_ne!(baseline, expected);
827        assert_eq!(packet.get_checksum(), expected);
828    }
829}