async_icmp/message/
echo.rs

1//! Support for ICMP Echo messages.
2//!
3//! See RFC 792 p14 or RFC 4443 section 4.1.
4//!
5//! Note that on Linux, the `id` field is overwritten to be the socket's local port when an Echo
6//! Request message is sent. See [`crate::platform::icmp_send_overwrite_echo_id_with_local_port`].
7
8use crate::{
9    message::{EncodeIcmpMessage, IcmpMessageBuffer, IcmpV4MsgType, IcmpV6MsgType},
10    Icmpv4, Icmpv6,
11};
12#[cfg(feature = "rand")]
13use rand::Rng;
14use std::fmt;
15use winnow::{combinator, token, Parser};
16
17/// Echo message "identifier" field.
18///
19/// # Examples
20///
21/// Constructing from a `u16` with big-endian byte ordering:
22///
23/// ```
24/// use async_icmp::message::echo::EchoId;
25///
26/// let id: EchoId = EchoId::from_be(123);
27/// ```
28///
29/// Generating a random id via the `rand` `Distribution` impl (enabled via the `rand` feature):
30///
31/// ```
32/// use async_icmp::message::echo::EchoId;
33///
34/// let id: EchoId = rand::random();
35/// ```
36#[derive(Clone, Copy, PartialEq, Eq, Hash)]
37pub struct EchoId([u8; 2]);
38
39impl EchoId {
40    /// Construct an id from the big-endian bytes of `num`.
41    pub fn from_be(num: u16) -> Self {
42        Self(num.to_be_bytes())
43    }
44    /// Construct an id from the little-endian bytes of `num`.
45    pub fn from_le(num: u16) -> Self {
46        Self(num.to_le_bytes())
47    }
48
49    /// Returns the id as a byte array.
50    pub fn as_array(&self) -> [u8; 2] {
51        self.0
52    }
53
54    /// Returns the id as a length-2 slice.
55    pub fn as_slice(&self) -> &[u8] {
56        &self.0
57    }
58
59    /// Returns the bytes interpreted as big-endian
60    pub fn as_be(&self) -> u16 {
61        u16::from_be_bytes(self.0)
62    }
63
64    /// Returns the bytes interpreted as little-endian
65    pub fn as_le(&self) -> u16 {
66        u16::from_le_bytes(self.0)
67    }
68}
69
70impl From<[u8; 2]> for EchoId {
71    fn from(value: [u8; 2]) -> Self {
72        Self(value)
73    }
74}
75
76impl fmt::Debug for EchoId {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "0x{}", hex::encode_upper(self.0))
79    }
80}
81
82#[cfg(feature = "rand")]
83impl rand::distributions::Distribution<EchoId> for rand::distributions::Standard {
84    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> EchoId {
85        EchoId::from_be(rng.gen())
86    }
87}
88
89/// Echo message "sequence number" field.
90///
91/// # Examples
92///
93/// Constructing from a `u16` with big-endian byte ordering:
94///
95/// ```
96/// use async_icmp::message::echo::EchoSeq;
97///
98/// let seq: EchoSeq = EchoSeq::from_be(456);
99/// ```
100#[derive(Clone, Copy, PartialEq, Eq, Hash)]
101pub struct EchoSeq([u8; 2]);
102
103impl EchoSeq {
104    /// Construct a seq num from the big-endian bytes of `num`.
105    pub fn from_be(num: u16) -> Self {
106        Self(num.to_be_bytes())
107    }
108    /// Construct a seq num from the little-endian bytes of `num`.
109    pub fn from_le(num: u16) -> Self {
110        Self(num.to_le_bytes())
111    }
112
113    /// Returns the sequence number as a byte array.
114    pub fn as_array(&self) -> [u8; 2] {
115        self.0
116    }
117
118    /// Returns the sequence number as a length-2 slice.
119    pub fn as_slice(&self) -> &[u8] {
120        &self.0
121    }
122
123    /// Returns the bytes interpreted as big-endian
124    pub fn as_be(&self) -> u16 {
125        u16::from_be_bytes(self.0)
126    }
127
128    /// Returns the bytes interpreted as little-endian
129    pub fn as_le(&self) -> u16 {
130        u16::from_le_bytes(self.0)
131    }
132}
133
134impl From<[u8; 2]> for EchoSeq {
135    fn from(value: [u8; 2]) -> Self {
136        Self(value)
137    }
138}
139
140impl fmt::Debug for EchoSeq {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "0x{}", hex::encode_upper(self.0))
143    }
144}
145
146/// An ICMP "Echo request" message, suitable for use with [`crate::socket::IcmpSocket::send_to`].
147///
148/// This can be re-used for different requests, if needed.
149///
150/// Sending this ICMP message should result in an "Echo reply" message.
151///
152/// This message supports both IPv4 and IPv6.
153///
154/// See RFC 792 and RFC 4443.
155#[derive(Clone, Debug)]
156pub struct IcmpEchoRequest {
157    /// Echo bytes (Following ICMP header):
158    ///
159    /// - 0-1: id
160    /// - 2-3: seq
161    /// - 4+ data
162    ///
163    /// Buffer body is always at least [`Self::ICMP_ECHO_HDR_LEN`] bytes.
164    buf: IcmpMessageBuffer,
165}
166
167impl IcmpEchoRequest {
168    /// Fortunately, both IPv4 and IPv6 Echo use an 4-byte header followed by arbitrary message
169    /// data.
170    ///
171    /// The ICMP message type is set at encode time. Other fields are set with individual setters.
172    const ICMP_ECHO_HDR_LEN: usize = 4;
173
174    /// Construct a new ICMP "Echo request" message with `id` and `seq` set to 0, and empty `data`.
175    #[allow(clippy::new_without_default)]
176    pub fn new() -> Self {
177        Self {
178            // Type will be filled in at encode time.
179            // Code is always 0.
180            // Body holds space for id and seq.
181            buf: IcmpMessageBuffer::new(0, 0, &[0; Self::ICMP_ECHO_HDR_LEN]),
182        }
183    }
184
185    /// Construct a new request with the specified `id`, `seq`, and `data`.
186    ///
187    /// `data` is arbitrary data expected to be returned in the Echo reply.
188    ///
189    /// Some hosts seem to append some zero bytes in the reply.
190    pub fn from_fields(id: EchoId, seq: EchoSeq, data: &[u8]) -> Self {
191        let mut req = Self::new();
192        req.set_id(id);
193        req.set_seq(seq);
194        req.set_data(data);
195
196        req
197    }
198
199    /// The id of the echo request
200    pub fn id(&self) -> EchoId {
201        <[u8; 2]>::try_from(&self.buf.body()[..2])
202            .map(EchoId::from)
203            .unwrap()
204    }
205
206    /// The seq num of the echo request
207    pub fn seq(&self) -> EchoSeq {
208        <[u8; 2]>::try_from(&self.buf.body()[2..4])
209            .map(EchoSeq::from)
210            .unwrap()
211    }
212
213    /// Set a new id number
214    pub fn set_id(&mut self, id: EchoId) {
215        self.buf.body_mut()[..2].copy_from_slice(id.as_slice());
216    }
217
218    /// Set a new sequence number
219    pub fn set_seq(&mut self, seq: EchoSeq) {
220        self.buf.body_mut()[2..4].copy_from_slice(seq.as_slice());
221    }
222
223    /// Set new data to be echoed
224    pub fn set_data(&mut self, data: &[u8]) {
225        self.buf.truncate_body(Self::ICMP_ECHO_HDR_LEN);
226        self.buf.extend_body(data.iter().cloned());
227    }
228}
229
230impl EncodeIcmpMessage<Icmpv4> for IcmpEchoRequest {
231    fn encode(&mut self) -> &mut IcmpMessageBuffer {
232        self.buf.set_type(IcmpV4MsgType::EchoRequest as u8);
233        &mut self.buf
234    }
235}
236
237impl EncodeIcmpMessage<Icmpv6> for IcmpEchoRequest {
238    fn encode(&mut self) -> &mut IcmpMessageBuffer {
239        self.buf.set_type(IcmpV6MsgType::EchoRequest as u8);
240        &mut self.buf
241    }
242}
243
244/// Parse the body of an echo reply message into (`ident`, `seq`, `data`).
245///
246/// Since ICMPv4 and ICMPv6 share the same Echo Reply format, this works for either.
247///
248/// This should only be used on ICMP message bodies of the appropriate type
249/// ([`IcmpV4MsgType::EchoReply`] or [`IcmpV6MsgType::EchoReply`], as appropiriate) and code (`0`)
250/// as per the relevant RFCs.
251pub fn parse_echo_reply(icmp_reply_body: &[u8]) -> Option<(EchoId, EchoSeq, &[u8])> {
252    (
253        token::take::<_, _, winnow::error::ContextError>(2_usize)
254            .try_map(|slice| <[u8; 2]>::try_from(slice).map(EchoId::from)),
255        token::take(2_usize).try_map(|slice| <[u8; 2]>::try_from(slice).map(EchoSeq::from)),
256        combinator::rest,
257    )
258        .parse(icmp_reply_body)
259        .ok()
260}