1use 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
13pub const ICMP_COMMON_HEADER_LEN: usize = 4;
15pub const ICMPV4_HEADER_LEN: usize = 8;
17pub const ICMPV4_PACKET_LEN: usize = ETHERNET_HEADER_LEN + IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
19pub const ICMPV4_IP_PACKET_LEN: usize = IPV4_HEADER_LEN + ICMPV4_HEADER_LEN;
21
22#[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 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 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#[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 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#[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
250pub 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 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 pub fn checksum_mode(&self) -> ChecksumMode {
324 self.checksum.mode()
325 }
326
327 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 pub fn enable_auto_checksum(&mut self) {
337 self.set_checksum_mode(ChecksumMode::Automatic);
338 }
339
340 pub fn disable_auto_checksum(&mut self) {
342 self.set_checksum_mode(ChecksumMode::Manual);
343 }
344
345 pub fn is_checksum_dirty(&self) -> bool {
347 self.checksum.is_dirty()
348 }
349
350 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 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 pub fn get_type(&self) -> IcmpType {
368 IcmpType::new(self.raw()[0])
369 }
370
371 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 pub fn get_code(&self) -> IcmpCode {
379 IcmpCode::new(self.raw()[1])
380 }
381
382 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 pub fn get_checksum(&self) -> u16 {
390 u16::from_be_bytes([self.raw()[2], self.raw()[3]])
391 }
392
393 pub fn set_checksum(&mut self, checksum: u16) {
395 self.write_checksum(checksum);
396 self.checksum.clear_dirty();
397 }
398}
399
400pub 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 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
413 pub struct Identifier(pub u16);
414
415 impl Identifier {
416 pub fn new(val: u16) -> Identifier {
418 Identifier(val)
419 }
420 pub fn value(&self) -> u16 {
421 self.0
422 }
423 }
424
425 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
427 pub struct SequenceNumber(pub u16);
428
429 impl SequenceNumber {
430 pub fn new(val: u16) -> SequenceNumber {
432 SequenceNumber(val)
433 }
434 pub fn value(&self) -> u16 {
435 self.0
436 }
437 }
438
439 #[allow(non_snake_case)]
442 #[allow(non_upper_case_globals)]
443 pub mod IcmpCodes {
444 use crate::icmp::IcmpCode;
445 pub const NoCode: IcmpCode = IcmpCode(0);
447 }
448
449 #[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 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
486 pub struct Identifier(pub u16);
487
488 impl Identifier {
489 pub fn new(val: u16) -> Identifier {
491 Identifier(val)
492 }
493 pub fn value(&self) -> u16 {
494 self.0
495 }
496 }
497
498 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
500 pub struct SequenceNumber(pub u16);
501
502 impl SequenceNumber {
503 pub fn new(val: u16) -> SequenceNumber {
505 SequenceNumber(val)
506 }
507 pub fn value(&self) -> u16 {
508 self.0
509 }
510 }
511
512 #[allow(non_snake_case)]
515 #[allow(non_upper_case_globals)]
516 pub mod IcmpCodes {
517 use crate::icmp::IcmpCode;
518 pub const NoCode: IcmpCode = IcmpCode(0);
520 }
521
522 #[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 #[allow(non_snake_case)]
559 #[allow(non_upper_case_globals)]
560 pub mod IcmpCodes {
561 use crate::icmp::IcmpCode;
562 pub const DestinationNetworkUnreachable: IcmpCode = IcmpCode(0);
564 pub const DestinationHostUnreachable: IcmpCode = IcmpCode(1);
566 pub const DestinationProtocolUnreachable: IcmpCode = IcmpCode(2);
568 pub const DestinationPortUnreachable: IcmpCode = IcmpCode(3);
570 pub const FragmentationRequiredAndDFFlagSet: IcmpCode = IcmpCode(4);
572 pub const SourceRouteFailed: IcmpCode = IcmpCode(5);
574 pub const DestinationNetworkUnknown: IcmpCode = IcmpCode(6);
576 pub const DestinationHostUnknown: IcmpCode = IcmpCode(7);
578 pub const SourceHostIsolated: IcmpCode = IcmpCode(8);
580 pub const NetworkAdministrativelyProhibited: IcmpCode = IcmpCode(9);
582 pub const HostAdministrativelyProhibited: IcmpCode = IcmpCode(10);
584 pub const NetworkUnreachableForTOS: IcmpCode = IcmpCode(11);
586 pub const HostUnreachableForTOS: IcmpCode = IcmpCode(12);
588 pub const CommunicationAdministrativelyProhibited: IcmpCode = IcmpCode(13);
590 pub const HostPrecedenceViolation: IcmpCode = IcmpCode(14);
592 pub const PrecedenceCutoffInEffect: IcmpCode = IcmpCode(15);
594 }
595
596 #[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 #[allow(non_snake_case)]
633 #[allow(non_upper_case_globals)]
634 pub mod IcmpCodes {
635 use crate::icmp::IcmpCode;
636 pub const TimeToLiveExceededInTransit: IcmpCode = IcmpCode(0);
638 pub const FragmentReasemblyTimeExceeded: IcmpCode = IcmpCode(1);
640 }
641 #[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, 0x04, 0xd2, 0x00, 0x2a, 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), 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), 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, 0, 1, 0, 1, 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, 0, 1, 0, 1, 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}