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 peek_message_type, IcmpIpExt, IcmpMessageType, IcmpPacket, IcmpPacketRaw, IcmpParseArgs,
19 IcmpZeroCode, IdAndSeq, OriginalPacket,
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(ref p) => f.debug_tuple("DestUnreachable").field(p).finish(),
86 EchoReply(ref p) => f.debug_tuple("EchoReply").field(p).finish(),
87 EchoRequest(ref p) => f.debug_tuple("EchoRequest").field(p).finish(),
88 ParameterProblem(ref p) => f.debug_tuple("ParameterProblem").field(p).finish(),
89 Redirect(ref p) => f.debug_tuple("Redirect").field(p).finish(),
90 TimeExceeded(ref p) => f.debug_tuple("TimeExceeded").field(p).finish(),
91 TimestampReply(ref p) => f.debug_tuple("TimestampReply").field(p).finish(),
92 TimestampRequest(ref 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_err(self) -> bool {
204 use Icmpv4MessageType::*;
205 [DestUnreachable, Redirect, TimeExceeded, ParameterProblem].contains(&self)
206 }
207}
208
209create_protocol_enum!(
210 #[allow(missing_docs)]
211 #[derive(PartialEq, Copy, Clone)]
212 pub enum Icmpv4DestUnreachableCode: u8 {
213 DestNetworkUnreachable, 0, "Destination Network Unreachable";
214 DestHostUnreachable, 1, "Destination Host Unreachable";
215 DestProtocolUnreachable, 2, "Destination Protocol Unreachable";
216 DestPortUnreachable, 3, "Destination Port Unreachable";
217 FragmentationRequired, 4, "Fragmentation Required";
218 SourceRouteFailed, 5, "Source Route Failed";
219 DestNetworkUnknown, 6, "Destination Network Unknown";
220 DestHostUnknown, 7, "Destination Host Unknown";
221 SourceHostIsolated, 8, "Source Host Isolated";
222 NetworkAdministrativelyProhibited, 9, "Network Administratively Prohibited";
223 HostAdministrativelyProhibited, 10, "Host Administratively Prohibited";
224 NetworkUnreachableForToS, 11, "Network Unreachable For ToS";
225 HostUnreachableForToS, 12, "Host Unreachable For ToS";
226 CommAdministrativelyProhibited, 13, "Comm Administratively Prohibited";
227 HostPrecedenceViolation, 14, "Host Precedence Violation";
228 PrecedenceCutoffInEffect, 15, "Precedence Cutoff In Effect";
229 }
230);
231
232impl_icmp_message!(
233 Ipv4,
234 IcmpDestUnreachable,
235 DestUnreachable,
236 Icmpv4DestUnreachableCode,
237 OriginalPacket<B>
238);
239
240create_protocol_enum!(
241 #[allow(missing_docs)]
242 #[derive(PartialEq, Copy, Clone)]
243 pub enum Icmpv4RedirectCode: u8 {
244 Network, 0, "Network";
245 Host, 1, "Host";
246 ToSNetwork, 2, "ToS Network";
247 ToSHost, 3, "ToS Host";
248 }
249);
250
251#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
253#[repr(C)]
254pub struct Icmpv4Redirect {
255 gateway: Ipv4Addr,
256}
257
258impl_icmp_message!(Ipv4, Icmpv4Redirect, Redirect, Icmpv4RedirectCode, OriginalPacket<B>);
259
260create_protocol_enum!(
261 #[allow(missing_docs)]
262 #[derive(PartialEq, Copy, Clone)]
263 pub enum Icmpv4TimeExceededCode: u8 {
264 TtlExpired, 0, "TTL Expired";
265 FragmentReassemblyTimeExceeded, 1, "Fragment Reassembly Time Exceeded";
266 }
267);
268
269impl_icmp_message!(Ipv4, IcmpTimeExceeded, TimeExceeded, Icmpv4TimeExceededCode, OriginalPacket<B>);
270
271#[derive(
272 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
273)]
274#[repr(C)]
275struct IcmpTimestampData {
276 origin_timestamp: U32,
277 recv_timestamp: U32,
278 tx_timestamp: U32,
279}
280
281#[derive(
282 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
283)]
284#[repr(C)]
285struct Timestamp {
286 id_seq: IdAndSeq,
287 timestamps: IcmpTimestampData,
288}
289
290#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
292#[repr(transparent)]
293pub struct Icmpv4TimestampRequest(Timestamp);
294
295impl Icmpv4TimestampRequest {
296 pub fn new(origin_timestamp: u32, id: u16, seq: u16) -> Icmpv4TimestampRequest {
302 Icmpv4TimestampRequest(Timestamp {
303 id_seq: IdAndSeq::new(id, seq),
304 timestamps: IcmpTimestampData {
305 origin_timestamp: U32::new(origin_timestamp),
306 recv_timestamp: U32::ZERO,
307 tx_timestamp: U32::ZERO,
308 },
309 })
310 }
311
312 pub fn reply(&self, recv_timestamp: u32, tx_timestamp: u32) -> Icmpv4TimestampReply {
325 let mut ret = self.0;
326 ret.timestamps.recv_timestamp = U32::new(recv_timestamp);
327 ret.timestamps.tx_timestamp = U32::new(tx_timestamp);
328 Icmpv4TimestampReply(ret)
329 }
330}
331
332#[derive(
334 Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Eq, PartialEq,
335)]
336#[repr(transparent)]
337pub struct Icmpv4TimestampReply(Timestamp);
338
339impl_icmp_message!(Ipv4, Icmpv4TimestampRequest, TimestampRequest, IcmpZeroCode);
340impl_icmp_message!(Ipv4, Icmpv4TimestampReply, TimestampReply, IcmpZeroCode);
341
342create_protocol_enum! (
343 #[allow(missing_docs)]
344 #[derive(PartialEq, Copy, Clone)]
345 pub enum Icmpv4ParameterProblemCode: u8 {
346 PointerIndicatesError, 0, "Pointer Indicates Error";
347 MissingRequiredOption, 1, "Missing Required Option";
348 BadLength, 2, "Bad Length";
349 }
350);
351
352#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
354#[repr(C)]
355pub struct Icmpv4ParameterProblem {
356 pointer: u8,
357 _unused: [u8; 3],
358 }
361
362impl Icmpv4ParameterProblem {
363 pub fn new(pointer: u8) -> Icmpv4ParameterProblem {
365 Icmpv4ParameterProblem { pointer, _unused: [0; 3] }
366 }
367}
368
369impl_icmp_message!(
370 Ipv4,
371 Icmpv4ParameterProblem,
372 ParameterProblem,
373 Icmpv4ParameterProblemCode,
374 OriginalPacket<B>
375);
376
377#[cfg(test)]
378mod tests {
379 use core::fmt::Debug;
380 use packet::{InnerPacketBuilder, ParseBuffer, Serializer};
381
382 use super::*;
383 use crate::icmp::{IcmpMessage, MessageBody};
384 use crate::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketBuilder};
385
386 fn serialize_to_bytes<B: SplitByteSlice + Debug, M: IcmpMessage<Ipv4> + Debug>(
387 src_ip: Ipv4Addr,
388 dst_ip: Ipv4Addr,
389 icmp: &IcmpPacket<Ipv4, B, M>,
390 builder: Ipv4PacketBuilder,
391 ) -> Vec<u8> {
392 let (inner_header, inner_body) = icmp.message_body.bytes();
395 let complete_packet = if let Some(body) = inner_body {
396 [inner_header, body].concat()
397 } else {
398 Default::default()
399 };
400 let complete_msg = if inner_body.is_some() { &complete_packet } else { inner_header };
401
402 complete_msg
403 .into_serializer()
404 .encapsulate(icmp.builder(src_ip, dst_ip))
405 .encapsulate(builder)
406 .serialize_vec_outer()
407 .unwrap()
408 .as_ref()
409 .to_vec()
410 }
411
412 fn test_parse_and_serialize<
413 M: IcmpMessage<Ipv4> + Debug,
414 F: for<'a> FnOnce(&IcmpPacket<Ipv4, &'a [u8], M>),
415 >(
416 mut req: &[u8],
417 check: F,
418 ) {
419 let orig_req = req;
420
421 let ip = req.parse::<Ipv4Packet<_>>().unwrap();
422 let mut body = ip.body();
423 let icmp = body
424 .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
425 .unwrap();
426 check(&icmp);
427
428 let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
429 assert_eq!(&data[..], orig_req);
430 }
431
432 #[test]
433 fn test_parse_and_serialize_echo_request() {
434 use crate::testdata::icmp_echo::*;
435 test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
436 let (inner_header, inner_body) = icmp.message_body.bytes();
437 assert!(inner_body.is_none());
438 let complete_msg = inner_header;
439 assert_eq!(complete_msg, ECHO_DATA);
440 assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
441 assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
442 });
443 }
444
445 #[test]
446 fn test_parse_and_serialize_echo_response() {
447 use crate::testdata::icmp_echo::*;
448 test_parse_and_serialize::<IcmpEchoReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
449 let (header, body) = icmp.message_body.bytes();
450 assert!(body.is_none());
451 let complete_msg = header;
452 assert_eq!(complete_msg, ECHO_DATA);
453 assert_eq!(icmp.message().id_seq.id.get(), IDENTIFIER);
454 assert_eq!(icmp.message().id_seq.seq.get(), SEQUENCE_NUM);
455 });
456 }
457
458 #[test]
459 fn test_parse_and_serialize_timestamp_request() {
460 use crate::testdata::icmp_timestamp::*;
461 test_parse_and_serialize::<Icmpv4TimestampRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
462 assert_eq!(icmp.message().0.timestamps.origin_timestamp.get(), ORIGIN_TIMESTAMP);
463 assert_eq!(icmp.message().0.timestamps.tx_timestamp.get(), RX_TX_TIMESTAMP);
464 assert_eq!(icmp.message().0.id_seq.id.get(), IDENTIFIER);
465 assert_eq!(icmp.message().0.id_seq.seq.get(), SEQUENCE_NUM);
466 });
467 }
468
469 #[test]
470 fn test_parse_and_serialize_timestamp_reply() {
471 use crate::testdata::icmp_timestamp::*;
472 test_parse_and_serialize::<Icmpv4TimestampReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
473 assert_eq!(icmp.message().0.timestamps.origin_timestamp.get(), ORIGIN_TIMESTAMP);
474 assert_eq!(icmp.message().0.id_seq.id.get(), IDENTIFIER);
477 assert_eq!(icmp.message().0.id_seq.seq.get(), SEQUENCE_NUM);
478 });
479 }
480
481 #[test]
482 fn test_parse_and_serialize_dest_unreachable() {
483 use crate::testdata::icmp_dest_unreachable::*;
484 test_parse_and_serialize::<IcmpDestUnreachable, _>(IP_PACKET_BYTES, |icmp| {
485 assert_eq!(icmp.code(), Icmpv4DestUnreachableCode::DestHostUnreachable);
486 assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
487 });
488 }
489
490 #[test]
491 fn test_parse_and_serialize_redirect() {
492 use crate::testdata::icmp_redirect::*;
493 test_parse_and_serialize::<Icmpv4Redirect, _>(IP_PACKET_BYTES, |icmp| {
494 assert_eq!(icmp.code(), Icmpv4RedirectCode::Host);
495 assert_eq!(icmp.message().gateway, GATEWAY_ADDR);
496 });
497 }
498
499 #[test]
500 fn test_parse_and_serialize_time_exceeded() {
501 use crate::testdata::icmp_time_exceeded::*;
502 test_parse_and_serialize::<IcmpTimeExceeded, _>(IP_PACKET_BYTES, |icmp| {
503 assert_eq!(icmp.code(), Icmpv4TimeExceededCode::TtlExpired);
504 assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
505 });
506 }
507}