xenet_packet/
icmp.rs

1//! An ICMP packet abstraction.
2
3use crate::PrimitiveValues;
4
5use alloc::vec::Vec;
6
7use crate::ethernet::ETHERNET_HEADER_LEN;
8use crate::ipv4::IPV4_HEADER_LEN;
9use xenet_macro::packet;
10use xenet_macro_helper::types::*;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15/// ICMPv4 Header Length.
16pub const ICMPV4_HEADER_LEN: usize = echo_request::MutableEchoRequestPacket::minimum_packet_size();
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 ICMPv4 header.
23#[derive(Clone, Debug, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct IcmpHeader {
26    pub icmp_type: IcmpType,
27    pub icmp_code: IcmpCode,
28    pub checksum: u16be,
29}
30
31impl IcmpHeader {
32    /// Construct an ICMPv4 header from a byte slice.
33    pub fn from_bytes(packet: &[u8]) -> Result<IcmpHeader, String> {
34        if packet.len() < ICMPV4_HEADER_LEN {
35            return Err("Packet is too small for ICMPv4 header".to_string());
36        }
37        match IcmpPacket::new(packet) {
38            Some(icmp_packet) => Ok(IcmpHeader {
39                icmp_type: icmp_packet.get_icmp_type(),
40                icmp_code: icmp_packet.get_icmp_code(),
41                checksum: icmp_packet.get_checksum(),
42            }),
43            None => Err("Failed to parse ICMPv4 packet".to_string()),
44        }
45    }
46    /// Construct an ICMPv4 header from a IcmpPacket.
47    pub(crate) fn from_packet(icmp_packet: &IcmpPacket) -> IcmpHeader {
48        IcmpHeader {
49            icmp_type: icmp_packet.get_icmp_type(),
50            icmp_code: icmp_packet.get_icmp_code(),
51            checksum: icmp_packet.get_checksum(),
52        }
53    }
54}
55
56/// Represents the "ICMP type" header field.
57#[repr(u8)]
58#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60pub enum IcmpType {
61    EchoReply,
62    DestinationUnreachable,
63    SourceQuench,
64    RedirectMessage,
65    EchoRequest,
66    RouterAdvertisement,
67    RouterSolicitation,
68    TimeExceeded,
69    ParameterProblem,
70    TimestampRequest,
71    TimestampReply,
72    InformationRequest,
73    InformationReply,
74    AddressMaskRequest,
75    AddressMaskReply,
76    Traceroute,
77    DatagramConversionError,
78    MobileHostRedirect,
79    IPv6WhereAreYou,
80    IPv6IAmHere,
81    MobileRegistrationRequest,
82    MobileRegistrationReply,
83    DomainNameRequest,
84    DomainNameReply,
85    SKIP,
86    Photuris,
87    Unknown(u8),
88}
89
90impl IcmpType {
91    /// Create a new `IcmpType` instance.
92    pub fn new(val: u8) -> IcmpType {
93        match val {
94            0 => IcmpType::EchoReply,
95            3 => IcmpType::DestinationUnreachable,
96            4 => IcmpType::SourceQuench,
97            5 => IcmpType::RedirectMessage,
98            8 => IcmpType::EchoRequest,
99            9 => IcmpType::RouterAdvertisement,
100            10 => IcmpType::RouterSolicitation,
101            11 => IcmpType::TimeExceeded,
102            12 => IcmpType::ParameterProblem,
103            13 => IcmpType::TimestampRequest,
104            14 => IcmpType::TimestampReply,
105            15 => IcmpType::InformationRequest,
106            16 => IcmpType::InformationReply,
107            17 => IcmpType::AddressMaskRequest,
108            18 => IcmpType::AddressMaskReply,
109            30 => IcmpType::Traceroute,
110            31 => IcmpType::DatagramConversionError,
111            32 => IcmpType::MobileHostRedirect,
112            33 => IcmpType::IPv6WhereAreYou,
113            34 => IcmpType::IPv6IAmHere,
114            35 => IcmpType::MobileRegistrationRequest,
115            36 => IcmpType::MobileRegistrationReply,
116            37 => IcmpType::DomainNameRequest,
117            38 => IcmpType::DomainNameReply,
118            39 => IcmpType::SKIP,
119            40 => IcmpType::Photuris,
120            n => IcmpType::Unknown(n),
121        }
122    }
123    /// Get the name of the ICMP type
124    pub fn name(&self) -> String {
125        match *self {
126            IcmpType::EchoReply => String::from("Echo Reply"),
127            IcmpType::DestinationUnreachable => String::from("Destination Unreachable"),
128            IcmpType::SourceQuench => String::from("Source Quench"),
129            IcmpType::RedirectMessage => String::from("Redirect Message"),
130            IcmpType::EchoRequest => String::from("Echo Request"),
131            IcmpType::RouterAdvertisement => String::from("Router Advertisement"),
132            IcmpType::RouterSolicitation => String::from("Router Solicitation"),
133            IcmpType::TimeExceeded => String::from("Time Exceeded"),
134            IcmpType::ParameterProblem => String::from("Parameter Problem"),
135            IcmpType::TimestampRequest => String::from("Timestamp Request"),
136            IcmpType::TimestampReply => String::from("Timestamp Reply"),
137            IcmpType::InformationRequest => String::from("Information Request"),
138            IcmpType::InformationReply => String::from("Information Reply"),
139            IcmpType::AddressMaskRequest => String::from("Address Mask Request"),
140            IcmpType::AddressMaskReply => String::from("Address Mask Reply"),
141            IcmpType::Traceroute => String::from("Traceroute"),
142            IcmpType::DatagramConversionError => String::from("Datagram Conversion Error"),
143            IcmpType::MobileHostRedirect => String::from("Mobile Host Redirect"),
144            IcmpType::IPv6WhereAreYou => String::from("IPv6 Where Are You"),
145            IcmpType::IPv6IAmHere => String::from("IPv6 I Am Here"),
146            IcmpType::MobileRegistrationRequest => String::from("Mobile Registration Request"),
147            IcmpType::MobileRegistrationReply => String::from("Mobile Registration Reply"),
148            IcmpType::DomainNameRequest => String::from("Domain Name Request"),
149            IcmpType::DomainNameReply => String::from("Domain Name Reply"),
150            IcmpType::SKIP => String::from("SKIP"),
151            IcmpType::Photuris => String::from("Photuris"),
152            IcmpType::Unknown(n) => format!("Unknown ({})", n),
153        }
154    }
155}
156
157impl PrimitiveValues for IcmpType {
158    type T = (u8,);
159    fn to_primitive_values(&self) -> (u8,) {
160        match *self {
161            IcmpType::EchoReply => (0,),
162            IcmpType::DestinationUnreachable => (3,),
163            IcmpType::SourceQuench => (4,),
164            IcmpType::RedirectMessage => (5,),
165            IcmpType::EchoRequest => (8,),
166            IcmpType::RouterAdvertisement => (9,),
167            IcmpType::RouterSolicitation => (10,),
168            IcmpType::TimeExceeded => (11,),
169            IcmpType::ParameterProblem => (12,),
170            IcmpType::TimestampRequest => (13,),
171            IcmpType::TimestampReply => (14,),
172            IcmpType::InformationRequest => (15,),
173            IcmpType::InformationReply => (16,),
174            IcmpType::AddressMaskRequest => (17,),
175            IcmpType::AddressMaskReply => (18,),
176            IcmpType::Traceroute => (30,),
177            IcmpType::DatagramConversionError => (31,),
178            IcmpType::MobileHostRedirect => (32,),
179            IcmpType::IPv6WhereAreYou => (33,),
180            IcmpType::IPv6IAmHere => (34,),
181            IcmpType::MobileRegistrationRequest => (35,),
182            IcmpType::MobileRegistrationReply => (36,),
183            IcmpType::DomainNameRequest => (37,),
184            IcmpType::DomainNameReply => (38,),
185            IcmpType::SKIP => (39,),
186            IcmpType::Photuris => (40,),
187            IcmpType::Unknown(n) => (n,),
188        }
189    }
190}
191
192/// Represents the "ICMP code" header field.
193#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195pub struct IcmpCode(pub u8);
196
197impl IcmpCode {
198    /// Create a new `IcmpCode` instance.
199    pub fn new(val: u8) -> IcmpCode {
200        IcmpCode(val)
201    }
202}
203
204impl PrimitiveValues for IcmpCode {
205    type T = (u8,);
206    fn to_primitive_values(&self) -> (u8,) {
207        (self.0,)
208    }
209}
210
211/// Represents a generic ICMP packet.
212#[packet]
213pub struct Icmp {
214    #[construct_with(u8)]
215    pub icmp_type: IcmpType,
216    #[construct_with(u8)]
217    pub icmp_code: IcmpCode,
218    pub checksum: u16be,
219    // theoretically, the header is 64 bytes long, but since the "Rest Of Header" part depends on
220    // the ICMP type and ICMP code, we consider it's part of the payload.
221    // rest_of_header: u32be,
222    #[payload]
223    pub payload: Vec<u8>,
224}
225
226/// Calculates a checksum of an ICMP packet.
227pub fn checksum(packet: &IcmpPacket) -> u16be {
228    use crate::util;
229    use crate::Packet;
230
231    util::checksum(packet.packet(), 1)
232}
233
234#[cfg(test)]
235mod checksum_tests {
236    use super::*;
237    use alloc::vec;
238
239    #[test]
240    fn checksum_zeros() {
241        let mut data = vec![0u8; 8];
242        let expected = 65535;
243        let mut pkg = MutableIcmpPacket::new(&mut data[..]).unwrap();
244        assert_eq!(checksum(&pkg.to_immutable()), expected);
245        pkg.set_checksum(123);
246        assert_eq!(checksum(&pkg.to_immutable()), expected);
247    }
248
249    #[test]
250    fn checksum_nonzero() {
251        let mut data = vec![255u8; 8];
252        let expected = 0;
253        let mut pkg = MutableIcmpPacket::new(&mut data[..]).unwrap();
254        assert_eq!(checksum(&pkg.to_immutable()), expected);
255        pkg.set_checksum(0);
256        assert_eq!(checksum(&pkg.to_immutable()), expected);
257    }
258
259    #[test]
260    fn checksum_odd_bytes() {
261        let mut data = vec![191u8; 7];
262        let expected = 49535;
263        let pkg = IcmpPacket::new(&mut data[..]).unwrap();
264        assert_eq!(checksum(&pkg), expected);
265    }
266}
267
268pub mod echo_reply {
269    //! abstraction for ICMP "echo reply" packets.
270    //!
271    //! ```text
272    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
273    //! |     Type      |     Code      |          Checksum             |
274    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
275    //! |           Identifier          |        Sequence Number        |
276    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
277    //! |     Data ...
278    //! +-+-+-+-+-
279    //! ```
280
281    use crate::icmp::{IcmpCode, IcmpType};
282    use crate::PrimitiveValues;
283
284    use alloc::vec::Vec;
285
286    use xenet_macro::packet;
287    use xenet_macro_helper::types::*;
288
289    /// Represent the "identifier" field of the ICMP echo replay header.
290    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
291    pub struct Identifier(pub u16);
292
293    impl Identifier {
294        /// Create a new `Identifier` instance.
295        pub fn new(val: u16) -> Identifier {
296            Identifier(val)
297        }
298    }
299
300    impl PrimitiveValues for Identifier {
301        type T = (u16,);
302        fn to_primitive_values(&self) -> (u16,) {
303            (self.0,)
304        }
305    }
306
307    /// Represent the "sequence number" field of the ICMP echo replay header.
308    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
309    pub struct SequenceNumber(pub u16);
310
311    impl SequenceNumber {
312        /// Create a new `SequenceNumber` instance.
313        pub fn new(val: u16) -> SequenceNumber {
314            SequenceNumber(val)
315        }
316    }
317
318    impl PrimitiveValues for SequenceNumber {
319        type T = (u16,);
320        fn to_primitive_values(&self) -> (u16,) {
321            (self.0,)
322        }
323    }
324
325    /// Enumeration of available ICMP codes for ICMP echo replay packets. There is actually only
326    /// one, since the only valid ICMP code is 0.
327    #[allow(non_snake_case)]
328    #[allow(non_upper_case_globals)]
329    pub mod IcmpCodes {
330        use crate::icmp::IcmpCode;
331        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
332        pub const NoCode: IcmpCode = IcmpCode(0);
333    }
334
335    /// Represents an ICMP echo reply packet.
336    #[packet]
337    pub struct EchoReply {
338        #[construct_with(u8)]
339        pub icmp_type: IcmpType,
340        #[construct_with(u8)]
341        pub icmp_code: IcmpCode,
342        pub checksum: u16be,
343        pub identifier: u16be,
344        pub sequence_number: u16be,
345        #[payload]
346        pub payload: Vec<u8>,
347    }
348}
349
350pub mod echo_request {
351    //! abstraction for "echo request" ICMP packets.
352    //!
353    //! ```text
354    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
355    //! |     Type      |     Code      |          Checksum             |
356    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
357    //! |           Identifier          |        Sequence Number        |
358    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
359    //! |     Data ...
360    //! +-+-+-+-+-
361    //! ```
362
363    use crate::icmp::{IcmpCode, IcmpType};
364    use crate::PrimitiveValues;
365
366    use alloc::vec::Vec;
367
368    use xenet_macro::packet;
369    use xenet_macro_helper::types::*;
370
371    /// Represents the identifier field.
372    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
373    pub struct Identifier(pub u16);
374
375    impl Identifier {
376        /// Create a new `Identifier` instance.
377        pub fn new(val: u16) -> Identifier {
378            Identifier(val)
379        }
380    }
381
382    impl PrimitiveValues for Identifier {
383        type T = (u16,);
384        fn to_primitive_values(&self) -> (u16,) {
385            (self.0,)
386        }
387    }
388
389    /// Represents the sequence number field.
390    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
391    pub struct SequenceNumber(pub u16);
392
393    impl SequenceNumber {
394        /// Create a new `SequenceNumber` instance.
395        pub fn new(val: u16) -> SequenceNumber {
396            SequenceNumber(val)
397        }
398    }
399
400    impl PrimitiveValues for SequenceNumber {
401        type T = (u16,);
402        fn to_primitive_values(&self) -> (u16,) {
403            (self.0,)
404        }
405    }
406
407    /// Enumeration of available ICMP codes for "echo reply" ICMP packets. There is actually only
408    /// one, since the only valid ICMP code is 0.
409    #[allow(non_snake_case)]
410    #[allow(non_upper_case_globals)]
411    pub mod IcmpCodes {
412        use crate::icmp::IcmpCode;
413        /// 0 is the only available ICMP code for "echo reply" ICMP packets.
414        pub const NoCode: IcmpCode = IcmpCode(0);
415    }
416
417    /// Represents an "echo request" ICMP packet.
418    #[packet]
419    pub struct EchoRequest {
420        #[construct_with(u8)]
421        pub icmp_type: IcmpType,
422        #[construct_with(u8)]
423        pub icmp_code: IcmpCode,
424        pub checksum: u16be,
425        pub identifier: u16be,
426        pub sequence_number: u16be,
427        #[payload]
428        pub payload: Vec<u8>,
429    }
430}
431
432pub mod destination_unreachable {
433    //! abstraction for "destination unreachable" ICMP packets.
434    //!
435    //! ```text
436    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
437    //! |     Type      |     Code      |          Checksum             |
438    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
439    //! |             unused            |        Next-Hop MTU           |
440    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
441    //! |      Internet Header + 64 bits of Original Data Datagram      |
442    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
443    //! ```
444
445    use crate::icmp::{IcmpCode, IcmpType};
446
447    use alloc::vec::Vec;
448
449    use xenet_macro::packet;
450    use xenet_macro_helper::types::*;
451
452    /// Enumeration of the recognized ICMP codes for "destination unreachable" ICMP packets.
453    #[allow(non_snake_case)]
454    #[allow(non_upper_case_globals)]
455    pub mod IcmpCodes {
456        use crate::icmp::IcmpCode;
457        /// ICMP code for "destination network unreachable" packet.
458        pub const DestinationNetworkUnreachable: IcmpCode = IcmpCode(0);
459        /// ICMP code for "destination host unreachable" packet.
460        pub const DestinationHostUnreachable: IcmpCode = IcmpCode(1);
461        /// ICMP code for "destination protocol unreachable" packet.
462        pub const DestinationProtocolUnreachable: IcmpCode = IcmpCode(2);
463        /// ICMP code for "destination port unreachable" packet.
464        pub const DestinationPortUnreachable: IcmpCode = IcmpCode(3);
465        /// ICMP code for "fragmentation required and DFF flag set" packet.
466        pub const FragmentationRequiredAndDFFlagSet: IcmpCode = IcmpCode(4);
467        /// ICMP code for "source route failed" packet.
468        pub const SourceRouteFailed: IcmpCode = IcmpCode(5);
469        /// ICMP code for "destination network unknown" packet.
470        pub const DestinationNetworkUnknown: IcmpCode = IcmpCode(6);
471        /// ICMP code for "destination host unknown" packet.
472        pub const DestinationHostUnknown: IcmpCode = IcmpCode(7);
473        /// ICMP code for "source host isolated" packet.
474        pub const SourceHostIsolated: IcmpCode = IcmpCode(8);
475        /// ICMP code for "network administrative prohibited" packet.
476        pub const NetworkAdministrativelyProhibited: IcmpCode = IcmpCode(9);
477        /// ICMP code for "host administrative prohibited" packet.
478        pub const HostAdministrativelyProhibited: IcmpCode = IcmpCode(10);
479        /// ICMP code for "network unreachable for this Type Of Service" packet.
480        pub const NetworkUnreachableForTOS: IcmpCode = IcmpCode(11);
481        /// ICMP code for "host unreachable for this Type Of Service" packet.
482        pub const HostUnreachableForTOS: IcmpCode = IcmpCode(12);
483        /// ICMP code for "communication administratively prohibited" packet.
484        pub const CommunicationAdministrativelyProhibited: IcmpCode = IcmpCode(13);
485        /// ICMP code for "host precedence violation" packet.
486        pub const HostPrecedenceViolation: IcmpCode = IcmpCode(14);
487        /// ICMP code for "precedence cut off in effect" packet.
488        pub const PrecedenceCutoffInEffect: IcmpCode = IcmpCode(15);
489    }
490
491    /// Represents an "echo request" ICMP packet.
492    #[packet]
493    pub struct DestinationUnreachable {
494        #[construct_with(u8)]
495        pub icmp_type: IcmpType,
496        #[construct_with(u8)]
497        pub icmp_code: IcmpCode,
498        pub checksum: u16be,
499        pub unused: u16be,
500        pub next_hop_mtu: u16be,
501        #[payload]
502        pub payload: Vec<u8>,
503    }
504}
505
506pub mod time_exceeded {
507    //! abstraction for "time exceeded" ICMP packets.
508    //!
509    //! ```text
510    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
511    //! |     Type      |     Code      |          Checksum             |
512    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
513    //! |                             unused                            |
514    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
515    //! |      Internet Header + 64 bits of Original Data Datagram      |
516    //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
517    //! ```
518
519    use crate::icmp::{IcmpCode, IcmpType};
520
521    use alloc::vec::Vec;
522
523    use xenet_macro::packet;
524    use xenet_macro_helper::types::*;
525
526    /// Enumeration of the recognized ICMP codes for "time exceeded" ICMP packets.
527    #[allow(non_snake_case)]
528    #[allow(non_upper_case_globals)]
529    pub mod IcmpCodes {
530        use crate::icmp::IcmpCode;
531        /// ICMP code for "time to live exceeded in transit" packet.
532        pub const TimeToLiveExceededInTransit: IcmpCode = IcmpCode(0);
533        /// ICMP code for "fragment reassembly time exceeded" packet.
534        pub const FragmentReasemblyTimeExceeded: IcmpCode = IcmpCode(1);
535    }
536
537    /// Represents an "echo request" ICMP packet.
538    #[packet]
539    pub struct TimeExceeded {
540        #[construct_with(u8)]
541        pub icmp_type: IcmpType,
542        #[construct_with(u8)]
543        pub icmp_code: IcmpCode,
544        pub checksum: u16be,
545        pub unused: u32be,
546        #[payload]
547        pub payload: Vec<u8>,
548    }
549}