Skip to main content

wireforge_core/
icmp.rs

1//! ICMP (v4) and ICMPv6 packet parsers and builders, including NDP messages.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use core::net::Ipv6Addr;
6
7use crate::types::IpProtocol;
8use crate::util::{internet_checksum, pseudo_header_checksum, read_u16be, read_u32be, write_u16be};
9
10// ---------------------------------------------------------------------------
11// ICMPv4 constants
12// ---------------------------------------------------------------------------
13
14pub const ICMP_HEADER_LEN: usize = 8;
15
16pub const ICMP_TYPE_ECHO_REPLY: u8 = 0;
17pub const ICMP_TYPE_DEST_UNREACH: u8 = 3;
18pub const ICMP_TYPE_ECHO_REQUEST: u8 = 8;
19pub const ICMP_TYPE_TIME_EXCEEDED: u8 = 11;
20
21// ---------------------------------------------------------------------------
22// ICMPv6 constants
23// ---------------------------------------------------------------------------
24
25pub const ICMPV6_HEADER_LEN: usize = 8;
26
27pub const ICMPV6_TYPE_ECHO_REQUEST: u8 = 128;
28pub const ICMPV6_TYPE_ECHO_REPLY: u8 = 129;
29pub const ICMPV6_TYPE_ROUTER_SOLICIT: u8 = 133;
30pub const ICMPV6_TYPE_ROUTER_ADVERT: u8 = 134;
31pub const ICMPV6_TYPE_NEIGHBOR_SOLICIT: u8 = 135;
32pub const ICMPV6_TYPE_NEIGHBOR_ADVERT: u8 = 136;
33
34// ---------------------------------------------------------------------------
35// ICMPv4 message types
36// ---------------------------------------------------------------------------
37
38/// Parsed ICMPv4 message variant.
39#[derive(Debug, Clone)]
40pub enum IcmpMessage<'a> {
41    EchoReply {
42        identifier: u16,
43        sequence_number: u16,
44        data: &'a [u8],
45    },
46    EchoRequest {
47        identifier: u16,
48        sequence_number: u16,
49        data: &'a [u8],
50    },
51    DestUnreachable {
52        code: u8,
53        data: &'a [u8],
54    },
55    TimeExceeded {
56        code: u8,
57        data: &'a [u8],
58    },
59    Unknown {
60        type_: u8,
61        code: u8,
62        data: &'a [u8],
63    },
64}
65
66/// Zero-copy ICMPv4 packet parser.
67#[derive(Debug, Clone)]
68pub struct IcmpPacket<'a> {
69    buf: &'a [u8],
70}
71
72impl<'a> IcmpPacket<'a> {
73    pub fn new(buf: &'a [u8]) -> Option<Self> {
74        if buf.len() < ICMP_HEADER_LEN {
75            return None;
76        }
77        Some(Self { buf })
78    }
79
80    #[inline]
81    pub fn type_(&self) -> u8 {
82        self.buf[0]
83    }
84
85    #[inline]
86    pub fn code(&self) -> u8 {
87        self.buf[1]
88    }
89
90    #[inline]
91    pub fn checksum(&self) -> u16 {
92        read_u16be(&self.buf[2..4])
93    }
94
95    /// The rest-of-header field (bytes 4..8), interpreted per message type.
96    #[inline]
97    pub fn rest_of_header(&self) -> u32 {
98        read_u32be(&self.buf[4..8])
99    }
100
101    /// Payload following the 8-byte ICMP header.
102    #[inline]
103    pub fn payload(&self) -> &'a [u8] {
104        &self.buf[ICMP_HEADER_LEN..]
105    }
106
107    /// Verify the ICMP checksum over the entire message.
108    pub fn verify_checksum(&self) -> bool {
109        internet_checksum(self.buf) == 0
110    }
111
112    /// Parse the ICMP message into a typed variant.
113    pub fn message(&self) -> IcmpMessage<'a> {
114        let payload = self.payload();
115        let roh = self.rest_of_header();
116        match self.type_() {
117            ICMP_TYPE_ECHO_REPLY => IcmpMessage::EchoReply {
118                identifier: (roh >> 16) as u16,
119                sequence_number: roh as u16,
120                data: payload,
121            },
122            ICMP_TYPE_ECHO_REQUEST => IcmpMessage::EchoRequest {
123                identifier: (roh >> 16) as u16,
124                sequence_number: roh as u16,
125                data: payload,
126            },
127            ICMP_TYPE_DEST_UNREACH => IcmpMessage::DestUnreachable {
128                code: self.code(),
129                data: payload,
130            },
131            ICMP_TYPE_TIME_EXCEEDED => IcmpMessage::TimeExceeded {
132                code: self.code(),
133                data: payload,
134            },
135            _ => IcmpMessage::Unknown {
136                type_: self.type_(),
137                code: self.code(),
138                data: payload,
139            },
140        }
141    }
142}
143
144// ---------------------------------------------------------------------------
145// ICMPv4 builder
146// ---------------------------------------------------------------------------
147
148pub struct IcmpPacketBuilder {
149    buf: Vec<u8>,
150    payload: Option<Vec<u8>>,
151}
152
153impl Default for IcmpPacketBuilder {
154    fn default() -> Self {
155        Self::new(ICMP_TYPE_ECHO_REQUEST, 0)
156    }
157}
158
159impl IcmpPacketBuilder {
160    pub fn new(type_: u8, code: u8) -> Self {
161        let mut buf = vec![0u8; ICMP_HEADER_LEN];
162        buf[0] = type_;
163        buf[1] = code;
164        Self { buf, payload: None }
165    }
166
167    /// Create a builder for an Echo Request (type 8).
168    pub fn echo_request() -> Self {
169        Self::new(ICMP_TYPE_ECHO_REQUEST, 0)
170    }
171
172    /// Create a builder for an Echo Reply (type 0).
173    pub fn echo_reply() -> Self {
174        Self::new(ICMP_TYPE_ECHO_REPLY, 0)
175    }
176
177    pub fn code(mut self, code: u8) -> Self {
178        self.buf[1] = code;
179        self
180    }
181
182    pub fn identifier(mut self, id: u16) -> Self {
183        write_u16be(&mut self.buf[4..6], id);
184        self
185    }
186
187    pub fn sequence_number(mut self, seq: u16) -> Self {
188        write_u16be(&mut self.buf[6..8], seq);
189        self
190    }
191
192    pub fn payload(mut self, data: &[u8]) -> Self {
193        self.payload = Some(data.to_vec());
194        self
195    }
196
197    pub fn build(mut self) -> Vec<u8> {
198        // Zero checksum, compute over entire message, write it back.
199        self.buf[2] = 0;
200        self.buf[3] = 0;
201
202        let mut packet = self.buf;
203        if let Some(ref p) = self.payload {
204            packet.extend_from_slice(p);
205        }
206        let csum = internet_checksum(&packet);
207        write_u16be(&mut packet[2..4], csum);
208        packet
209    }
210}
211
212// ---------------------------------------------------------------------------
213// ICMPv6 message types
214// ---------------------------------------------------------------------------
215
216/// Parsed ICMPv6 message variant, including NDP messages.
217#[derive(Debug, Clone)]
218pub enum Icmpv6Message<'a> {
219    EchoRequest {
220        identifier: u16,
221        sequence_number: u16,
222        data: &'a [u8],
223    },
224    EchoReply {
225        identifier: u16,
226        sequence_number: u16,
227        data: &'a [u8],
228    },
229    NeighborSolicitation {
230        target_address: Ipv6Addr,
231        options: &'a [u8],
232    },
233    NeighborAdvertisement {
234        flags: u8,
235        target_address: Ipv6Addr,
236        options: &'a [u8],
237    },
238    RouterSolicitation {
239        options: &'a [u8],
240    },
241    RouterAdvertisement {
242        hop_limit: u8,
243        flags: u8,
244        router_lifetime: u16,
245        reachable_time: u32,
246        retrans_timer: u32,
247        options: &'a [u8],
248    },
249    Unknown {
250        type_: u8,
251        code: u8,
252        data: &'a [u8],
253    },
254}
255
256/// Zero-copy ICMPv6 packet parser.
257#[derive(Debug, Clone)]
258pub struct Icmpv6Packet<'a> {
259    buf: &'a [u8],
260}
261
262impl<'a> Icmpv6Packet<'a> {
263    pub fn new(buf: &'a [u8]) -> Option<Self> {
264        if buf.len() < ICMPV6_HEADER_LEN {
265            return None;
266        }
267        Some(Self { buf })
268    }
269
270    #[inline]
271    pub fn type_(&self) -> u8 {
272        self.buf[0]
273    }
274
275    #[inline]
276    pub fn code(&self) -> u8 {
277        self.buf[1]
278    }
279
280    #[inline]
281    pub fn checksum(&self) -> u16 {
282        read_u16be(&self.buf[2..4])
283    }
284
285    /// The body after the 8-byte ICMPv6 header.
286    #[inline]
287    pub fn body(&self) -> &'a [u8] {
288        &self.buf[ICMPV6_HEADER_LEN..]
289    }
290
291    /// Verify the ICMPv6 checksum (includes IPv6 pseudo-header).
292    pub fn verify_checksum(&self, src: Ipv6Addr, dst: Ipv6Addr) -> bool {
293        let csum = pseudo_header_checksum(
294            &src.octets(),
295            &dst.octets(),
296            u8::from(IpProtocol::Icmpv6),
297            self.buf,
298        );
299        csum == 0
300    }
301
302    /// Parse the ICMPv6 message into a typed variant.
303    pub fn message(&self) -> Icmpv6Message<'a> {
304        let buf = self.buf;
305        match self.type_() {
306            ICMPV6_TYPE_ECHO_REQUEST => {
307                let id = if buf.len() >= 6 { read_u16be(&buf[4..6]) } else { 0 };
308                let seq = if buf.len() >= 8 { read_u16be(&buf[6..8]) } else { 0 };
309                let data = if buf.len() > 8 { &buf[8..] } else { &[] };
310                Icmpv6Message::EchoRequest { identifier: id, sequence_number: seq, data }
311            }
312            ICMPV6_TYPE_ECHO_REPLY => {
313                let id = if buf.len() >= 6 { read_u16be(&buf[4..6]) } else { 0 };
314                let seq = if buf.len() >= 8 { read_u16be(&buf[6..8]) } else { 0 };
315                let data = if buf.len() > 8 { &buf[8..] } else { &[] };
316                Icmpv6Message::EchoReply { identifier: id, sequence_number: seq, data }
317            }
318            ICMPV6_TYPE_NEIGHBOR_SOLICIT => {
319                let target = if buf.len() >= 24 {
320                    let b: [u8; 16] = buf[8..24].try_into().unwrap();
321                    Ipv6Addr::from(b)
322                } else {
323                    Ipv6Addr::UNSPECIFIED
324                };
325                let options = if buf.len() > 24 { &buf[24..] } else { &[] };
326                Icmpv6Message::NeighborSolicitation { target_address: target, options }
327            }
328            ICMPV6_TYPE_NEIGHBOR_ADVERT => {
329                let flags = if buf.len() >= 8 { buf[4] } else { 0 };
330                let target = if buf.len() >= 24 {
331                    let b: [u8; 16] = buf[8..24].try_into().unwrap();
332                    Ipv6Addr::from(b)
333                } else {
334                    Ipv6Addr::UNSPECIFIED
335                };
336                let options = if buf.len() > 24 { &buf[24..] } else { &[] };
337                Icmpv6Message::NeighborAdvertisement { flags, target_address: target, options }
338            }
339            ICMPV6_TYPE_ROUTER_SOLICIT => {
340                let options = if buf.len() > 8 { &buf[8..] } else { &[] };
341                Icmpv6Message::RouterSolicitation { options }
342            }
343            ICMPV6_TYPE_ROUTER_ADVERT => {
344                let hop_limit = if buf.len() >= 8 { buf[4] } else { 0 };
345                let flags = if buf.len() >= 8 { buf[5] } else { 0 };
346                let router_lifetime = if buf.len() >= 8 { read_u16be(&buf[6..8]) } else { 0 };
347                let reachable_time = if buf.len() >= 12 { read_u32be(&buf[8..12]) } else { 0 };
348                let retrans_timer = if buf.len() >= 16 { read_u32be(&buf[12..16]) } else { 0 };
349                let options = if buf.len() > 16 { &buf[16..] } else { &[] };
350                Icmpv6Message::RouterAdvertisement {
351                    hop_limit, flags, router_lifetime, reachable_time, retrans_timer, options,
352                }
353            }
354            _ => Icmpv6Message::Unknown {
355                type_: self.type_(),
356                code: self.code(),
357                data: if buf.len() > 8 { &buf[8..] } else { &[] },
358            },
359        }
360    }
361}
362
363// ---------------------------------------------------------------------------
364// ICMPv6 builder
365// ---------------------------------------------------------------------------
366
367pub struct Icmpv6PacketBuilder {
368    buf: Vec<u8>,
369    body: Vec<u8>,
370}
371
372impl Default for Icmpv6PacketBuilder {
373    fn default() -> Self {
374        Self::new(ICMPV6_TYPE_ECHO_REQUEST, 0)
375    }
376}
377
378impl Icmpv6PacketBuilder {
379    pub fn new(type_: u8, code: u8) -> Self {
380        let mut buf = vec![0u8; ICMPV6_HEADER_LEN];
381        buf[0] = type_;
382        buf[1] = code;
383        Self { buf, body: Vec::new() }
384    }
385
386    pub fn echo_request() -> Self {
387        Self::new(ICMPV6_TYPE_ECHO_REQUEST, 0)
388    }
389
390    pub fn echo_reply() -> Self {
391        Self::new(ICMPV6_TYPE_ECHO_REPLY, 0)
392    }
393
394    pub fn code(mut self, code: u8) -> Self {
395        self.buf[1] = code;
396        self
397    }
398
399    /// Set the identifier field (bytes 4-5 of ICMPv6 header; used by Echo).
400    pub fn identifier(mut self, id: u16) -> Self {
401        write_u16be(&mut self.buf[4..6], id);
402        self
403    }
404
405    /// Set the sequence number field (bytes 6-7 of ICMPv6 header; used by Echo).
406    pub fn sequence_number(mut self, seq: u16) -> Self {
407        write_u16be(&mut self.buf[6..8], seq);
408        self
409    }
410
411    /// Set raw body bytes (for NDP messages and arbitrary data).
412    pub fn body(mut self, data: &[u8]) -> Self {
413        self.body = data.to_vec();
414        self
415    }
416
417    /// Build the ICMPv6 packet, computing the checksum with the IPv6 pseudo-header.
418    pub fn build(mut self, src: Ipv6Addr, dst: Ipv6Addr) -> Vec<u8> {
419        self.buf[2] = 0;
420        self.buf[3] = 0;
421
422        let mut packet = self.buf;
423        packet.extend_from_slice(&self.body);
424        let csum = pseudo_header_checksum(
425            &src.octets(),
426            &dst.octets(),
427            u8::from(IpProtocol::Icmpv6),
428            &packet,
429        );
430        write_u16be(&mut packet[2..4], csum);
431        packet
432    }
433}
434
435// ---------------------------------------------------------------------------
436// Tests
437// ---------------------------------------------------------------------------
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use core::net::Ipv6Addr;
443
444    // ---------- ICMPv4 tests ----------
445
446    #[test]
447    fn parse_icmp_echo_request() {
448        // ICMP Echo Request: type=8, code=0, id=0x0001, seq=0x0002
449        let data: &[u8] = &[
450            0x08, 0x00, 0x00, 0x00, // type, code, checksum (zero for sample)
451            0x00, 0x01, 0x00, 0x02, // id=1, seq=2
452            0x41, 0x42, 0x43, 0x44, // payload "ABCD"
453        ];
454        let pkt = IcmpPacket::new(data).unwrap();
455        assert_eq!(pkt.type_(), 8);
456        assert_eq!(pkt.code(), 0);
457        assert_eq!(pkt.payload(), &[0x41, 0x42, 0x43, 0x44]);
458
459        match pkt.message() {
460            IcmpMessage::EchoRequest { identifier, sequence_number, data } => {
461                assert_eq!(identifier, 1);
462                assert_eq!(sequence_number, 2);
463                assert_eq!(data, &[0x41, 0x42, 0x43, 0x44]);
464            }
465            _ => panic!("expected EchoRequest"),
466        }
467    }
468
469    #[test]
470    fn parse_icmp_echo_reply() {
471        let data: &[u8] = &[
472            0x00, 0x00, 0x00, 0x00, // type=0, code=0
473            0x00, 0x03, 0x00, 0x04, // id=3, seq=4
474        ];
475        let pkt = IcmpPacket::new(data).unwrap();
476        match pkt.message() {
477            IcmpMessage::EchoReply { identifier, sequence_number, .. } => {
478                assert_eq!(identifier, 3);
479                assert_eq!(sequence_number, 4);
480            }
481            _ => panic!("expected EchoReply"),
482        }
483    }
484
485    #[test]
486    fn parse_icmp_dest_unreachable() {
487        let data: &[u8] = &[
488            0x03, 0x01, 0x00, 0x00, // type=3, code=1 (host unreachable)
489            0x00, 0x00, 0x00, 0x00, // rest of header
490            0x11, 0x22, // payload
491        ];
492        let pkt = IcmpPacket::new(data).unwrap();
493        match pkt.message() {
494            IcmpMessage::DestUnreachable { code, data: d } => {
495                assert_eq!(code, 1);
496                assert_eq!(d, &[0x11, 0x22]);
497            }
498            _ => panic!("expected DestUnreachable"),
499        }
500    }
501
502    #[test]
503    fn parse_icmp_time_exceeded() {
504        let data: &[u8] = &[
505            0x0B, 0x00, 0x00, 0x00, // type=11, code=0 (TTL exceeded in transit)
506            0x00, 0x00, 0x00, 0x00,
507        ];
508        let pkt = IcmpPacket::new(data).unwrap();
509        match pkt.message() {
510            IcmpMessage::TimeExceeded { code, .. } => {
511                assert_eq!(code, 0);
512            }
513            _ => panic!("expected TimeExceeded"),
514        }
515    }
516
517    #[test]
518    fn parse_icmp_too_short() {
519        assert!(IcmpPacket::new(&[]).is_none());
520        assert!(IcmpPacket::new(&[0u8; 7]).is_none());
521        assert!(IcmpPacket::new(&[0u8; 8]).is_some());
522    }
523
524    #[test]
525    fn icmp_echo_build_and_verify_checksum() {
526        let pkt_bytes = IcmpPacketBuilder::echo_request()
527            .identifier(0x1234)
528            .sequence_number(1)
529            .payload(&[0xAA, 0xBB, 0xCC, 0xDD])
530            .build();
531
532        let pkt = IcmpPacket::new(&pkt_bytes).unwrap();
533        assert!(pkt.verify_checksum());
534        match pkt.message() {
535            IcmpMessage::EchoRequest { identifier, sequence_number, .. } => {
536                assert_eq!(identifier, 0x1234);
537                assert_eq!(sequence_number, 1);
538            }
539            _ => panic!("expected EchoRequest"),
540        }
541    }
542
543    // ---------- ICMPv6 tests ----------
544
545    #[test]
546    fn parse_icmpv6_echo_request() {
547        let data: &[u8] = &[
548            0x80, 0x00, 0x00, 0x00, // type=128, code=0
549            0x00, 0x05, 0x00, 0x06, // id=5, seq=6
550            0xDE, 0xAD, // data
551        ];
552        let pkt = Icmpv6Packet::new(data).unwrap();
553        assert_eq!(pkt.type_(), 128);
554        match pkt.message() {
555            Icmpv6Message::EchoRequest { identifier, sequence_number, data } => {
556                assert_eq!(identifier, 5);
557                assert_eq!(sequence_number, 6);
558                assert_eq!(data, &[0xDE, 0xAD]);
559            }
560            _ => panic!("expected EchoRequest"),
561        }
562    }
563
564    #[test]
565    fn parse_icmpv6_echo_reply() {
566        let data: &[u8] = &[
567            0x81, 0x00, 0x00, 0x00,
568            0x00, 0x07, 0x00, 0x08,
569        ];
570        let pkt = Icmpv6Packet::new(data).unwrap();
571        match pkt.message() {
572            Icmpv6Message::EchoReply { identifier, sequence_number, .. } => {
573                assert_eq!(identifier, 7);
574                assert_eq!(sequence_number, 8);
575            }
576            _ => panic!("expected EchoReply"),
577        }
578    }
579
580    #[test]
581    fn parse_icmpv6_neighbor_solicitation() {
582        let target = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
583        let mut data = vec![0x87u8, 0x00, 0x00, 0x00]; // type=135, code=0
584        data.extend_from_slice(&[0u8; 4]); // reserved
585        data.extend_from_slice(&target.octets()); // target address
586        data.extend_from_slice(&[0x01, 0x01, 0x02, 0x03]); // source link-layer option
587
588        let pkt = Icmpv6Packet::new(&data).unwrap();
589        match pkt.message() {
590            Icmpv6Message::NeighborSolicitation { target_address, options } => {
591                assert_eq!(target_address, target);
592                assert_eq!(options.len(), 4);
593            }
594            _ => panic!("expected NeighborSolicitation"),
595        }
596    }
597
598    #[test]
599    fn parse_icmpv6_neighbor_advertisement() {
600        let target = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
601        let mut data = vec![0x88u8, 0x00, 0x00, 0x00]; // type=136, code=0
602        data.push(0x60); // flags: S=1, O=1
603        data.extend_from_slice(&[0u8; 3]); // reserved
604        data.extend_from_slice(&target.octets()); // target address
605
606        let pkt = Icmpv6Packet::new(&data).unwrap();
607        match pkt.message() {
608            Icmpv6Message::NeighborAdvertisement { flags, target_address, .. } => {
609                assert_eq!(flags, 0x60);
610                assert_eq!(target_address, target);
611            }
612            _ => panic!("expected NeighborAdvertisement"),
613        }
614    }
615
616    #[test]
617    fn parse_icmpv6_router_solicitation() {
618        let data: &[u8] = &[
619            0x85, 0x00, 0x00, 0x00, // type=133, code=0
620            0x00, 0x00, 0x00, 0x00, // reserved
621            0x01, 0x01, // option (source link-layer address)
622        ];
623        let pkt = Icmpv6Packet::new(data).unwrap();
624        match pkt.message() {
625            Icmpv6Message::RouterSolicitation { options } => {
626                assert_eq!(options.len(), 2);
627            }
628            _ => panic!("expected RouterSolicitation"),
629        }
630    }
631
632    #[test]
633    fn parse_icmpv6_router_advertisement() {
634        let mut data = vec![0x86u8, 0x00, 0x00, 0x00]; // type=134, code=0
635        data.push(64); // hop_limit
636        data.push(0x08); // flags: prf=medium
637        data.extend_from_slice(&0x0E10u16.to_be_bytes()); // router_lifetime = 3600
638        data.extend_from_slice(&0x0000EA60u32.to_be_bytes()); // reachable_time = 60000
639        data.extend_from_slice(&0x00002710u32.to_be_bytes()); // retrans_timer = 10000
640        data.extend_from_slice(&[0x01, 0x01]); // options
641
642        let pkt = Icmpv6Packet::new(&data).unwrap();
643        match pkt.message() {
644            Icmpv6Message::RouterAdvertisement {
645                hop_limit, flags, router_lifetime,
646                reachable_time, retrans_timer, options,
647            } => {
648                assert_eq!(hop_limit, 64);
649                assert_eq!(flags, 0x08);
650                assert_eq!(router_lifetime, 3600);
651                assert_eq!(reachable_time, 60000);
652                assert_eq!(retrans_timer, 10000);
653                assert_eq!(options.len(), 2);
654            }
655            _ => panic!("expected RouterAdvertisement"),
656        }
657    }
658
659    #[test]
660    fn parse_icmpv6_too_short() {
661        assert!(Icmpv6Packet::new(&[]).is_none());
662        assert!(Icmpv6Packet::new(&[0u8; 7]).is_none());
663        assert!(Icmpv6Packet::new(&[0u8; 8]).is_some());
664    }
665
666    #[test]
667    fn icmpv6_echo_build_and_verify_checksum() {
668        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
669        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
670        let pkt_bytes = Icmpv6PacketBuilder::echo_request()
671            .identifier(0x42)
672            .sequence_number(1)
673            .build(src, dst);
674
675        let pkt = Icmpv6Packet::new(&pkt_bytes).unwrap();
676        assert!(pkt.verify_checksum(src, dst));
677        match pkt.message() {
678            Icmpv6Message::EchoRequest { identifier, sequence_number, .. } => {
679                assert_eq!(identifier, 0x42);
680                assert_eq!(sequence_number, 1);
681            }
682            _ => panic!("expected EchoRequest"),
683        }
684    }
685}