1use std::fmt::{self, Formatter};
73use std::mem;
74
75use zerocopy::byteorder::{BigEndian, U16, U32};
76use zerocopy::{FromBytes, IntoBytes, Unaligned};
77
78use crate::packet::HeaderParser;
79use crate::packet::PacketHeader;
80
81#[derive(
83 Debug,
84 Clone,
85 Copy,
86 PartialEq,
87 Eq,
88 Hash,
89 FromBytes,
90 IntoBytes,
91 Unaligned,
92 zerocopy::Immutable,
93 zerocopy::KnownLayout,
94)]
95#[repr(transparent)]
96pub struct IcmpType(pub u8);
97
98impl IcmpType {
99 pub const ECHO_REPLY: IcmpType = IcmpType(0); pub const DEST_UNREACH: IcmpType = IcmpType(3); pub const SOURCE_QUENCH: IcmpType = IcmpType(4); pub const REDIRECT: IcmpType = IcmpType(5); pub const ECHO: IcmpType = IcmpType(8); pub const ROUTER_ADV: IcmpType = IcmpType(9); pub const ROUTER_SOLICIT: IcmpType = IcmpType(10); pub const TIME_EXCEEDED: IcmpType = IcmpType(11); pub const PARAMETER_PROBLEM: IcmpType = IcmpType(12); pub const TIMESTAMP: IcmpType = IcmpType(13); pub const TIMESTAMP_REPLY: IcmpType = IcmpType(14); pub const INFO_REQUEST: IcmpType = IcmpType(15); pub const INFO_REPLY: IcmpType = IcmpType(16); pub const ADDRESS: IcmpType = IcmpType(17); pub const ADDRESS_REPLY: IcmpType = IcmpType(18); pub const EX_ECHO: IcmpType = IcmpType(42); pub const EX_ECHO_REPLY: IcmpType = IcmpType(43); }
117
118impl From<u8> for IcmpType {
119 fn from(value: u8) -> Self {
120 IcmpType(value)
121 }
122}
123
124impl From<IcmpType> for u8 {
125 fn from(value: IcmpType) -> Self {
126 value.0
127 }
128}
129
130impl fmt::Display for IcmpType {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 let s = match self.0 {
133 0 => "echo-reply",
134 3 => "dest-unreachable",
135 4 => "source-quench",
136 5 => "redirect",
137 8 => "echo-request",
138 9 => "router-adv",
139 10 => "router-solicit",
140 11 => "time-exceeded",
141 12 => "param-problem",
142 13 => "timestamp-request",
143 14 => "timestamp-reply",
144 15 => "info-request",
145 16 => "info-reply",
146 17 => "address-request",
147 18 => "address-reply",
148 42 => "ex-echo-request",
149 43 => "ex-echo-reply",
150 _ => return write!(f, "unknown-{}", self.0),
151 };
152 write!(f, "{}", s)
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
158#[repr(transparent)]
159pub struct IcmpCodeUnreachable(pub u8);
160
161impl IcmpCodeUnreachable {
162 pub const NET_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(0); pub const HOST_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(1); pub const PROT_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(2); pub const PORT_UNREACH: IcmpCodeUnreachable = IcmpCodeUnreachable(3); pub const FRAG_NEEDED: IcmpCodeUnreachable = IcmpCodeUnreachable(4); pub const SR_FAILED: IcmpCodeUnreachable = IcmpCodeUnreachable(5); pub const NET_UNKNOWN: IcmpCodeUnreachable = IcmpCodeUnreachable(6); pub const HOST_UNKNOWN: IcmpCodeUnreachable = IcmpCodeUnreachable(7); pub const HOST_ISOLATED: IcmpCodeUnreachable = IcmpCodeUnreachable(8); pub const NET_ANO: IcmpCodeUnreachable = IcmpCodeUnreachable(9); pub const HOST_ANO: IcmpCodeUnreachable = IcmpCodeUnreachable(10); pub const NET_UNR_TOS: IcmpCodeUnreachable = IcmpCodeUnreachable(11); pub const HOST_UNR_TOS: IcmpCodeUnreachable = IcmpCodeUnreachable(12); pub const PKT_FILTERED: IcmpCodeUnreachable = IcmpCodeUnreachable(13); pub const PREC_VIOLATION: IcmpCodeUnreachable = IcmpCodeUnreachable(14); pub const PREC_CUTOFF: IcmpCodeUnreachable = IcmpCodeUnreachable(15); }
179
180impl From<u8> for IcmpCodeUnreachable {
181 fn from(value: u8) -> Self {
182 IcmpCodeUnreachable(value)
183 }
184}
185
186impl From<IcmpCodeUnreachable> for u8 {
187 fn from(value: IcmpCodeUnreachable) -> Self {
188 value.0
189 }
190}
191
192impl fmt::Display for IcmpCodeUnreachable {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 let s = match self.0 {
195 0 => "net-unreachable",
196 1 => "host-unreachable",
197 2 => "protocol-unreachable",
198 3 => "port-unreachable",
199 4 => "frag-needed",
200 5 => "source-route-failed",
201 6 => "dest-net-unknown",
202 7 => "dest-host-unknown",
203 8 => "source-host-isolated",
204 9 => "dest-net-prohibited",
205 10 => "dest-host-prohibited",
206 11 => "net-unreachable-tos",
207 12 => "host-unreachable-tos",
208 13 => "pkt-filtered",
209 14 => "precedence-violation",
210 15 => "precedence-cutoff",
211 _ => return write!(f, "unknown-{}", self.0),
212 };
213 write!(f, "{}", s)
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
219#[repr(transparent)]
220pub struct IcmpCodeRedirect(pub u8);
221
222impl IcmpCodeRedirect {
223 pub const REDIR_NET: IcmpCodeRedirect = IcmpCodeRedirect(0); pub const REDIR_HOST: IcmpCodeRedirect = IcmpCodeRedirect(1); pub const REDIR_NETTOS: IcmpCodeRedirect = IcmpCodeRedirect(2); pub const REDIR_HOSTTOS: IcmpCodeRedirect = IcmpCodeRedirect(3); }
228
229impl From<u8> for IcmpCodeRedirect {
230 fn from(value: u8) -> Self {
231 IcmpCodeRedirect(value)
232 }
233}
234
235impl From<IcmpCodeRedirect> for u8 {
236 fn from(value: IcmpCodeRedirect) -> Self {
237 value.0
238 }
239}
240
241impl fmt::Display for IcmpCodeRedirect {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 let s = match self.0 {
244 0 => "redirect-net",
245 1 => "redirect-host",
246 2 => "redirect-net-tos",
247 3 => "redirect-host-tos",
248 _ => return write!(f, "unknown-{}", self.0),
249 };
250 write!(f, "{}", s)
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
256#[repr(transparent)]
257pub struct IcmpCodeTimeExceed(pub u8);
258
259impl IcmpCodeTimeExceed {
260 pub const EXC_TTL: IcmpCodeTimeExceed = IcmpCodeTimeExceed(0); pub const EXC_FRAGTIME: IcmpCodeTimeExceed = IcmpCodeTimeExceed(1); }
263
264impl From<u8> for IcmpCodeTimeExceed {
265 fn from(value: u8) -> Self {
266 IcmpCodeTimeExceed(value)
267 }
268}
269
270impl From<IcmpCodeTimeExceed> for u8 {
271 fn from(value: IcmpCodeTimeExceed) -> Self {
272 value.0
273 }
274}
275
276impl fmt::Display for IcmpCodeTimeExceed {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 let s = match self.0 {
279 0 => "ttl-exceeded",
280 1 => "frag-time-exceeded",
281 _ => return write!(f, "unknown-{}", self.0),
282 };
283 write!(f, "{}", s)
284 }
285}
286
287#[repr(C, packed)]
289#[derive(
290 FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
291)]
292pub struct IcmpHeader {
293 icmp_type: IcmpType,
294 code: u8,
295 checksum: U16<BigEndian>,
296 un: U32<BigEndian>,
302}
303
304impl IcmpHeader {
305 pub const FIXED_LEN: usize = mem::size_of::<IcmpHeader>();
306
307 #[inline]
309 pub fn icmp_type(&self) -> IcmpType {
310 self.icmp_type
311 }
312
313 #[inline]
315 pub fn code(&self) -> u8 {
316 self.code
317 }
318
319 #[inline]
321 pub fn checksum(&self) -> u16 {
322 self.checksum.get()
323 }
324
325 #[inline]
327 pub fn un(&self) -> u32 {
328 self.un.get()
329 }
330
331 #[inline]
333 pub fn echo_id(&self) -> u16 {
334 (self.un.get() >> 16) as u16
335 }
336
337 #[inline]
339 pub fn echo_sequence(&self) -> u16 {
340 (self.un.get() & 0xFFFF) as u16
341 }
342
343 #[inline]
345 pub fn gateway(&self) -> u32 {
346 self.un.get()
347 }
348
349 #[inline]
351 pub fn frag_mtu(&self) -> u16 {
352 (self.un.get() & 0xFFFF) as u16
353 }
354
355 #[inline]
357 pub fn is_valid(&self) -> bool {
358 true
361 }
362
363 pub fn compute_checksum(icmp_data: &[u8]) -> u16 {
365 let mut sum: u32 = 0;
366
367 let mut i = 0;
368 while i < icmp_data.len() {
369 if i + 1 < icmp_data.len() {
370 let word = u16::from_be_bytes([icmp_data[i], icmp_data[i + 1]]);
371 sum += word as u32;
372 i += 2;
373 } else {
374 let word = u16::from_be_bytes([icmp_data[i], 0]);
376 sum += word as u32;
377 i += 1;
378 }
379 }
380
381 while sum >> 16 != 0 {
383 sum = (sum & 0xFFFF) + (sum >> 16);
384 }
385
386 !sum as u16
388 }
389
390 pub fn verify_checksum(&self, icmp_data: &[u8]) -> bool {
392 let computed = Self::compute_checksum(icmp_data);
393 computed == 0 || computed == 0xFFFF
394 }
395}
396
397impl PacketHeader for IcmpHeader {
398 const NAME: &'static str = "IcmpHeader";
399 type InnerType = IcmpType;
400
401 #[inline]
402 fn inner_type(&self) -> Self::InnerType {
403 self.icmp_type
404 }
405
406 #[inline]
407 fn total_len(&self, _buf: &[u8]) -> usize {
408 Self::FIXED_LEN
409 }
410
411 #[inline]
412 fn is_valid(&self) -> bool {
413 self.is_valid()
414 }
415}
416
417impl HeaderParser for IcmpHeader {
418 type Output<'a> = &'a IcmpHeader;
419
420 #[inline]
421 fn into_view<'a>(header: &'a Self, _: &'a [u8]) -> Self::Output<'a> {
422 header
423 }
424}
425
426impl fmt::Display for IcmpHeader {
427 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
428 write!(f, "ICMP {}", self.icmp_type())?;
429
430 match self.icmp_type() {
432 IcmpType::ECHO | IcmpType::ECHO_REPLY => {
433 write!(f, " id={} seq={}", self.echo_id(), self.echo_sequence())?;
434 }
435 IcmpType::DEST_UNREACH => {
436 let code = IcmpCodeUnreachable::from(self.code());
437 write!(f, " code={}", code)?;
438 if self.code() == IcmpCodeUnreachable::FRAG_NEEDED.into() {
439 write!(f, " mtu={}", self.frag_mtu())?;
440 }
441 }
442 IcmpType::REDIRECT => {
443 let code = IcmpCodeRedirect::from(self.code());
444 write!(f, " code={} gateway={}", code, self.gateway())?;
445 }
446 IcmpType::TIME_EXCEEDED => {
447 let code = IcmpCodeTimeExceed::from(self.code());
448 write!(f, " code={}", code)?;
449 }
450 _ => {
451 if self.code() != 0 {
452 write!(f, " code={}", self.code())?;
453 }
454 }
455 }
456
457 Ok(())
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn test_icmp_type_constants() {
467 assert_eq!(IcmpType::ECHO_REPLY.0, 0);
468 assert_eq!(IcmpType::DEST_UNREACH.0, 3);
469 assert_eq!(IcmpType::ECHO.0, 8);
470 assert_eq!(IcmpType::TIME_EXCEEDED.0, 11);
471 }
472
473 #[test]
474 fn test_icmp_type_as_str() {
475 assert_eq!(format!("{}", IcmpType::ECHO), "echo-request");
476 assert_eq!(format!("{}", IcmpType::ECHO_REPLY), "echo-reply");
477 assert_eq!(format!("{}", IcmpType::DEST_UNREACH), "dest-unreachable");
478 assert_eq!(format!("{}", IcmpType::TIME_EXCEEDED), "time-exceeded");
479 assert_eq!(format!("{}", IcmpType::from(99)), "unknown-99");
480 }
481
482 #[test]
483 fn test_icmp_code_constants() {
484 assert_eq!(IcmpCodeUnreachable::NET_UNREACH.0, 0);
485 assert_eq!(IcmpCodeUnreachable::HOST_UNREACH.0, 1);
486 assert_eq!(IcmpCodeUnreachable::PORT_UNREACH.0, 3);
487
488 assert_eq!(IcmpCodeRedirect::REDIR_NET.0, 0);
489 assert_eq!(IcmpCodeRedirect::REDIR_HOST.0, 1);
490
491 assert_eq!(IcmpCodeTimeExceed::EXC_TTL.0, 0);
492 assert_eq!(IcmpCodeTimeExceed::EXC_FRAGTIME.0, 1);
493 }
494
495 #[test]
496 fn test_icmp_code_display() {
497 assert_eq!(
498 format!("{}", IcmpCodeUnreachable::PORT_UNREACH),
499 "port-unreachable"
500 );
501 assert_eq!(
502 format!("{}", IcmpCodeUnreachable::HOST_UNREACH),
503 "host-unreachable"
504 );
505 assert_eq!(format!("{}", IcmpCodeUnreachable::from(99)), "unknown-99");
506
507 assert_eq!(format!("{}", IcmpCodeRedirect::REDIR_HOST), "redirect-host");
508 assert_eq!(format!("{}", IcmpCodeRedirect::from(99)), "unknown-99");
509
510 assert_eq!(format!("{}", IcmpCodeTimeExceed::EXC_TTL), "ttl-exceeded");
511 assert_eq!(format!("{}", IcmpCodeTimeExceed::from(99)), "unknown-99");
512 }
513
514 #[test]
515 fn test_icmp_header_size() {
516 assert_eq!(mem::size_of::<IcmpHeader>(), 8);
517 assert_eq!(IcmpHeader::FIXED_LEN, 8);
518 }
519
520 #[test]
521 fn test_icmp_echo_request() {
522 let mut packet = Vec::new();
523
524 packet.push(8); packet.push(0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&1234u16.to_be_bytes()); packet.extend_from_slice(&5678u16.to_be_bytes()); let result = IcmpHeader::from_bytes(&packet);
532 assert!(result.is_ok());
533
534 let (header, _) = result.unwrap();
535 assert_eq!(header.icmp_type(), IcmpType::ECHO);
536 assert_eq!(header.code(), 0);
537 assert_eq!(header.echo_id(), 1234);
538 assert_eq!(header.echo_sequence(), 5678);
539 assert!(header.is_valid());
540 }
541
542 #[test]
543 fn test_icmp_echo_reply() {
544 let mut packet = Vec::new();
545
546 packet.push(0); packet.push(0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&1234u16.to_be_bytes()); packet.extend_from_slice(&5678u16.to_be_bytes()); let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
554
555 assert_eq!(header.icmp_type(), IcmpType::ECHO_REPLY);
556 assert_eq!(header.code(), 0);
557 assert_eq!(header.echo_id(), 1234);
558 assert_eq!(header.echo_sequence(), 5678);
559 }
560
561 #[test]
562 fn test_icmp_dest_unreachable() {
563 let mut packet = Vec::new();
564
565 packet.push(3); packet.push(IcmpCodeUnreachable::PORT_UNREACH.0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0u32.to_be_bytes()); let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
572
573 assert_eq!(header.icmp_type(), IcmpType::DEST_UNREACH);
574 assert_eq!(header.code(), IcmpCodeUnreachable::PORT_UNREACH.0);
575 }
576
577 #[test]
578 fn test_icmp_time_exceeded() {
579 let mut packet = Vec::new();
580
581 packet.push(11); packet.push(IcmpCodeTimeExceed::EXC_TTL.0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0u32.to_be_bytes()); let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
588
589 assert_eq!(header.icmp_type(), IcmpType::TIME_EXCEEDED);
590 assert_eq!(header.code(), IcmpCodeTimeExceed::EXC_TTL.0);
591 }
592
593 #[test]
594 fn test_icmp_redirect() {
595 let mut packet = Vec::new();
596
597 packet.push(5); packet.push(IcmpCodeRedirect::REDIR_HOST.0); packet.extend_from_slice(&0u16.to_be_bytes()); let gateway = 0xC0A80101u32;
604 packet.extend_from_slice(&gateway.to_be_bytes());
605
606 let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
607
608 assert_eq!(header.icmp_type(), IcmpType::REDIRECT);
609 assert_eq!(header.code(), IcmpCodeRedirect::REDIR_HOST.0);
610 assert_eq!(header.gateway(), gateway);
611 }
612
613 #[test]
614 fn test_icmp_frag_needed() {
615 let mut packet = Vec::new();
616
617 packet.push(3); packet.push(IcmpCodeUnreachable::FRAG_NEEDED.0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&1500u16.to_be_bytes()); let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
625
626 assert_eq!(header.icmp_type(), IcmpType::DEST_UNREACH);
627 assert_eq!(header.code(), IcmpCodeUnreachable::FRAG_NEEDED.0);
628 assert_eq!(header.frag_mtu(), 1500);
629 }
630
631 #[test]
632 fn test_icmp_parsing_too_small() {
633 let packet = vec![0u8; 7]; let result = IcmpHeader::from_bytes(&packet);
636 assert!(result.is_err());
637 }
638
639 #[test]
640 fn test_icmp_total_len() {
641 let packet = create_test_echo_packet();
642 let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
643
644 assert_eq!(header.total_len(&packet), 8);
646 }
647
648 #[test]
649 fn test_icmp_checksum_computation() {
650 let mut packet = Vec::new();
651
652 packet.push(8); packet.push(0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&0x1234u16.to_be_bytes()); packet.extend_from_slice(&0x5678u16.to_be_bytes()); packet.extend_from_slice(b"test data");
661
662 let checksum = IcmpHeader::compute_checksum(&packet);
663
664 assert_ne!(checksum, 0);
666
667 packet[2..4].copy_from_slice(&checksum.to_be_bytes());
669
670 let (header, _) = IcmpHeader::from_bytes(&packet).unwrap();
672 assert!(header.verify_checksum(&packet));
673 }
674
675 #[test]
676 fn test_icmp_from_bytes_with_payload() {
677 let mut packet = Vec::new();
678
679 packet.push(8); packet.push(0); packet.extend_from_slice(&0u16.to_be_bytes()); packet.extend_from_slice(&1u16.to_be_bytes()); packet.extend_from_slice(&1u16.to_be_bytes()); let payload_data = b"Hello ICMP!";
688 packet.extend_from_slice(payload_data);
689
690 let result = IcmpHeader::from_bytes(&packet);
691 assert!(result.is_ok());
692
693 let (header, payload) = result.unwrap();
694
695 assert_eq!(header.icmp_type(), IcmpType::ECHO);
697 assert_eq!(header.code(), 0);
698 assert_eq!(header.echo_id(), 1);
699 assert_eq!(header.echo_sequence(), 1);
700
701 assert_eq!(payload.len(), payload_data.len());
703 assert_eq!(payload, payload_data);
704 }
705
706 fn create_test_echo_packet() -> Vec<u8> {
708 let mut packet = Vec::new();
709
710 packet.push(8);
712
713 packet.push(0);
715
716 packet.extend_from_slice(&0u16.to_be_bytes());
718
719 packet.extend_from_slice(&1234u16.to_be_bytes());
721
722 packet.extend_from_slice(&1u16.to_be_bytes());
724
725 packet
726 }
727}