1use core::fmt;
8
9use net_types::ip::{GenericOverIp, Ipv4, Ipv4Addr};
10use packet::{BufferView, ParsablePacket, ParseMetadata};
11use zerocopy::byteorder::network_endian::U32;
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, SplitByteSlice, Unaligned};
13
14use crate::error::{ParseError, ParseResult};
15
16use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
17use super::{
18 IcmpIpExt, IcmpMessageType, IcmpPacket, IcmpPacketRaw, IcmpParseArgs, IcmpZeroCode, IdAndSeq,
19 OriginalPacket, peek_message_type,
20};
21
22#[macro_export]
33macro_rules! icmpv4_dispatch {
34 (__internal__, $x:ident, $variable:pat => $expression:expr, $typ:ty) => {
35 {
36 use $typ::*;
37
38 match $x {
39 EchoReply($variable) => $expression,
40 DestUnreachable($variable) => $expression,
41 Redirect($variable) => $expression,
42 EchoRequest($variable) => $expression,
43 TimeExceeded($variable) => $expression,
44 ParameterProblem($variable) => $expression,
45 TimestampRequest($variable) => $expression,
46 TimestampReply($variable) => $expression,
47 }
48 }
49 };
50 ($x:ident : raw, $variable:pat => $expression:expr) => {
51 $crate::icmpv4_dispatch!(__internal__,
52 $x, $variable => $expression,
53 $crate::icmp::Icmpv4PacketRaw)
54 };
55 ($x:ident, $variable:pat => $expression:expr) => {
56 $crate::icmpv4_dispatch!(__internal__,
57 $x, $variable => $expression,
58 $crate::icmp::Icmpv4Packet)
59 };
60}
61
62#[allow(missing_docs)]
70pub enum Icmpv4Packet<B: SplitByteSlice> {
71 EchoReply(IcmpPacket<Ipv4, B, IcmpEchoReply>),
72 DestUnreachable(IcmpPacket<Ipv4, B, IcmpDestUnreachable>),
73 Redirect(IcmpPacket<Ipv4, B, Icmpv4Redirect>),
74 EchoRequest(IcmpPacket<Ipv4, B, IcmpEchoRequest>),
75 TimeExceeded(IcmpPacket<Ipv4, B, IcmpTimeExceeded>),
76 ParameterProblem(IcmpPacket<Ipv4, B, Icmpv4ParameterProblem>),
77 TimestampRequest(IcmpPacket<Ipv4, B, Icmpv4TimestampRequest>),
78 TimestampReply(IcmpPacket<Ipv4, B, Icmpv4TimestampReply>),
79}
80
81impl<B: SplitByteSlice + fmt::Debug> fmt::Debug for Icmpv4Packet<B> {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 use self::Icmpv4Packet::*;
84 match self {
85 DestUnreachable(p) => f.debug_tuple("DestUnreachable").field(p).finish(),
86 EchoReply(p) => f.debug_tuple("EchoReply").field(p).finish(),
87 EchoRequest(p) => f.debug_tuple("EchoRequest").field(p).finish(),
88 ParameterProblem(p) => f.debug_tuple("ParameterProblem").field(p).finish(),
89 Redirect(p) => f.debug_tuple("Redirect").field(p).finish(),
90 TimeExceeded(p) => f.debug_tuple("TimeExceeded").field(p).finish(),
91 TimestampReply(p) => f.debug_tuple("TimestampReply").field(p).finish(),
92 TimestampRequest(p) => f.debug_tuple("TimestampRequest").field(p).finish(),
93 }
94 }
95}
96
97impl<B: SplitByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv4Addr>> for Icmpv4Packet<B> {
98 type Error = ParseError;
99
100 fn parse_metadata(&self) -> ParseMetadata {
101 icmpv4_dispatch!(self, p => p.parse_metadata())
102 }
103
104 fn parse<BV: BufferView<B>>(buffer: BV, args: IcmpParseArgs<Ipv4Addr>) -> ParseResult<Self> {
105 macro_rules! mtch {
106 ($buffer:expr, $args:expr, $($variant:ident => $type:ty,)*) => {
107 match peek_message_type($buffer.as_ref())? {
108 $(Icmpv4MessageType::$variant => {
109 let packet = <IcmpPacket<Ipv4, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
110 Icmpv4Packet::$variant(packet)
111 })*
112 }
113 }
114 }
115
116 Ok(mtch!(
117 buffer,
118 args,
119 EchoReply => IcmpEchoReply,
120 DestUnreachable => IcmpDestUnreachable,
121 Redirect => Icmpv4Redirect,
122 EchoRequest => IcmpEchoRequest,
123 TimeExceeded => IcmpTimeExceeded,
124 ParameterProblem => Icmpv4ParameterProblem,
125 TimestampRequest => Icmpv4TimestampRequest,
126 TimestampReply => Icmpv4TimestampReply,
127 ))
128 }
129}
130
131#[allow(missing_docs)]
139pub enum Icmpv4PacketRaw<B: SplitByteSlice> {
140 EchoReply(IcmpPacketRaw<Ipv4, B, IcmpEchoReply>),
141 DestUnreachable(IcmpPacketRaw<Ipv4, B, IcmpDestUnreachable>),
142 Redirect(IcmpPacketRaw<Ipv4, B, Icmpv4Redirect>),
143 EchoRequest(IcmpPacketRaw<Ipv4, B, IcmpEchoRequest>),
144 TimeExceeded(IcmpPacketRaw<Ipv4, B, IcmpTimeExceeded>),
145 ParameterProblem(IcmpPacketRaw<Ipv4, B, Icmpv4ParameterProblem>),
146 TimestampRequest(IcmpPacketRaw<Ipv4, B, Icmpv4TimestampRequest>),
147 TimestampReply(IcmpPacketRaw<Ipv4, B, Icmpv4TimestampReply>),
148}
149
150impl<B: SplitByteSlice> ParsablePacket<B, ()> for Icmpv4PacketRaw<B> {
151 type Error = ParseError;
152
153 fn parse_metadata(&self) -> ParseMetadata {
154 icmpv4_dispatch!(self: raw, p => p.parse_metadata())
155 }
156
157 fn parse<BV: BufferView<B>>(buffer: BV, _args: ()) -> ParseResult<Self> {
158 macro_rules! mtch {
159 ($buffer:expr, $($variant:ident => $type:ty,)*) => {
160 match peek_message_type($buffer.as_ref())? {
161 $(Icmpv4MessageType::$variant => {
162 let packet = <IcmpPacketRaw<Ipv4, B, $type> as ParsablePacket<_, _>>::parse($buffer, ())?;
163 Icmpv4PacketRaw::$variant(packet)
164 })*
165 }
166 }
167 }
168
169 Ok(mtch!(
170 buffer,
171 EchoReply => IcmpEchoReply,
172 DestUnreachable => IcmpDestUnreachable,
173 Redirect => Icmpv4Redirect,
174 EchoRequest => IcmpEchoRequest,
175 TimeExceeded => IcmpTimeExceeded,
176 ParameterProblem => Icmpv4ParameterProblem,
177 TimestampRequest => Icmpv4TimestampRequest,
178 TimestampReply => Icmpv4TimestampReply,
179 ))
180 }
181}
182
183create_protocol_enum!(
184 #[allow(missing_docs)]
185 #[derive(PartialEq, Copy, Clone)]
186 pub enum Icmpv4MessageType: u8 {
187 EchoReply, 0, "Echo Reply";
188 DestUnreachable, 3, "Destination Unreachable";
189 Redirect, 5, "Redirect";
190 EchoRequest, 8, "Echo Request";
191 TimeExceeded, 11, "Time Exceeded";
192 ParameterProblem, 12, "Parameter Problem";
193 TimestampRequest, 13, "Timestamp Request";
194 TimestampReply, 14, "Timestamp Reply";
195 }
196);
197
198impl<I: IcmpIpExt> GenericOverIp<I> for Icmpv4MessageType {
199 type Type = I::IcmpMessageType;
200}
201
202impl IcmpMessageType for Icmpv4MessageType {
203 fn is_error_or_redirect(self) -> bool {
204 match self {
205 Icmpv4MessageType::DestUnreachable
206 | Icmpv4MessageType::Redirect
207 | Icmpv4MessageType::TimeExceeded
208 | Icmpv4MessageType::ParameterProblem => true,
209 _ => false,
210 }
211 }
212}
213
214create_protocol_enum!(
215 #[allow(missing_docs)]
216 #[derive(PartialEq, Copy, Clone)]
217 pub enum Icmpv4DestUnreachableCode: u8 {
218 DestNetworkUnreachable, 0, "Destination Network Unreachable";
219 DestHostUnreachable, 1, "Destination Host Unreachable";
220 DestProtocolUnreachable, 2, "Destination Protocol Unreachable";
221 DestPortUnreachable, 3, "Destination Port Unreachable";
222 FragmentationRequired, 4, "Fragmentation Required";
223 SourceRouteFailed, 5, "Source Route Failed";
224 DestNetworkUnknown, 6, "Destination Network Unknown";
225 DestHostUnknown, 7, "Destination Host Unknown";
226 SourceHostIsolated, 8, "Source Host Isolated";
227 NetworkAdministrativelyProhibited, 9, "Network Administratively Prohibited";
228 HostAdministrativelyProhibited, 10, "Host Administratively Prohibited";
229 NetworkUnreachableForToS, 11, "Network Unreachable For ToS";
230 HostUnreachableForToS, 12, "Host Unreachable For ToS";
231 CommAdministrativelyProhibited, 13, "Comm Administratively Prohibited";
232 HostPrecedenceViolation, 14, "Host Precedence Violation";
233 PrecedenceCutoffInEffect, 15, "Precedence Cutoff In Effect";
234 }
235);
236
237impl_icmp_message!(
238 Ipv4,
239 IcmpDestUnreachable,
240 DestUnreachable,
241 Icmpv4DestUnreachableCode,
242 OriginalPacket<B>
243);
244
245create_protocol_enum!(
246 #[allow(missing_docs)]
247 #[derive(PartialEq, Copy, Clone)]
248 pub enum Icmpv4RedirectCode: u8 {
249 Network, 0, "Network";
250 Host, 1, "Host";
251 ToSNetwork, 2, "ToS Network";
252 ToSHost, 3, "ToS Host";
253 }
254);
255
256#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
258#[repr(C)]
259pub struct Icmpv4Redirect {
260 gateway: Ipv4Addr,
261}
262
263impl_icmp_message!(Ipv4, Icmpv4Redirect, Redirect, Icmpv4RedirectCode, OriginalPacket<B>);
264
265create_protocol_enum!(
266 #[allow(missing_docs)]
267 #[derive(PartialEq, Copy, Clone)]
268 pub enum Icmpv4TimeExceededCode: u8 {
269 TtlExpired, 0, "TTL Expired";
270 FragmentReassemblyTimeExceeded, 1, "Fragment Reassembly Time Exceeded";
271 }
272);
273
274impl_icmp_message!(Ipv4, IcmpTimeExceeded, TimeExceeded, Icmpv4TimeExceededCode, OriginalPacket<B>);
275
276#[derive(
277 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
278)]
279#[repr(C)]
280struct IcmpTimestampData {
281 origin_timestamp: U32,
282 recv_timestamp: U32,
283 tx_timestamp: U32,
284}
285
286#[derive(
287 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
288)]
289#[repr(C)]
290struct Timestamp {
291 id_seq: IdAndSeq,
292 timestamps: IcmpTimestampData,
293}
294
295#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
297#[repr(transparent)]
298pub struct Icmpv4TimestampRequest(Timestamp);
299
300impl Icmpv4TimestampRequest {
301 pub fn new(origin_timestamp: u32, id: u16, seq: u16) -> Icmpv4TimestampRequest {
307 Icmpv4TimestampRequest(Timestamp {
308 id_seq: IdAndSeq::new(id, seq),
309 timestamps: IcmpTimestampData {
310 origin_timestamp: U32::new(origin_timestamp),
311 recv_timestamp: U32::ZERO,
312 tx_timestamp: U32::ZERO,
313 },
314 })
315 }
316
317 pub fn reply(&self, recv_timestamp: u32, tx_timestamp: u32) -> Icmpv4TimestampReply {
330 let mut ret = self.0;
331 ret.timestamps.recv_timestamp = U32::new(recv_timestamp);
332 ret.timestamps.tx_timestamp = U32::new(tx_timestamp);
333 Icmpv4TimestampReply(ret)
334 }
335}
336
337#[derive(
339 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
340)]
341#[repr(transparent)]
342pub struct Icmpv4TimestampReply(Timestamp);
343
344impl_icmp_message!(Ipv4, Icmpv4TimestampRequest, TimestampRequest, IcmpZeroCode);
345impl_icmp_message!(Ipv4, Icmpv4TimestampReply, TimestampReply, IcmpZeroCode);
346
347create_protocol_enum! (
348 #[allow(missing_docs)]
349 #[derive(PartialEq, Copy, Clone)]
350 pub enum Icmpv4ParameterProblemCode: u8 {
351 PointerIndicatesError, 0, "Pointer Indicates Error";
352 MissingRequiredOption, 1, "Missing Required Option";
353 BadLength, 2, "Bad Length";
354 }
355);
356
357#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
359#[repr(C)]
360pub struct Icmpv4ParameterProblem {
361 pointer: u8,
362 _unused: [u8; 3],
363 }
366
367impl Icmpv4ParameterProblem {
368 pub fn new(pointer: u8) -> Icmpv4ParameterProblem {
370 Icmpv4ParameterProblem { pointer, _unused: [0; 3] }
371 }
372}
373
374impl_icmp_message!(
375 Ipv4,
376 Icmpv4ParameterProblem,
377 ParameterProblem,
378 Icmpv4ParameterProblemCode,
379 OriginalPacket<B>
380);
381
382#[cfg(test)]
383mod tests {
384 use core::fmt::Debug;
385 use packet::{InnerPacketBuilder, ParseBuffer, Serializer};
386
387 use super::*;
388 use crate::icmp::{IcmpMessage, MessageBody};
389 use crate::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketBuilder};
390
391 fn serialize_to_bytes<B: SplitByteSlice + Debug, M: IcmpMessage<Ipv4> + Debug>(
392 src_ip: Ipv4Addr,
393 dst_ip: Ipv4Addr,
394 icmp: &IcmpPacket<Ipv4, B, M>,
395 builder: Ipv4PacketBuilder,
396 ) -> Vec<u8> {
397 let (inner_header, inner_body) = icmp.message_body.bytes();
400 let complete_packet = if let Some(body) = inner_body {
401 [inner_header, body].concat()
402 } else {
403 Default::default()
404 };
405 let complete_msg = if inner_body.is_some() { &complete_packet } else { inner_header };
406
407 complete_msg
408 .into_serializer()
409 .wrap_in(icmp.builder(src_ip, dst_ip))
410 .wrap_in(builder)
411 .serialize_vec_outer()
412 .unwrap()
413 .as_ref()
414 .to_vec()
415 }
416
417 fn test_parse_and_serialize<
418 M: IcmpMessage<Ipv4> + Debug,
419 F: for<'a> FnOnce(&IcmpPacket<Ipv4, &'a [u8], M>),
420 >(
421 mut req: &[u8],
422 check: F,
423 ) {
424 let orig_req = req;
425
426 let ip = req.parse::<Ipv4Packet<_>>().unwrap();
427 let mut body = ip.body();
428 let icmp = body
429 .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
430 .unwrap();
431 check(&icmp);
432
433 let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
434 assert_eq!(&data[..], orig_req);
435 }
436
437 #[test]
438 fn test_parse_and_serialize_echo_request() {
439 use crate::testdata::icmp_echo::*;
440 test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
441 let (inner_header, inner_body) = icmp.message_body.bytes();
442 assert!(inner_body.is_none());
443 let complete_msg = inner_header;
444 assert_eq!(complete_msg, ECHO_DATA);
445 assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
446 assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
447 });
448 }
449
450 #[test]
451 fn test_parse_and_serialize_echo_response() {
452 use crate::testdata::icmp_echo::*;
453 test_parse_and_serialize::<IcmpEchoReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
454 let (header, body) = icmp.message_body.bytes();
455 assert!(body.is_none());
456 let complete_msg = header;
457 assert_eq!(complete_msg, ECHO_DATA);
458 assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
459 assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
460 });
461 }
462
463 #[test]
464 fn test_parse_and_serialize_timestamp_request() {
465 use crate::testdata::icmp_timestamp::*;
466 test_parse_and_serialize::<Icmpv4TimestampRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
467 assert_eq!(icmp.message().0.timestamps.origin_timestamp.get(), ORIGIN_TIMESTAMP);
468 assert_eq!(icmp.message().0.timestamps.tx_timestamp.get(), RX_TX_TIMESTAMP);
469 assert_eq!(icmp.message().0.id_seq.id.get(), IDENTIFIER);
470 assert_eq!(icmp.message().0.id_seq.seq.get(), SEQUENCE_NUM);
471 });
472 }
473
474 #[test]
475 fn test_parse_and_serialize_timestamp_reply() {
476 use crate::testdata::icmp_timestamp::*;
477 test_parse_and_serialize::<Icmpv4TimestampReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
478 assert_eq!(icmp.message().0.timestamps.origin_timestamp.get(), ORIGIN_TIMESTAMP);
479 assert_eq!(icmp.message().0.id_seq.id.get(), IDENTIFIER);
482 assert_eq!(icmp.message().0.id_seq.seq.get(), SEQUENCE_NUM);
483 });
484 }
485
486 #[test]
487 fn test_parse_and_serialize_dest_unreachable() {
488 use crate::testdata::icmp_dest_unreachable::*;
489 test_parse_and_serialize::<IcmpDestUnreachable, _>(IP_PACKET_BYTES, |icmp| {
490 assert_eq!(icmp.code(), Icmpv4DestUnreachableCode::DestHostUnreachable);
491 assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
492 });
493 }
494
495 #[test]
496 fn test_parse_and_serialize_redirect() {
497 use crate::testdata::icmp_redirect::*;
498 test_parse_and_serialize::<Icmpv4Redirect, _>(IP_PACKET_BYTES, |icmp| {
499 assert_eq!(icmp.code(), Icmpv4RedirectCode::Host);
500 assert_eq!(icmp.message().gateway, GATEWAY_ADDR);
501 });
502 }
503
504 #[test]
505 fn test_parse_and_serialize_time_exceeded() {
506 use crate::testdata::icmp_time_exceeded::*;
507 test_parse_and_serialize::<IcmpTimeExceeded, _>(IP_PACKET_BYTES, |icmp| {
508 assert_eq!(icmp.code(), Icmpv4TimeExceededCode::TtlExpired);
509 assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
510 });
511 }
512}