packet_formats/icmp/
icmpv4.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! ICMPv4
6
7use 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/// Dispatches expressions to the type-safe variants of [`Icmpv4Packet`] or
23/// [`Icmpv4PacketRaw`].
24///
25/// # Usage
26///
27/// ```
28/// icmpv4_dispatch!(packet, p => p.message_mut().foo());
29///
30/// icmpv4_dispatch!(packet: raw, p => p.message_mut().foo());
31/// ```
32#[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/// An ICMPv4 packet with a dynamic message type.
63///
64/// Unlike `IcmpPacket`, `Packet` only supports ICMPv4, and does not
65/// require a static message type. Each enum variant contains an `IcmpPacket` of
66/// the appropriate static type, making it easier to call `parse` without
67/// knowing the message type ahead of time while still getting the benefits of a
68/// statically-typed packet struct after parsing is complete.
69#[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/// A raw ICMPv4 packet with a dynamic message type.
132///
133/// Unlike `IcmpPacketRaw`, `Packet` only supports ICMPv4, and does not
134/// require a static message type. Each enum variant contains an `IcmpPacketRaw`
135/// of the appropriate static type, making it easier to call `parse` without
136/// knowing the message type ahead of time while still getting the benefits of a
137/// statically-typed packet struct after parsing is complete.
138#[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/// An ICMPv4 Redirect Message.
252#[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/// An ICMPv4 Timestamp Request message.
291#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
292#[repr(transparent)]
293pub struct Icmpv4TimestampRequest(Timestamp);
294
295impl Icmpv4TimestampRequest {
296    /// Creates an `Icmpv4TimestampRequest`.
297    ///
298    /// `new` constructs a new `Icmpv4TimestampRequest` with the given
299    /// parameters, and sets the Receive Timestamp and Transmit Timestamp values
300    /// to zero.
301    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    /// Reply to a Timestamp Request message.
313    ///
314    /// `reply` takes the `Icmpv4TimestampRequest` from a Timestamp Request
315    /// message, and produces the appropriate `Icmpv4TimestampReply` value for a
316    /// Timestamp Reply message. The original Originate Timestamp, ICMP ID, and
317    /// ICMP Sequence Number are retained, while the Receive Timestamp and
318    /// Transmit Timestamp are overwritten with the given values.
319    ///
320    /// The Receive Timestamp (`recv_timestamp`) indicates the time at which the
321    /// Timestamp Request was first received, while the Transmit Timestamp
322    /// (`tx_timestamp`) indicates the time at which the Timestamp Reply was
323    /// last processed before being sent.
324    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/// An ICMPv4 Timestamp Reply message.
333#[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/// An ICMPv4 Parameter Problem message.
353#[derive(Copy, Clone, Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
354#[repr(C)]
355pub struct Icmpv4ParameterProblem {
356    pointer: u8,
357    _unused: [u8; 3],
358    /* The rest of Icmpv4ParameterProblem is variable-length, so is stored in
359     * the message_body field in IcmpPacket */
360}
361
362impl Icmpv4ParameterProblem {
363    /// Returns a new `Icmpv4ParameterProblem` with the given pointer.
364    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        // We allocate a Vec<u8> only if the packet has records, otherwise we
393        // simply return a refernence to the header.
394        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            // TODO: Assert other values here?
475            // TODO: Check value of recv_timestamp and tx_timestamp
476            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}