Skip to main content

ya_smoltcp/wire/
icmpv4.rs

1use byteorder::{ByteOrder, NetworkEndian};
2use core::{cmp, fmt};
3
4use crate::phy::ChecksumCapabilities;
5use crate::wire::ip::checksum;
6use crate::wire::{Ipv4Packet, Ipv4Repr};
7use crate::{Error, Result};
8
9enum_with_unknown! {
10    /// Internet protocol control message type.
11    pub enum Message(u8) {
12        /// Echo reply
13        EchoReply      =  0,
14        /// Destination unreachable
15        DstUnreachable =  3,
16        /// Message redirect
17        Redirect       =  5,
18        /// Echo request
19        EchoRequest    =  8,
20        /// Router advertisement
21        RouterAdvert   =  9,
22        /// Router solicitation
23        RouterSolicit  = 10,
24        /// Time exceeded
25        TimeExceeded   = 11,
26        /// Parameter problem
27        ParamProblem   = 12,
28        /// Timestamp
29        Timestamp      = 13,
30        /// Timestamp reply
31        TimestampReply = 14
32    }
33}
34
35impl fmt::Display for Message {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        match *self {
38            Message::EchoReply => write!(f, "echo reply"),
39            Message::DstUnreachable => write!(f, "destination unreachable"),
40            Message::Redirect => write!(f, "message redirect"),
41            Message::EchoRequest => write!(f, "echo request"),
42            Message::RouterAdvert => write!(f, "router advertisement"),
43            Message::RouterSolicit => write!(f, "router solicitation"),
44            Message::TimeExceeded => write!(f, "time exceeded"),
45            Message::ParamProblem => write!(f, "parameter problem"),
46            Message::Timestamp => write!(f, "timestamp"),
47            Message::TimestampReply => write!(f, "timestamp reply"),
48            Message::Unknown(id) => write!(f, "{}", id),
49        }
50    }
51}
52
53enum_with_unknown! {
54    /// Internet protocol control message subtype for type "Destination Unreachable".
55    pub enum DstUnreachable(u8) {
56        /// Destination network unreachable
57        NetUnreachable   =  0,
58        /// Destination host unreachable
59        HostUnreachable  =  1,
60        /// Destination protocol unreachable
61        ProtoUnreachable =  2,
62        /// Destination port unreachable
63        PortUnreachable  =  3,
64        /// Fragmentation required, and DF flag set
65        FragRequired     =  4,
66        /// Source route failed
67        SrcRouteFailed   =  5,
68        /// Destination network unknown
69        DstNetUnknown    =  6,
70        /// Destination host unknown
71        DstHostUnknown   =  7,
72        /// Source host isolated
73        SrcHostIsolated  =  8,
74        /// Network administratively prohibited
75        NetProhibited    =  9,
76        /// Host administratively prohibited
77        HostProhibited   = 10,
78        /// Network unreachable for ToS
79        NetUnreachToS    = 11,
80        /// Host unreachable for ToS
81        HostUnreachToS   = 12,
82        /// Communication administratively prohibited
83        CommProhibited   = 13,
84        /// Host precedence violation
85        HostPrecedViol   = 14,
86        /// Precedence cutoff in effect
87        PrecedCutoff     = 15
88    }
89}
90
91impl fmt::Display for DstUnreachable {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        match *self {
94            DstUnreachable::NetUnreachable => write!(f, "destination network unreachable"),
95            DstUnreachable::HostUnreachable => write!(f, "destination host unreachable"),
96            DstUnreachable::ProtoUnreachable => write!(f, "destination protocol unreachable"),
97            DstUnreachable::PortUnreachable => write!(f, "destination port unreachable"),
98            DstUnreachable::FragRequired => write!(f, "fragmentation required, and DF flag set"),
99            DstUnreachable::SrcRouteFailed => write!(f, "source route failed"),
100            DstUnreachable::DstNetUnknown => write!(f, "destination network unknown"),
101            DstUnreachable::DstHostUnknown => write!(f, "destination host unknown"),
102            DstUnreachable::SrcHostIsolated => write!(f, "source host isolated"),
103            DstUnreachable::NetProhibited => write!(f, "network administratively prohibited"),
104            DstUnreachable::HostProhibited => write!(f, "host administratively prohibited"),
105            DstUnreachable::NetUnreachToS => write!(f, "network unreachable for ToS"),
106            DstUnreachable::HostUnreachToS => write!(f, "host unreachable for ToS"),
107            DstUnreachable::CommProhibited => {
108                write!(f, "communication administratively prohibited")
109            }
110            DstUnreachable::HostPrecedViol => write!(f, "host precedence violation"),
111            DstUnreachable::PrecedCutoff => write!(f, "precedence cutoff in effect"),
112            DstUnreachable::Unknown(id) => write!(f, "{}", id),
113        }
114    }
115}
116
117enum_with_unknown! {
118    /// Internet protocol control message subtype for type "Redirect Message".
119    pub enum Redirect(u8) {
120        /// Redirect Datagram for the Network
121        Net     = 0,
122        /// Redirect Datagram for the Host
123        Host    = 1,
124        /// Redirect Datagram for the ToS & network
125        NetToS  = 2,
126        /// Redirect Datagram for the ToS & host
127        HostToS = 3
128    }
129}
130
131enum_with_unknown! {
132    /// Internet protocol control message subtype for type "Time Exceeded".
133    pub enum TimeExceeded(u8) {
134        /// TTL expired in transit
135        TtlExpired  = 0,
136        /// Fragment reassembly time exceeded
137        FragExpired = 1
138    }
139}
140
141enum_with_unknown! {
142    /// Internet protocol control message subtype for type "Parameter Problem".
143    pub enum ParamProblem(u8) {
144        /// Pointer indicates the error
145        AtPointer     = 0,
146        /// Missing a required option
147        MissingOption = 1,
148        /// Bad length
149        BadLength     = 2
150    }
151}
152
153/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer.
154#[derive(Debug, PartialEq, Clone)]
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156pub struct Packet<T: AsRef<[u8]>> {
157    buffer: T,
158}
159
160mod field {
161    use crate::wire::field::*;
162
163    pub const TYPE: usize = 0;
164    pub const CODE: usize = 1;
165    pub const CHECKSUM: Field = 2..4;
166
167    pub const UNUSED: Field = 4..8;
168
169    pub const ECHO_IDENT: Field = 4..6;
170    pub const ECHO_SEQNO: Field = 6..8;
171
172    pub const HEADER_END: usize = 8;
173}
174
175impl<T: AsRef<[u8]>> Packet<T> {
176    /// Imbue a raw octet buffer with ICMPv4 packet structure.
177    pub fn new_unchecked(buffer: T) -> Packet<T> {
178        Packet { buffer }
179    }
180
181    /// Shorthand for a combination of [new_unchecked] and [check_len].
182    ///
183    /// [new_unchecked]: #method.new_unchecked
184    /// [check_len]: #method.check_len
185    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
186        let packet = Self::new_unchecked(buffer);
187        packet.check_len()?;
188        Ok(packet)
189    }
190
191    /// Ensure that no accessor method will panic if called.
192    /// Returns `Err(Error::Truncated)` if the buffer is too short.
193    ///
194    /// The result of this check is invalidated by calling [set_header_len].
195    ///
196    /// [set_header_len]: #method.set_header_len
197    pub fn check_len(&self) -> Result<()> {
198        let len = self.buffer.as_ref().len();
199        if len < field::HEADER_END {
200            Err(Error::Truncated)
201        } else {
202            Ok(())
203        }
204    }
205
206    /// Consume the packet, returning the underlying buffer.
207    pub fn into_inner(self) -> T {
208        self.buffer
209    }
210
211    /// Return the message type field.
212    #[inline]
213    pub fn msg_type(&self) -> Message {
214        let data = self.buffer.as_ref();
215        Message::from(data[field::TYPE])
216    }
217
218    /// Return the message code field.
219    #[inline]
220    pub fn msg_code(&self) -> u8 {
221        let data = self.buffer.as_ref();
222        data[field::CODE]
223    }
224
225    /// Return the checksum field.
226    #[inline]
227    pub fn checksum(&self) -> u16 {
228        let data = self.buffer.as_ref();
229        NetworkEndian::read_u16(&data[field::CHECKSUM])
230    }
231
232    /// Return the identifier field (for echo request and reply packets).
233    ///
234    /// # Panics
235    /// This function may panic if this packet is not an echo request or reply packet.
236    #[inline]
237    pub fn echo_ident(&self) -> u16 {
238        let data = self.buffer.as_ref();
239        NetworkEndian::read_u16(&data[field::ECHO_IDENT])
240    }
241
242    /// Return the sequence number field (for echo request and reply packets).
243    ///
244    /// # Panics
245    /// This function may panic if this packet is not an echo request or reply packet.
246    #[inline]
247    pub fn echo_seq_no(&self) -> u16 {
248        let data = self.buffer.as_ref();
249        NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
250    }
251
252    /// Return the header length.
253    /// The result depends on the value of the message type field.
254    pub fn header_len(&self) -> usize {
255        match self.msg_type() {
256            Message::EchoRequest => field::ECHO_SEQNO.end,
257            Message::EchoReply => field::ECHO_SEQNO.end,
258            Message::DstUnreachable => field::UNUSED.end,
259            _ => field::UNUSED.end, // make a conservative assumption
260        }
261    }
262
263    /// Validate the header checksum.
264    ///
265    /// # Fuzzing
266    /// This function always returns `true` when fuzzing.
267    pub fn verify_checksum(&self) -> bool {
268        if cfg!(fuzzing) {
269            return true;
270        }
271
272        let data = self.buffer.as_ref();
273        checksum::data(data) == !0
274    }
275}
276
277impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
278    /// Return a pointer to the type-specific data.
279    #[inline]
280    pub fn data(&self) -> &'a [u8] {
281        let data = self.buffer.as_ref();
282        &data[self.header_len()..]
283    }
284}
285
286impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
287    /// Set the message type field.
288    #[inline]
289    pub fn set_msg_type(&mut self, value: Message) {
290        let data = self.buffer.as_mut();
291        data[field::TYPE] = value.into()
292    }
293
294    /// Set the message code field.
295    #[inline]
296    pub fn set_msg_code(&mut self, value: u8) {
297        let data = self.buffer.as_mut();
298        data[field::CODE] = value
299    }
300
301    /// Set the checksum field.
302    #[inline]
303    pub fn set_checksum(&mut self, value: u16) {
304        let data = self.buffer.as_mut();
305        NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
306    }
307
308    /// Set the identifier field (for echo request and reply packets).
309    ///
310    /// # Panics
311    /// This function may panic if this packet is not an echo request or reply packet.
312    #[inline]
313    pub fn set_echo_ident(&mut self, value: u16) {
314        let data = self.buffer.as_mut();
315        NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
316    }
317
318    /// Set the sequence number field (for echo request and reply packets).
319    ///
320    /// # Panics
321    /// This function may panic if this packet is not an echo request or reply packet.
322    #[inline]
323    pub fn set_echo_seq_no(&mut self, value: u16) {
324        let data = self.buffer.as_mut();
325        NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
326    }
327
328    /// Compute and fill in the header checksum.
329    pub fn fill_checksum(&mut self) {
330        self.set_checksum(0);
331        let checksum = {
332            let data = self.buffer.as_ref();
333            !checksum::data(data)
334        };
335        self.set_checksum(checksum)
336    }
337}
338
339impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
340    /// Return a mutable pointer to the type-specific data.
341    #[inline]
342    pub fn data_mut(&mut self) -> &mut [u8] {
343        let range = self.header_len()..;
344        let data = self.buffer.as_mut();
345        &mut data[range]
346    }
347}
348
349impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
350    fn as_ref(&self) -> &[u8] {
351        self.buffer.as_ref()
352    }
353}
354
355/// A high-level representation of an Internet Control Message Protocol version 4 packet header.
356#[derive(Debug, PartialEq, Eq, Clone, Copy)]
357#[cfg_attr(feature = "defmt", derive(defmt::Format))]
358#[non_exhaustive]
359pub enum Repr<'a> {
360    EchoRequest {
361        ident: u16,
362        seq_no: u16,
363        data: &'a [u8],
364    },
365    EchoReply {
366        ident: u16,
367        seq_no: u16,
368        data: &'a [u8],
369    },
370    DstUnreachable {
371        reason: DstUnreachable,
372        header: Ipv4Repr,
373        data: &'a [u8],
374    },
375}
376
377impl<'a> Repr<'a> {
378    /// Parse an Internet Control Message Protocol version 4 packet and return
379    /// a high-level representation.
380    pub fn parse<T>(
381        packet: &Packet<&'a T>,
382        checksum_caps: &ChecksumCapabilities,
383    ) -> Result<Repr<'a>>
384    where
385        T: AsRef<[u8]> + ?Sized,
386    {
387        // Valid checksum is expected.
388        if checksum_caps.icmpv4.rx() && !packet.verify_checksum() {
389            return Err(Error::Checksum);
390        }
391
392        match (packet.msg_type(), packet.msg_code()) {
393            (Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
394                ident: packet.echo_ident(),
395                seq_no: packet.echo_seq_no(),
396                data: packet.data(),
397            }),
398
399            (Message::EchoReply, 0) => Ok(Repr::EchoReply {
400                ident: packet.echo_ident(),
401                seq_no: packet.echo_seq_no(),
402                data: packet.data(),
403            }),
404
405            (Message::DstUnreachable, code) => {
406                let ip_packet = Ipv4Packet::new_checked(packet.data())?;
407
408                let payload = &packet.data()[ip_packet.header_len() as usize..];
409                // RFC 792 requires exactly eight bytes to be returned.
410                // We allow more, since there isn't a reason not to, but require at least eight.
411                if payload.len() < 8 {
412                    return Err(Error::Truncated);
413                }
414
415                Ok(Repr::DstUnreachable {
416                    reason: DstUnreachable::from(code),
417                    header: Ipv4Repr {
418                        src_addr: ip_packet.src_addr(),
419                        dst_addr: ip_packet.dst_addr(),
420                        protocol: ip_packet.protocol(),
421                        payload_len: payload.len(),
422                        hop_limit: ip_packet.hop_limit(),
423                    },
424                    data: payload,
425                })
426            }
427            _ => Err(Error::Unrecognized),
428        }
429    }
430
431    /// Return the length of a packet that will be emitted from this high-level representation.
432    pub fn buffer_len(&self) -> usize {
433        match self {
434            &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
435                field::ECHO_SEQNO.end + data.len()
436            }
437            &Repr::DstUnreachable { header, data, .. } => {
438                field::UNUSED.end + header.buffer_len() + data.len()
439            }
440        }
441    }
442
443    /// Emit a high-level representation into an Internet Control Message Protocol version 4
444    /// packet.
445    pub fn emit<T>(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities)
446    where
447        T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
448    {
449        packet.set_msg_code(0);
450        match *self {
451            Repr::EchoRequest {
452                ident,
453                seq_no,
454                data,
455            } => {
456                packet.set_msg_type(Message::EchoRequest);
457                packet.set_msg_code(0);
458                packet.set_echo_ident(ident);
459                packet.set_echo_seq_no(seq_no);
460                let data_len = cmp::min(packet.data_mut().len(), data.len());
461                packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
462            }
463
464            Repr::EchoReply {
465                ident,
466                seq_no,
467                data,
468            } => {
469                packet.set_msg_type(Message::EchoReply);
470                packet.set_msg_code(0);
471                packet.set_echo_ident(ident);
472                packet.set_echo_seq_no(seq_no);
473                let data_len = cmp::min(packet.data_mut().len(), data.len());
474                packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
475            }
476
477            Repr::DstUnreachable {
478                reason,
479                header,
480                data,
481            } => {
482                packet.set_msg_type(Message::DstUnreachable);
483                packet.set_msg_code(reason.into());
484
485                let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut());
486                header.emit(&mut ip_packet, checksum_caps);
487                let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
488                payload.copy_from_slice(data)
489            }
490        }
491
492        if checksum_caps.icmpv4.tx() {
493            packet.fill_checksum()
494        } else {
495            // make sure we get a consistently zeroed checksum,
496            // since implementations might rely on it
497            packet.set_checksum(0);
498        }
499    }
500}
501
502impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
503    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
504        match Repr::parse(self, &ChecksumCapabilities::default()) {
505            Ok(repr) => write!(f, "{}", repr),
506            Err(err) => {
507                write!(f, "ICMPv4 ({})", err)?;
508                write!(f, " type={:?}", self.msg_type())?;
509                match self.msg_type() {
510                    Message::DstUnreachable => {
511                        write!(f, " code={:?}", DstUnreachable::from(self.msg_code()))
512                    }
513                    _ => write!(f, " code={}", self.msg_code()),
514                }
515            }
516        }
517    }
518}
519
520impl<'a> fmt::Display for Repr<'a> {
521    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
522        match *self {
523            Repr::EchoRequest {
524                ident,
525                seq_no,
526                data,
527            } => write!(
528                f,
529                "ICMPv4 echo request id={} seq={} len={}",
530                ident,
531                seq_no,
532                data.len()
533            ),
534            Repr::EchoReply {
535                ident,
536                seq_no,
537                data,
538            } => write!(
539                f,
540                "ICMPv4 echo reply id={} seq={} len={}",
541                ident,
542                seq_no,
543                data.len()
544            ),
545            Repr::DstUnreachable { reason, .. } => {
546                write!(f, "ICMPv4 destination unreachable ({})", reason)
547            }
548        }
549    }
550}
551
552use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
553
554impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
555    fn pretty_print(
556        buffer: &dyn AsRef<[u8]>,
557        f: &mut fmt::Formatter,
558        indent: &mut PrettyIndent,
559    ) -> fmt::Result {
560        let packet = match Packet::new_checked(buffer) {
561            Err(err) => return write!(f, "{}({})", indent, err),
562            Ok(packet) => packet,
563        };
564        write!(f, "{}{}", indent, packet)?;
565
566        match packet.msg_type() {
567            Message::DstUnreachable => {
568                indent.increase(f)?;
569                super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent)
570            }
571            _ => Ok(()),
572        }
573    }
574}
575
576#[cfg(test)]
577mod test {
578    use super::*;
579
580    static ECHO_PACKET_BYTES: [u8; 12] = [
581        0x08, 0x00, 0x8e, 0xfe, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
582    ];
583
584    static ECHO_DATA_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
585
586    #[test]
587    fn test_echo_deconstruct() {
588        let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
589        assert_eq!(packet.msg_type(), Message::EchoRequest);
590        assert_eq!(packet.msg_code(), 0);
591        assert_eq!(packet.checksum(), 0x8efe);
592        assert_eq!(packet.echo_ident(), 0x1234);
593        assert_eq!(packet.echo_seq_no(), 0xabcd);
594        assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]);
595        assert!(packet.verify_checksum());
596    }
597
598    #[test]
599    fn test_echo_construct() {
600        let mut bytes = vec![0xa5; 12];
601        let mut packet = Packet::new_unchecked(&mut bytes);
602        packet.set_msg_type(Message::EchoRequest);
603        packet.set_msg_code(0);
604        packet.set_echo_ident(0x1234);
605        packet.set_echo_seq_no(0xabcd);
606        packet.data_mut().copy_from_slice(&ECHO_DATA_BYTES[..]);
607        packet.fill_checksum();
608        assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
609    }
610
611    fn echo_packet_repr() -> Repr<'static> {
612        Repr::EchoRequest {
613            ident: 0x1234,
614            seq_no: 0xabcd,
615            data: &ECHO_DATA_BYTES,
616        }
617    }
618
619    #[test]
620    fn test_echo_parse() {
621        let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
622        let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap();
623        assert_eq!(repr, echo_packet_repr());
624    }
625
626    #[test]
627    fn test_echo_emit() {
628        let repr = echo_packet_repr();
629        let mut bytes = vec![0xa5; repr.buffer_len()];
630        let mut packet = Packet::new_unchecked(&mut bytes);
631        repr.emit(&mut packet, &ChecksumCapabilities::default());
632        assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
633    }
634
635    #[test]
636    fn test_check_len() {
637        let bytes = [0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
638        assert_eq!(Packet::new_checked(&[]), Err(Error::Truncated));
639        assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error::Truncated));
640        assert!(Packet::new_checked(&bytes[..]).is_ok());
641    }
642}