async_icmp/message/
mod.rs

1//! ICMP message encoding and decoding.
2
3use crate::IcmpVersion;
4
5#[cfg(test)]
6mod tests;
7
8pub mod decode;
9pub mod echo;
10
11/// Encode an ICMP message to be sent via an [`crate::socket::IcmpSocket`].
12pub trait EncodeIcmpMessage<V: IcmpVersion> {
13    /// Encode the message per the spec for `V`.
14    fn encode(&mut self) -> &mut IcmpMessageBuffer;
15}
16
17/// A helper for constructing ICMP packets.
18#[derive(Clone, Debug)]
19pub struct IcmpMessageBuffer {
20    /// Message bytes:
21    ///
22    /// - 0: type
23    /// - 1: code
24    /// - 2-3: checksum
25    /// - 4+: per-type body
26    ///
27    /// The buffer is always at least 4 bytes long.
28    buf: Vec<u8>,
29}
30
31impl IcmpMessageBuffer {
32    const ICMP_HDR_LEN: usize = 4;
33
34    /// Create a new buffer with the provided ICMP type `typ`, `code`, and `body`.
35    pub fn new(typ: u8, code: u8, body: &[u8]) -> Self {
36        let mut buf = vec![typ, code, 0, 0];
37        buf.extend_from_slice(body);
38        Self { buf }
39    }
40
41    /// Set byte 0 with the ICMP message type
42    pub fn set_type(&mut self, typ: u8) {
43        self.buf[0] = typ;
44    }
45
46    /// Set byte 1 with the ICMP message code
47    pub fn set_code(&mut self, code: u8) {
48        self.buf[1] = code;
49    }
50
51    /// Access the message body as a slice.
52    pub fn body(&self) -> &[u8] {
53        &self.buf[Self::ICMP_HDR_LEN..]
54    }
55
56    /// Access the message body as a mutable slice.
57    pub fn body_mut(&mut self) -> &mut [u8] {
58        &mut self.buf[Self::ICMP_HDR_LEN..]
59    }
60
61    /// Set the type-specific ICMP message body
62    pub fn set_body(&mut self, body: impl IntoIterator<Item = u8>) {
63        self.truncate_body(0);
64        self.extend_body(body);
65    }
66
67    /// Append `body_suffix` to the buffer, leaving the existing body in place
68    pub fn extend_body(&mut self, body_suffix: impl IntoIterator<Item = u8>) {
69        self.buf.extend(body_suffix);
70    }
71
72    /// Truncate the message body at `body_len` bytes
73    pub fn truncate_body(&mut self, body_len: usize) {
74        self.buf.truncate(Self::ICMP_HDR_LEN + body_len);
75    }
76
77    /// Populate bytes 2-3 with the IPv4 ones complement checksum.
78    pub(crate) fn calculate_icmpv4_checksum(&mut self) {
79        // checksum starts at zero
80        self.buf[2..4].fill(0);
81        let checksum = ones_complement_checksum(&self.buf);
82        self.buf[2..4].copy_from_slice(&checksum.to_be_bytes());
83    }
84
85    /// The entire buffer (ICMP header and body)
86    pub fn as_slice(&self) -> &[u8] {
87        &self.buf
88    }
89}
90
91/// RFC 792 ICMPv4 message types.
92///
93/// Provided as a reference for the message type magic numbers.
94#[repr(u8)]
95#[allow(missing_docs)]
96pub enum IcmpV4MsgType {
97    EchoReply = 0,
98    DestinationUnreachable = 3,
99    SourceQuench = 4,
100    Redirect = 5,
101    EchoRequest = 8,
102    TimeExceeded = 11,
103    ParameterProblem = 12,
104    Timestamp = 13,
105    TimestampReply = 14,
106    InfoRequest = 15,
107    InfoReply = 16,
108}
109
110/// RFC 4443 ICMPv6 message types.
111///
112/// Provided as a reference for the message type magic numbers.
113#[repr(u8)]
114#[allow(missing_docs)]
115pub enum IcmpV6MsgType {
116    DestinationUnreachable = 1,
117    PacketTooBig = 2,
118    TimeExceeded = 3,
119    ParameterProblem = 4,
120    PrivateExperimentationError1 = 100,
121    PrivateExperimentationError2 = 101,
122    ReservedForExpansionError = 127,
123    EchoRequest = 128,
124    EchoReply = 129,
125    PrivateExperimentationInfo1 = 200,
126    PrivateExperimentationInfo2 = 201,
127    ReservedForExpansionInfo = 255,
128}
129
130/// See https://www.rfc-editor.org/rfc/rfc1071 section 4.1
131fn ones_complement_checksum(data: &[u8]) -> u16 {
132    let last = if data.len() % 2 == 1 {
133        u16::from(data.last().copied().unwrap()) << 8
134    } else {
135        0_u16
136    };
137
138    let mut sum = data
139        .chunks_exact(2)
140        .map(|chunk| u16::from_be_bytes(chunk.try_into().unwrap()))
141        .fold(0_u32, |accum, num| accum.wrapping_add(num.into()))
142        .wrapping_add(last.into());
143
144    while (sum >> 16) > 0 {
145        sum = (sum & 0xffff) + (sum >> 16);
146    }
147
148    !(sum as u16)
149}