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