1use 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
10pub const ICMP_COMMON_HEADER_LEN: usize = 4;
12pub const ICMPV4_HEADER_LEN: usize = 8;
14pub const ICMPV4_PACKET_LEN: usize = ETHERNET_HEADER_LEN + IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
16pub const ICMPV4_IP_PACKET_LEN: usize = IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
18
19#[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 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 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#[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 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#[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
247pub 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 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
260 pub struct Identifier(pub u16);
261
262 impl Identifier {
263 pub fn new(val: u16) -> Identifier {
265 Identifier(val)
266 }
267 pub fn value(&self) -> u16 {
268 self.0
269 }
270 }
271
272 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
274 pub struct SequenceNumber(pub u16);
275
276 impl SequenceNumber {
277 pub fn new(val: u16) -> SequenceNumber {
279 SequenceNumber(val)
280 }
281 pub fn value(&self) -> u16 {
282 self.0
283 }
284 }
285
286 #[allow(non_snake_case)]
289 #[allow(non_upper_case_globals)]
290 pub mod IcmpCodes {
291 use crate::icmp::IcmpCode;
292 pub const NoCode: IcmpCode = IcmpCode(0);
294 }
295
296 #[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 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
333 pub struct Identifier(pub u16);
334
335 impl Identifier {
336 pub fn new(val: u16) -> Identifier {
338 Identifier(val)
339 }
340 pub fn value(&self) -> u16 {
341 self.0
342 }
343 }
344
345 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
347 pub struct SequenceNumber(pub u16);
348
349 impl SequenceNumber {
350 pub fn new(val: u16) -> SequenceNumber {
352 SequenceNumber(val)
353 }
354 pub fn value(&self) -> u16 {
355 self.0
356 }
357 }
358
359 #[allow(non_snake_case)]
362 #[allow(non_upper_case_globals)]
363 pub mod IcmpCodes {
364 use crate::icmp::IcmpCode;
365 pub const NoCode: IcmpCode = IcmpCode(0);
367 }
368
369 #[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 #[allow(non_snake_case)]
406 #[allow(non_upper_case_globals)]
407 pub mod IcmpCodes {
408 use crate::icmp::IcmpCode;
409 pub const DestinationNetworkUnreachable: IcmpCode = IcmpCode(0);
411 pub const DestinationHostUnreachable: IcmpCode = IcmpCode(1);
413 pub const DestinationProtocolUnreachable: IcmpCode = IcmpCode(2);
415 pub const DestinationPortUnreachable: IcmpCode = IcmpCode(3);
417 pub const FragmentationRequiredAndDFFlagSet: IcmpCode = IcmpCode(4);
419 pub const SourceRouteFailed: IcmpCode = IcmpCode(5);
421 pub const DestinationNetworkUnknown: IcmpCode = IcmpCode(6);
423 pub const DestinationHostUnknown: IcmpCode = IcmpCode(7);
425 pub const SourceHostIsolated: IcmpCode = IcmpCode(8);
427 pub const NetworkAdministrativelyProhibited: IcmpCode = IcmpCode(9);
429 pub const HostAdministrativelyProhibited: IcmpCode = IcmpCode(10);
431 pub const NetworkUnreachableForTOS: IcmpCode = IcmpCode(11);
433 pub const HostUnreachableForTOS: IcmpCode = IcmpCode(12);
435 pub const CommunicationAdministrativelyProhibited: IcmpCode = IcmpCode(13);
437 pub const HostPrecedenceViolation: IcmpCode = IcmpCode(14);
439 pub const PrecedenceCutoffInEffect: IcmpCode = IcmpCode(15);
441 }
442
443 #[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 #[allow(non_snake_case)]
480 #[allow(non_upper_case_globals)]
481 pub mod IcmpCodes {
482 use crate::icmp::IcmpCode;
483 pub const TimeToLiveExceededInTransit: IcmpCode = IcmpCode(0);
485 pub const FragmentReasemblyTimeExceeded: IcmpCode = IcmpCode(1);
487 }
488 #[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, 0x04, 0xd2, 0x00, 0x2a, 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), 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), 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}