pktparse/
icmp.rs

1//! Handles parsing of ICMP
2
3use crate::ipv4::{address, parse_ipv4_header, IPv4Header};
4use nom::{bytes::streaming::take, number, IResult};
5use std::net::Ipv4Addr;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum Unreachable {
10    DestinationNetworkUnreachable,
11    DestinationHostUnreachable,
12    DestinationProtocolUnreachable,
13    DestinationPortUnreachable,
14    FragmentationRequired,
15    SourceRouteFailed,
16    DestinationNetworkUnknown,
17    DestinationHostUnknown,
18    SourceHostIsolated,
19    NetworkAdministrativelyProhibited,
20    HostAdministrativelyProhibited,
21    NetworkUnreachableForTos,
22    HostUnreachableForTos,
23    CommunicationAdministrativelyProhibited,
24    HostPrecedenceViolation,
25    PrecedentCutoffInEffect,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum Redirect {
31    Network,
32    Host,
33    TosAndNetwork,
34    TosAndHost,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub enum TimeExceeded {
40    TTL,
41    FragmentReassembly,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub enum ParameterProblem {
47    Pointer,
48    MissingRequiredOption,
49    BadLength,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54pub enum ExtendedEchoReply {
55    NoError,
56    MalformedQuery,
57    NoSuchInterface,
58    NoSuchTableEntry,
59    MupltipleInterfacesStatisfyQuery,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub enum IcmpCode {
65    EchoReply,
66    Reserved,
67    DestinationUnreachable(Unreachable),
68    SourceQuench,
69    Redirect(Redirect),
70    EchoRequest,
71    RouterAdvertisment,
72    RouterSolicication,
73    TimeExceeded(TimeExceeded),
74    ParameterProblem(ParameterProblem),
75    Timestamp,
76    TimestampReply,
77    ExtendedEchoRequest,
78    ExtendedEchoReply(ExtendedEchoReply),
79    Other(u16),
80}
81
82impl From<u16> for IcmpCode {
83    fn from(raw: u16) -> Self {
84        let [t, c] = raw.to_be_bytes();
85        match t {
86            0x00 => Self::EchoReply,
87            0x01 => Self::Reserved,
88            0x02 => Self::Reserved,
89            0x03 => match c {
90                0x00 => Self::DestinationUnreachable(Unreachable::DestinationNetworkUnreachable),
91                0x01 => Self::DestinationUnreachable(Unreachable::DestinationHostUnreachable),
92                0x02 => Self::DestinationUnreachable(Unreachable::DestinationProtocolUnreachable),
93                0x03 => Self::DestinationUnreachable(Unreachable::DestinationPortUnreachable),
94                0x04 => Self::DestinationUnreachable(Unreachable::FragmentationRequired),
95                0x05 => Self::DestinationUnreachable(Unreachable::SourceRouteFailed),
96                0x06 => Self::DestinationUnreachable(Unreachable::DestinationNetworkUnknown),
97                0x07 => Self::DestinationUnreachable(Unreachable::DestinationHostUnknown),
98                0x08 => Self::DestinationUnreachable(Unreachable::SourceHostIsolated),
99                0x09 => {
100                    Self::DestinationUnreachable(Unreachable::NetworkAdministrativelyProhibited)
101                }
102                0x0A => Self::DestinationUnreachable(Unreachable::HostAdministrativelyProhibited),
103                0x0B => Self::DestinationUnreachable(Unreachable::NetworkUnreachableForTos),
104                0x0C => Self::DestinationUnreachable(Unreachable::HostUnreachableForTos),
105                0x0D => Self::DestinationUnreachable(
106                    Unreachable::CommunicationAdministrativelyProhibited,
107                ),
108                0x0E => Self::DestinationUnreachable(Unreachable::HostPrecedenceViolation),
109                0x0F => Self::DestinationUnreachable(Unreachable::PrecedentCutoffInEffect),
110                _ => Self::Other(raw),
111            },
112            0x04 => match c {
113                0x00 => Self::SourceQuench,
114                _ => Self::Other(raw),
115            },
116            0x05 => match c {
117                0x00 => Self::Redirect(Redirect::Network),
118                0x01 => Self::Redirect(Redirect::Host),
119                0x02 => Self::Redirect(Redirect::TosAndNetwork),
120                0x03 => Self::Redirect(Redirect::TosAndHost),
121                _ => Self::Other(raw),
122            },
123            0x07 => Self::Reserved,
124            0x08 => Self::EchoRequest,
125            0x09 => Self::RouterAdvertisment,
126            0x0A => Self::RouterSolicication,
127            0x0B => match c {
128                0x00 => Self::TimeExceeded(TimeExceeded::TTL),
129                0x01 => Self::TimeExceeded(TimeExceeded::FragmentReassembly),
130                _ => Self::Other(raw),
131            },
132            0x0C => match c {
133                0x00 => Self::ParameterProblem(ParameterProblem::Pointer),
134                0x01 => Self::ParameterProblem(ParameterProblem::MissingRequiredOption),
135                0x02 => Self::ParameterProblem(ParameterProblem::BadLength),
136                _ => Self::Other(raw),
137            },
138            0x0D => Self::Timestamp,
139            0x0E => Self::TimestampReply,
140            0x2A => Self::ExtendedEchoRequest,
141            0x2B => match c {
142                0x00 => Self::ExtendedEchoReply(ExtendedEchoReply::NoError),
143                0x01 => Self::ExtendedEchoReply(ExtendedEchoReply::MalformedQuery),
144                0x02 => Self::ExtendedEchoReply(ExtendedEchoReply::NoSuchInterface),
145                0x03 => Self::ExtendedEchoReply(ExtendedEchoReply::NoSuchTableEntry),
146                0x04 => {
147                    Self::ExtendedEchoReply(ExtendedEchoReply::MupltipleInterfacesStatisfyQuery)
148                }
149                _ => Self::Other(raw),
150            },
151            _ => Self::Other(raw),
152        }
153    }
154}
155
156fn parse_icmp_code(input: &[u8]) -> IResult<&[u8], IcmpCode> {
157    let (input, code) = number::streaming::be_u16(input)?;
158
159    Ok((input, code.into()))
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
164#[repr(transparent)]
165pub struct IcmpPayloadPacket([u8; 8]);
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub enum IcmpData {
170    Unreachable {
171        nexthop_mtu: u16,
172        header: IPv4Header,
173        packet: IcmpPayloadPacket,
174    },
175    Redirect {
176        gateway: Ipv4Addr,
177        header: IPv4Header,
178        packet: IcmpPayloadPacket,
179    },
180    TimeExceeded {
181        header: IPv4Header,
182        packet: IcmpPayloadPacket,
183    },
184    None,
185}
186
187fn parse_ipv4_header_and_packet(input: &[u8]) -> IResult<&[u8], (IPv4Header, IcmpPayloadPacket)> {
188    let (input, header) = parse_ipv4_header(input)?;
189    let mut packet: [u8; 8] = Default::default();
190    let (input, data) = take(8usize)(input)?;
191    packet.copy_from_slice(data);
192
193    Ok((input, (header, IcmpPayloadPacket(packet))))
194}
195
196fn parse_icmp_unreachable_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
197    let (input, _) = number::streaming::be_u16(input)?;
198    let (input, nexthop_mtu) = number::streaming::be_u16(input)?;
199    let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
200
201    Ok((
202        input,
203        IcmpData::Unreachable {
204            nexthop_mtu,
205            header,
206            packet,
207        },
208    ))
209}
210
211fn parse_icmp_redirect_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
212    let (input, gateway) = address(input)?;
213    let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
214
215    Ok((
216        input,
217        IcmpData::Redirect {
218            gateway,
219            header,
220            packet,
221        },
222    ))
223}
224
225fn parse_icmp_timeexceeded_data(input: &[u8]) -> IResult<&[u8], IcmpData> {
226    let (input, _) = number::streaming::be_u32(input)?;
227    let (input, (header, packet)) = parse_ipv4_header_and_packet(input)?;
228
229    Ok((input, IcmpData::TimeExceeded { header, packet }))
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
233#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
234pub struct IcmpHeader {
235    pub code: IcmpCode,
236    pub checksum: u16,
237    pub data: IcmpData,
238}
239
240pub fn parse_icmp_header(input: &[u8]) -> IResult<&[u8], IcmpHeader> {
241    let (input, code) = parse_icmp_code(input)?;
242    let (input, checksum) = number::streaming::be_u16(input)?;
243
244    let (input, data) = match code {
245        IcmpCode::DestinationUnreachable(_) => parse_icmp_unreachable_data(input)?,
246        IcmpCode::Redirect(_) => parse_icmp_redirect_data(input)?,
247        IcmpCode::TimeExceeded(_) => parse_icmp_timeexceeded_data(input)?,
248        _ => (input, IcmpData::None),
249    };
250
251    Ok((
252        input,
253        IcmpHeader {
254            code,
255            checksum,
256            data,
257        },
258    ))
259}
260
261#[cfg(test)]
262mod tests {
263    use super::{
264        parse_icmp_header, IcmpCode, IcmpData, IcmpHeader, IcmpPayloadPacket, Redirect, Unreachable,
265    };
266    use crate::ip::IPProtocol;
267    use crate::ipv4::IPv4Header;
268    use nom::{Err, Needed};
269    use std::net::Ipv4Addr;
270
271    const EMPTY_SLICE: &'static [u8] = &[];
272
273    fn get_icmp_ipv4_header_and_packet() -> (IPv4Header, IcmpPayloadPacket) {
274        (
275            IPv4Header {
276                version: 4,
277                ihl: 5,
278                tos: 0,
279                length: 1500,
280                id: 0x1ae6,
281                flags: 0x01,
282                fragment_offset: 0,
283                ttl: 64,
284                protocol: IPProtocol::ICMP,
285                chksum: 0x22ed,
286                source_addr: Ipv4Addr::new(10, 10, 1, 135),
287                dest_addr: Ipv4Addr::new(10, 10, 1, 180),
288            },
289            IcmpPayloadPacket([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]),
290        )
291    }
292
293    fn get_icmp_redirect_data() -> (Vec<u8>, IcmpHeader) {
294        let bytes = [
295            5, // type
296            1, // code
297            0xaa, 0xbb, // checksum
298            0x0a, 0x0a, 0x01, 0x86, // gateway addr
299            0x45, /* IP version and length = 20 */
300            0x00, /* Differentiated services field */
301            0x05, 0xdc, /* Total length */
302            0x1a, 0xe6, /* Identification */
303            0x20, 0x00, /* flags and fragment offset */
304            0x40, /* TTL */
305            0x01, /* protocol */
306            0x22, 0xed, /* checksum */
307            0x0a, 0x0a, 0x01, 0x87, /* source IP */
308            0x0a, 0x0a, 0x01, 0xb4, /* destination IP */
309            0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
310        ];
311
312        let (header, packet) = get_icmp_ipv4_header_and_packet();
313
314        let expected = IcmpHeader {
315            code: IcmpCode::Redirect(Redirect::Host),
316            checksum: 0xaabb,
317            data: IcmpData::Redirect {
318                gateway: Ipv4Addr::new(10, 10, 1, 134),
319                header: header,
320                packet: packet,
321            },
322        };
323
324        (bytes.to_vec(), expected)
325    }
326
327    fn get_icmp_unreachable_data() -> (Vec<u8>, IcmpHeader) {
328        let bytes = [
329            3, // type
330            1, // code
331            0xaa, 0xbb, // checksum
332            0x00, 0x00, // unused
333            0x00, 0x7,  // Next-hop MTU
334            0x45, /* IP version and length = 20 */
335            0x00, /* Differentiated services field */
336            0x05, 0xdc, /* Total length */
337            0x1a, 0xe6, /* Identification */
338            0x20, 0x00, /* flags and fragment offset */
339            0x40, /* TTL */
340            0x01, /* protocol */
341            0x22, 0xed, /* checksum */
342            0x0a, 0x0a, 0x01, 0x87, /* source IP */
343            0x0a, 0x0a, 0x01, 0xb4, /* destination IP */
344            0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
345        ];
346
347        let (header, packet) = get_icmp_ipv4_header_and_packet();
348
349        let expected = IcmpHeader {
350            code: IcmpCode::DestinationUnreachable(Unreachable::DestinationHostUnreachable),
351            checksum: 0xaabb,
352            data: IcmpData::Unreachable {
353                nexthop_mtu: 7,
354                header: header,
355                packet: packet,
356            },
357        };
358
359        (bytes.to_vec(), expected)
360    }
361
362    #[test]
363    fn icmp_unreachable() {
364        let (bytes, expected) = get_icmp_unreachable_data();
365        assert_eq!(parse_icmp_header(&bytes), Ok((EMPTY_SLICE, expected)))
366    }
367
368    #[test]
369    fn icmp_unreachable_incomplete() {
370        let (mut bytes, _) = get_icmp_unreachable_data();
371        bytes.pop();
372
373        assert_eq!(
374            parse_icmp_header(&bytes),
375            Err(Err::Incomplete(Needed::new(1)))
376        )
377    }
378
379    #[test]
380    fn icmp_redirect() {
381        let (bytes, expected) = get_icmp_redirect_data();
382        assert_eq!(parse_icmp_header(&bytes), Ok((EMPTY_SLICE, expected)))
383    }
384
385    #[test]
386    fn icmp_redirect_incomplete() {
387        let (mut bytes, _) = get_icmp_redirect_data();
388        bytes.pop();
389
390        assert_eq!(
391            parse_icmp_header(&bytes),
392            Err(Err::Incomplete(Needed::new(1)))
393        )
394    }
395}