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}