Skip to main content

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