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