packet_formats/
testutil.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//! Testing-related utilities.
6
7// TODO(https://fxbug.dev/326330182): this import seems actually necessary. Is this a bug on the
8// lint?
9#[allow(unused_imports)]
10use alloc::vec::Vec;
11use core::num::NonZeroU16;
12use core::ops::Range;
13use zerocopy::SplitByteSlice;
14
15use log::debug;
16use net_types::ethernet::Mac;
17use net_types::ip::{Ipv4Addr, Ipv6Addr};
18use packet::{ParsablePacket, ParseBuffer, SliceBufViewMut};
19
20use crate::arp::{ArpOp, ArpPacket};
21use crate::error::{IpParseResult, ParseError, ParseResult};
22use crate::ethernet::{EtherType, EthernetFrame, EthernetFrameLengthCheck};
23use crate::icmp::{IcmpIpExt, IcmpMessage, IcmpPacket, IcmpParseArgs, Icmpv6PacketRaw};
24use crate::ip::{DscpAndEcn, IpExt, Ipv4Proto};
25use crate::ipv4::{Ipv4FragmentType, Ipv4Header, Ipv4Packet};
26use crate::ipv6::{Ipv6Header, Ipv6Packet};
27use crate::tcp::options::TcpOption;
28use crate::tcp::TcpSegment;
29use crate::udp::UdpPacket;
30
31#[cfg(test)]
32pub(crate) use crateonly::*;
33
34/// Metadata of an Ethernet frame.
35#[allow(missing_docs)]
36pub struct EthernetFrameMetadata {
37    pub src_mac: Mac,
38    pub dst_mac: Mac,
39    pub ethertype: Option<EtherType>,
40}
41
42/// Metadata of an IPv4 packet.
43#[allow(missing_docs)]
44pub struct Ipv4PacketMetadata {
45    pub id: u16,
46    pub dscp_and_ecn: DscpAndEcn,
47    pub dont_fragment: bool,
48    pub more_fragments: bool,
49    pub fragment_offset: u16,
50    pub fragment_type: Ipv4FragmentType,
51    pub ttl: u8,
52    pub proto: Ipv4Proto,
53    pub src_ip: Ipv4Addr,
54    pub dst_ip: Ipv4Addr,
55}
56
57/// Metadata of an IPv6 packet.
58#[allow(missing_docs)]
59pub struct Ipv6PacketMetadata {
60    pub dscp_and_ecn: DscpAndEcn,
61    pub flowlabel: u32,
62    pub hop_limit: u8,
63    pub src_ip: Ipv6Addr,
64    pub dst_ip: Ipv6Addr,
65}
66
67/// Metadata of a TCP segment.
68#[allow(missing_docs)]
69pub struct TcpSegmentMetadata {
70    pub src_port: u16,
71    pub dst_port: u16,
72    pub seq_num: u32,
73    pub ack_num: Option<u32>,
74    pub flags: u16,
75    pub psh: bool,
76    pub rst: bool,
77    pub syn: bool,
78    pub fin: bool,
79    pub window_size: u16,
80    pub options: &'static [TcpOption<'static>],
81}
82
83/// Metadata of a UDP packet.
84#[allow(missing_docs)]
85pub struct UdpPacketMetadata {
86    pub src_port: u16,
87    pub dst_port: u16,
88}
89
90/// Represents a packet (usually from a live capture) used for testing.
91///
92/// Includes the raw bytes, metadata of the packet (currently just fields from the packet header)
93/// and the range which indicates where the body is.
94#[allow(missing_docs)]
95pub struct TestPacket<M> {
96    pub bytes: &'static [u8],
97    pub metadata: M,
98    pub body_range: Range<usize>,
99}
100
101/// Verify that a parsed Ethernet frame is as expected.
102///
103/// Ensures the parsed packet's header fields and body are equal to those in the test packet.
104pub fn verify_ethernet_frame(
105    frame: &EthernetFrame<&[u8]>,
106    expected: TestPacket<EthernetFrameMetadata>,
107) {
108    assert_eq!(frame.src_mac(), expected.metadata.src_mac);
109    assert_eq!(frame.dst_mac(), expected.metadata.dst_mac);
110    assert_eq!(frame.ethertype(), expected.metadata.ethertype);
111    assert_eq!(frame.body(), &expected.bytes[expected.body_range]);
112}
113
114/// Verify that a parsed IPv4 packet is as expected.
115///
116/// Ensures the parsed packet's header fields and body are equal to those in the test packet.
117pub fn verify_ipv4_packet(packet: &Ipv4Packet<&[u8]>, expected: TestPacket<Ipv4PacketMetadata>) {
118    assert_eq!(packet.dscp_and_ecn(), expected.metadata.dscp_and_ecn);
119    assert_eq!(packet.id(), expected.metadata.id);
120    assert_eq!(packet.df_flag(), expected.metadata.dont_fragment);
121    assert_eq!(packet.mf_flag(), expected.metadata.more_fragments);
122    assert_eq!(packet.fragment_offset().into_raw(), expected.metadata.fragment_offset);
123    assert_eq!(packet.ttl(), expected.metadata.ttl);
124    assert_eq!(packet.proto(), expected.metadata.proto);
125    assert_eq!(packet.src_ip(), expected.metadata.src_ip);
126    assert_eq!(packet.dst_ip(), expected.metadata.dst_ip);
127    assert_eq!(packet.body(), &expected.bytes[expected.body_range]);
128}
129
130/// Verify that a parsed IPv6 packet is as expected.
131///
132/// Ensures the parsed packet's header fields and body are equal to those in the test packet.
133pub fn verify_ipv6_packet(packet: &Ipv6Packet<&[u8]>, expected: TestPacket<Ipv6PacketMetadata>) {
134    assert_eq!(packet.dscp_and_ecn(), expected.metadata.dscp_and_ecn);
135    assert_eq!(packet.flowlabel(), expected.metadata.flowlabel);
136    assert_eq!(packet.hop_limit(), expected.metadata.hop_limit);
137    assert_eq!(packet.src_ip(), expected.metadata.src_ip);
138    assert_eq!(packet.dst_ip(), expected.metadata.dst_ip);
139    assert_eq!(packet.body(), &expected.bytes[expected.body_range]);
140}
141
142/// Verify that a parsed UDP packet is as expected.
143///
144/// Ensures the parsed packet's header fields and body are equal to those in the test packet.
145pub fn verify_udp_packet(packet: &UdpPacket<&[u8]>, expected: TestPacket<UdpPacketMetadata>) {
146    assert_eq!(packet.src_port().map(NonZeroU16::get).unwrap_or(0), expected.metadata.src_port);
147    assert_eq!(packet.dst_port().get(), expected.metadata.dst_port);
148    assert_eq!(packet.body(), &expected.bytes[expected.body_range]);
149}
150
151/// Verify that a parsed TCP segment is as expected.
152///
153/// Ensures the parsed packet's header fields and body are equal to those in the test packet.
154pub fn verify_tcp_segment(segment: &TcpSegment<&[u8]>, expected: TestPacket<TcpSegmentMetadata>) {
155    assert_eq!(segment.src_port().get(), expected.metadata.src_port);
156    assert_eq!(segment.dst_port().get(), expected.metadata.dst_port);
157    assert_eq!(segment.seq_num(), expected.metadata.seq_num);
158    assert_eq!(segment.ack_num(), expected.metadata.ack_num);
159    assert_eq!(segment.rst(), expected.metadata.rst);
160    assert_eq!(segment.syn(), expected.metadata.syn);
161    assert_eq!(segment.fin(), expected.metadata.fin);
162    assert_eq!(segment.window_size(), expected.metadata.window_size);
163    assert_eq!(segment.iter_options().collect::<Vec<_>>().as_slice(), expected.metadata.options);
164    assert_eq!(segment.body(), &expected.bytes[expected.body_range]);
165}
166
167/// Parse an ethernet frame.
168///
169/// `parse_ethernet_frame` parses an ethernet frame, returning the body along
170/// with some important header fields.
171pub fn parse_ethernet_frame(
172    mut buf: &[u8],
173    ethernet_length_check: EthernetFrameLengthCheck,
174) -> ParseResult<(&[u8], Mac, Mac, Option<EtherType>)> {
175    let frame = (&mut buf).parse_with::<_, EthernetFrame<_>>(ethernet_length_check)?;
176    let src_mac = frame.src_mac();
177    let dst_mac = frame.dst_mac();
178    let ethertype = frame.ethertype();
179    Ok((buf, src_mac, dst_mac, ethertype))
180}
181
182/// Information about an [`ArpPacket`].
183#[allow(missing_docs)]
184pub struct ArpPacketInfo {
185    pub sender_hardware_address: Mac,
186    pub sender_protocol_address: Ipv4Addr,
187    pub target_hardware_address: Mac,
188    pub target_protocol_address: Ipv4Addr,
189    pub operation: ArpOp,
190}
191
192impl<B: SplitByteSlice> From<ArpPacket<B, Mac, Ipv4Addr>> for ArpPacketInfo {
193    fn from(packet: ArpPacket<B, Mac, Ipv4Addr>) -> ArpPacketInfo {
194        ArpPacketInfo {
195            sender_hardware_address: packet.sender_hardware_address(),
196            sender_protocol_address: packet.sender_protocol_address(),
197            target_hardware_address: packet.target_hardware_address(),
198            target_protocol_address: packet.target_protocol_address(),
199            operation: packet.operation(),
200        }
201    }
202}
203
204/// Parse an ARP packet.
205pub fn parse_arp_packet(mut buf: &[u8]) -> ParseResult<ArpPacketInfo> {
206    (&mut buf).parse::<ArpPacket<_, Mac, Ipv4Addr>>().map(ArpPacketInfo::from)
207}
208
209/// Parse an ARP packet in an Ethernet frame.
210pub fn parse_arp_packet_in_ethernet_frame(
211    buf: &[u8],
212    ethernet_length_check: EthernetFrameLengthCheck,
213) -> ParseResult<ArpPacketInfo> {
214    let (body, _src_mac, _dst_mac, ethertype) = parse_ethernet_frame(buf, ethernet_length_check)?;
215    if ethertype != Some(EtherType::Arp) {
216        debug!("unexpected ethertype: {:?}", ethertype);
217        return Err(ParseError::NotExpected.into());
218    }
219
220    parse_arp_packet(body)
221}
222
223/// Parse an IP packet.
224///
225/// `parse_ip_packet` parses an IP packet, returning the body along with some
226/// important header fields.
227#[allow(clippy::type_complexity)]
228pub fn parse_ip_packet<I: IpExt>(
229    mut buf: &[u8],
230) -> IpParseResult<I, (&[u8], I::Addr, I::Addr, I::Proto, u8)> {
231    use crate::ip::IpPacket;
232
233    let packet = (&mut buf).parse::<I::Packet<_>>()?;
234    let src_ip = packet.src_ip();
235    let dst_ip = packet.dst_ip();
236    let proto = packet.proto();
237    let ttl = packet.ttl();
238    // Because the packet type here is generic, Rust doesn't know that it
239    // doesn't implement Drop, and so it doesn't know that it's safe to drop as
240    // soon as it's no longer used and allow buf to no longer be borrowed on the
241    // next line. It works fine in parse_ethernet_frame because EthernetFrame is
242    // a concrete type which Rust knows doesn't implement Drop.
243    core::mem::drop(packet);
244    Ok((buf, src_ip, dst_ip, proto, ttl))
245}
246
247/// Parse an ICMP packet.
248///
249/// `parse_icmp_packet` parses an ICMP packet, returning the body along with
250/// some important fields. Before returning, it invokes the callback `f` on the
251/// parsed packet.
252pub fn parse_icmp_packet<
253    I: IcmpIpExt,
254    C,
255    M: IcmpMessage<I, Code = C>,
256    F: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>),
257>(
258    mut buf: &[u8],
259    src_ip: I::Addr,
260    dst_ip: I::Addr,
261    f: F,
262) -> ParseResult<(M, C)>
263where
264    for<'a> IcmpPacket<I, &'a [u8], M>:
265        ParsablePacket<&'a [u8], IcmpParseArgs<I::Addr>, Error = ParseError>,
266{
267    let packet =
268        (&mut buf).parse_with::<_, IcmpPacket<I, _, M>>(IcmpParseArgs::new(src_ip, dst_ip))?;
269    let message = *packet.message();
270    let code = packet.code();
271    f(&packet);
272    Ok((message, code))
273}
274
275/// Parse an IP packet in an Ethernet frame.
276///
277/// `parse_ip_packet_in_ethernet_frame` parses an IP packet in an Ethernet
278/// frame, returning the body of the IP packet along with some important fields
279/// from both the IP and Ethernet headers.
280#[allow(clippy::type_complexity)]
281pub fn parse_ip_packet_in_ethernet_frame<I: IpExt>(
282    buf: &[u8],
283    ethernet_length_check: EthernetFrameLengthCheck,
284) -> IpParseResult<I, (&[u8], Mac, Mac, I::Addr, I::Addr, I::Proto, u8)> {
285    let (body, src_mac, dst_mac, ethertype) = parse_ethernet_frame(buf, ethernet_length_check)?;
286    if ethertype != Some(I::ETHER_TYPE) {
287        debug!("unexpected ethertype: {:?}", ethertype);
288        return Err(ParseError::NotExpected.into());
289    }
290
291    let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<I>(body)?;
292    Ok((body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl))
293}
294
295/// Parse an ICMP packet in an IP packet in an Ethernet frame.
296///
297/// `parse_icmp_packet_in_ip_packet_in_ethernet_frame` parses an ICMP packet in
298/// an IP packet in an Ethernet frame, returning the message and code from the
299/// ICMP packet along with some important fields from both the IP and Ethernet
300/// headers. Before returning, it invokes the callback `f` on the parsed packet.
301#[allow(clippy::type_complexity)]
302pub fn parse_icmp_packet_in_ip_packet_in_ethernet_frame<
303    I: IpExt,
304    C,
305    M: IcmpMessage<I, Code = C>,
306    F: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>),
307>(
308    buf: &[u8],
309    ethernet_length_check: EthernetFrameLengthCheck,
310    f: F,
311) -> IpParseResult<I, (Mac, Mac, I::Addr, I::Addr, u8, M, C)>
312where
313    for<'a> IcmpPacket<I, &'a [u8], M>:
314        ParsablePacket<&'a [u8], IcmpParseArgs<I::Addr>, Error = ParseError>,
315{
316    let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) =
317        parse_ip_packet_in_ethernet_frame::<I>(buf, ethernet_length_check)?;
318    if proto != I::ICMP_IP_PROTO {
319        debug!("unexpected IP protocol: {} (wanted {})", proto, I::ICMP_IP_PROTO);
320        return Err(ParseError::NotExpected.into());
321    }
322    let (message, code) = parse_icmp_packet(body, src_ip, dst_ip, f)?;
323    Ok((src_mac, dst_mac, src_ip, dst_ip, ttl, message, code))
324}
325
326/// Overwrite the checksum in an ICMPv6 message, returning the original value.
327///
328/// On `Err`, the provided buf is unmodified.
329pub fn overwrite_icmpv6_checksum(buf: &mut [u8], checksum: [u8; 2]) -> ParseResult<[u8; 2]> {
330    let buf = SliceBufViewMut::new(buf);
331    let mut message = Icmpv6PacketRaw::parse_mut(buf, ())?;
332    Ok(message.overwrite_checksum(checksum))
333}
334
335#[cfg(test)]
336mod crateonly {
337    use std::sync::Once;
338
339    /// Install a logger for tests.
340    ///
341    /// Call this method at the beginning of the test for which logging is desired.
342    /// This function sets global program state, so all tests that run after this
343    /// function is called will use the logger.
344    pub(crate) fn set_logger_for_test() {
345        /// log::Log implementation that uses stdout.
346        ///
347        /// Useful when debugging tests.
348        struct Logger;
349
350        impl log::Log for Logger {
351            fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
352                true
353            }
354
355            fn log(&self, record: &log::Record<'_>) {
356                println!("{}", record.args())
357            }
358
359            fn flush(&self) {}
360        }
361        static LOGGER_ONCE: Once = Once::new();
362
363        // log::set_logger will panic if called multiple times; using a Once makes
364        // set_logger_for_test idempotent
365        LOGGER_ONCE.call_once(|| {
366            log::set_logger(&Logger).unwrap();
367            log::set_max_level(log::LevelFilter::Trace);
368        })
369    }
370
371    /// Utilities to allow running benchmarks as tests.
372    ///
373    /// Our benchmarks rely on the unstable `test` feature, which is disallowed in
374    /// Fuchsia's build system. In order to ensure that our benchmarks are always
375    /// compiled and tested, this module provides mocks that allow us to run our
376    /// benchmarks as normal tests when the `benchmark` feature is disabled.
377    ///
378    /// See the `bench!` macro for details on how this module is used.
379    pub(crate) mod benchmarks {
380        /// A trait to allow mocking of the `test::Bencher` type.
381        pub(crate) trait Bencher {
382            fn iter<T, F: FnMut() -> T>(&mut self, inner: F);
383        }
384
385        #[cfg(feature = "benchmark")]
386        impl Bencher for test::Bencher {
387            fn iter<T, F: FnMut() -> T>(&mut self, inner: F) {
388                test::Bencher::iter(self, inner)
389            }
390        }
391
392        /// A `Bencher` whose `iter` method runs the provided argument once.
393        #[cfg(not(feature = "benchmark"))]
394        pub(crate) struct TestBencher;
395
396        #[cfg(not(feature = "benchmark"))]
397        impl Bencher for TestBencher {
398            fn iter<T, F: FnMut() -> T>(&mut self, mut inner: F) {
399                super::set_logger_for_test();
400                let _: T = inner();
401            }
402        }
403
404        #[inline(always)]
405        pub(crate) fn black_box<T>(dummy: T) -> T {
406            #[cfg(feature = "benchmark")]
407            return test::black_box(dummy);
408            #[cfg(not(feature = "benchmark"))]
409            return dummy;
410        }
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use net_types::ip::{Ipv4, Ipv6};
417
418    use crate::icmp::{IcmpDestUnreachable, IcmpEchoReply, Icmpv4DestUnreachableCode};
419    use crate::ip::Ipv6Proto;
420
421    use super::*;
422
423    #[test]
424    fn test_parse_ethernet_frame() {
425        use crate::testdata::arp_request::*;
426        let (body, src_mac, dst_mac, ethertype) =
427            parse_ethernet_frame(ETHERNET_FRAME.bytes, EthernetFrameLengthCheck::Check).unwrap();
428        assert_eq!(body, &ETHERNET_FRAME.bytes[14..]);
429        assert_eq!(src_mac, ETHERNET_FRAME.metadata.src_mac);
430        assert_eq!(dst_mac, ETHERNET_FRAME.metadata.dst_mac);
431        assert_eq!(ethertype, ETHERNET_FRAME.metadata.ethertype);
432    }
433
434    #[test]
435    fn test_parse_ip_packet() {
436        use crate::testdata::icmp_redirect::IP_PACKET_BYTES;
437        let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(IP_PACKET_BYTES).unwrap();
438        assert_eq!(body, &IP_PACKET_BYTES[20..]);
439        assert_eq!(src_ip, Ipv4Addr::new([10, 123, 0, 2]));
440        assert_eq!(dst_ip, Ipv4Addr::new([10, 123, 0, 1]));
441        assert_eq!(proto, Ipv4Proto::Icmp);
442        assert_eq!(ttl, 255);
443
444        use crate::testdata::icmp_echo_v6::REQUEST_IP_PACKET_BYTES;
445        let (body, src_ip, dst_ip, proto, ttl) =
446            parse_ip_packet::<Ipv6>(REQUEST_IP_PACKET_BYTES).unwrap();
447        assert_eq!(body, &REQUEST_IP_PACKET_BYTES[40..]);
448        assert_eq!(src_ip, Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 1]));
449        assert_eq!(dst_ip, Ipv6Addr::new([0xfec0, 0, 0, 0, 0, 0, 0, 0]));
450        assert_eq!(proto, Ipv6Proto::Icmpv6);
451        assert_eq!(ttl, 64);
452    }
453
454    #[test]
455    fn test_parse_ip_packet_in_ethernet_frame() {
456        use crate::testdata::tls_client_hello_v4::*;
457        let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) =
458            parse_ip_packet_in_ethernet_frame::<Ipv4>(
459                ETHERNET_FRAME.bytes,
460                EthernetFrameLengthCheck::Check,
461            )
462            .unwrap();
463        assert_eq!(body, &IPV4_PACKET.bytes[IPV4_PACKET.body_range]);
464        assert_eq!(src_mac, ETHERNET_FRAME.metadata.src_mac);
465        assert_eq!(dst_mac, ETHERNET_FRAME.metadata.dst_mac);
466        assert_eq!(src_ip, IPV4_PACKET.metadata.src_ip);
467        assert_eq!(dst_ip, IPV4_PACKET.metadata.dst_ip);
468        assert_eq!(proto, IPV4_PACKET.metadata.proto);
469        assert_eq!(ttl, IPV4_PACKET.metadata.ttl);
470    }
471
472    #[test]
473    fn test_parse_icmp_packet() {
474        set_logger_for_test();
475        use crate::testdata::icmp_dest_unreachable::*;
476        let (body, ..) = parse_ip_packet::<Ipv4>(&IP_PACKET_BYTES).unwrap();
477        let (_, code) = parse_icmp_packet::<Ipv4, _, IcmpDestUnreachable, _>(
478            body,
479            Ipv4Addr::new([172, 217, 6, 46]),
480            Ipv4Addr::new([192, 168, 0, 105]),
481            |_| {},
482        )
483        .unwrap();
484        assert_eq!(code, Icmpv4DestUnreachableCode::DestHostUnreachable);
485    }
486
487    #[test]
488    fn test_parse_icmp_packet_in_ip_packet_in_ethernet_frame() {
489        set_logger_for_test();
490        use crate::testdata::icmp_echo_ethernet::*;
491        let (src_mac, dst_mac, src_ip, dst_ip, _, _, _) =
492            parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpEchoReply, _>(
493                &REPLY_ETHERNET_FRAME_BYTES,
494                EthernetFrameLengthCheck::Check,
495                |_| {},
496            )
497            .unwrap();
498        assert_eq!(src_mac, Mac::new([0x50, 0xc7, 0xbf, 0x1d, 0xf4, 0xd2]));
499        assert_eq!(dst_mac, Mac::new([0x8c, 0x85, 0x90, 0xc9, 0xc9, 0x00]));
500        assert_eq!(src_ip, Ipv4Addr::new([172, 217, 6, 46]));
501        assert_eq!(dst_ip, Ipv4Addr::new([192, 168, 0, 105]));
502    }
503}