crafter 0.3.1

Packet-level network interaction for Rust tools and agents.
Documentation
use std::net::{Ipv4Addr, Ipv6Addr};

use crate::wire::{IpDefrag, PacketRecord, PacketTransform, Sniffer, VecPacketSource, WireError};
use crate::{CrafterError, Ipv4, Ipv6, Ipv6FragmentHeader, Raw, IPPROTO_TCP, IPPROTO_UDP};

const IPV4_ID: u16 = 0x4222;
const IPV6_ID: u32 = 0x6000_4222;

fn ipv4_source() -> Ipv4Addr {
    Ipv4Addr::new(192, 0, 2, 42)
}

fn ipv4_destination() -> Ipv4Addr {
    Ipv4Addr::new(198, 51, 100, 42)
}

fn ipv6_source() -> Ipv6Addr {
    "2001:db8:42::1".parse().unwrap()
}

fn ipv6_destination() -> Ipv6Addr {
    "2001:db8:42::2".parse().unwrap()
}

fn ipv4_fragment(fragment_offset: u16, more_fragments: bool, payload: &[u8]) -> PacketRecord {
    PacketRecord::new(
        Ipv4::with_addresses(ipv4_source(), ipv4_destination())
            .protocol(IPPROTO_UDP)
            .identification(IPV4_ID)
            .fragment_offset(fragment_offset)
            .more_fragments(more_fragments)
            / Raw::from_bytes(payload),
    )
}

fn ipv4_non_fragment() -> PacketRecord {
    PacketRecord::new(
        Ipv4::with_addresses(ipv4_source(), ipv4_destination()).protocol(IPPROTO_TCP)
            / Raw::from_bytes(b"not-fragmented"),
    )
}

fn ipv6_fragment(fragment_offset: u16, more_fragments: bool, payload: &[u8]) -> PacketRecord {
    PacketRecord::new(
        Ipv6::new().src(ipv6_source()).dst(ipv6_destination())
            / Ipv6FragmentHeader::new()
                .next_header(IPPROTO_UDP)
                .identification(IPV6_ID)
                .fragment_offset(fragment_offset)
                .more_fragments(more_fragments)
            / Raw::from_bytes(payload),
    )
}

fn ipv6_non_fragment() -> PacketRecord {
    PacketRecord::new(
        Ipv6::new()
            .src(ipv6_source())
            .dst(ipv6_destination())
            .next_header(IPPROTO_TCP)
            / Raw::from_bytes(b"not-fragmented"),
    )
}

#[test]
fn defrag_contract_transform_to_output_incomplete_fragment_sets_emit_zero_records() {
    let mut ipv4_transform = IpDefrag::new();
    let ipv4_output = ipv4_transform
        .transform_to_output(ipv4_fragment(0, true, b"abcdefgh"))
        .unwrap();
    assert_eq!(ipv4_output.len(), 0);

    let mut ipv6_transform = IpDefrag::new();
    let ipv6_output = ipv6_transform
        .transform_to_output(ipv6_fragment(0, true, b"abcdefgh"))
        .unwrap();
    assert_eq!(ipv6_output.len(), 0);
}

#[test]
fn defrag_contract_sniffer_incomplete_fragment_sets_emit_zero_records() {
    let ipv4_records = Sniffer::new(VecPacketSource::new([ipv4_fragment(0, true, b"abcdefgh")]))
        .with(IpDefrag::new())
        .no_timeout()
        .collect_records()
        .unwrap();
    assert_eq!(ipv4_records.len(), 0);

    let ipv6_records = Sniffer::new(VecPacketSource::new([ipv6_fragment(0, true, b"abcdefgh")]))
        .with(IpDefrag::new())
        .no_timeout()
        .collect_records()
        .unwrap();
    assert_eq!(ipv6_records.len(), 0);
}

#[test]
fn defrag_contract_transform_to_output_complete_fragment_sets_emit_one_record() {
    let mut ipv4_transform = IpDefrag::new();
    assert!(ipv4_transform
        .transform_to_output(ipv4_fragment(1, false, b"ijkl"))
        .unwrap()
        .is_empty());
    let ipv4_output = ipv4_transform
        .transform_to_output(ipv4_fragment(0, true, b"abcdefgh"))
        .unwrap();
    assert_eq!(ipv4_output.len(), 1);
    assert_eq!(
        ipv4_output.records()[0]
            .metadata()
            .ip_defrag_metadata()
            .len(),
        1
    );

    let mut ipv6_transform = IpDefrag::new();
    assert!(ipv6_transform
        .transform_to_output(ipv6_fragment(1, false, b"ijkl"))
        .unwrap()
        .is_empty());
    let ipv6_output = ipv6_transform
        .transform_to_output(ipv6_fragment(0, true, b"abcdefgh"))
        .unwrap();
    assert_eq!(ipv6_output.len(), 1);
    assert_eq!(
        ipv6_output.records()[0]
            .metadata()
            .ip_defrag_metadata()
            .len(),
        1
    );
}

#[test]
fn defrag_contract_sniffer_complete_fragment_sets_emit_one_record() {
    let ipv4_records = Sniffer::new(VecPacketSource::new([
        ipv4_fragment(1, false, b"ijkl"),
        ipv4_fragment(0, true, b"abcdefgh"),
    ]))
    .with(IpDefrag::new())
    .no_timeout()
    .collect_records()
    .unwrap();
    assert_eq!(ipv4_records.len(), 1);
    assert_eq!(ipv4_records[0].metadata().ip_defrag_metadata().len(), 1);

    let ipv6_records = Sniffer::new(VecPacketSource::new([
        ipv6_fragment(1, false, b"ijkl"),
        ipv6_fragment(0, true, b"abcdefgh"),
    ]))
    .with(IpDefrag::new())
    .no_timeout()
    .collect_records()
    .unwrap();
    assert_eq!(ipv6_records.len(), 1);
    assert_eq!(ipv6_records[0].metadata().ip_defrag_metadata().len(), 1);
}

#[test]
fn defrag_contract_transform_to_output_non_fragmented_records_emit_one_record() {
    let mut ipv4_transform = IpDefrag::new();
    let ipv4_output = ipv4_transform
        .transform_to_output(ipv4_non_fragment())
        .unwrap();
    assert_eq!(ipv4_output.len(), 1);
    assert!(ipv4_output.records()[0]
        .metadata()
        .ip_defrag_metadata()
        .is_empty());

    let mut ipv6_transform = IpDefrag::new();
    let ipv6_output = ipv6_transform
        .transform_to_output(ipv6_non_fragment())
        .unwrap();
    assert_eq!(ipv6_output.len(), 1);
    assert!(ipv6_output.records()[0]
        .metadata()
        .ip_defrag_metadata()
        .is_empty());
}

#[test]
fn defrag_contract_sniffer_non_fragmented_records_emit_one_record() {
    let ipv4_records = Sniffer::new(VecPacketSource::new([ipv4_non_fragment()]))
        .with(IpDefrag::new())
        .no_timeout()
        .collect_records()
        .unwrap();
    assert_eq!(ipv4_records.len(), 1);

    let ipv6_records = Sniffer::new(VecPacketSource::new([ipv6_non_fragment()]))
        .with(IpDefrag::new())
        .no_timeout()
        .collect_records()
        .unwrap();
    assert_eq!(ipv6_records.len(), 1);
}

#[test]
fn defrag_contract_conflicting_overlaps_do_not_emit_ambiguous_reassemblies() {
    let mut ipv4_transform = IpDefrag::new();
    assert!(ipv4_transform
        .transform_to_output(ipv4_fragment(0, true, b"abcdefghijklmnop"))
        .unwrap()
        .is_empty());
    let ipv4_error = ipv4_transform
        .transform_to_output(ipv4_fragment(1, false, b"QRSTUVWX"))
        .unwrap_err();
    assert_ambiguous_overlap_error(ipv4_error, "ip.defrag.ipv4.overlap");
    assert_eq!(ipv4_transform.emitted_count(), 0);

    let mut ipv6_transform = IpDefrag::new();
    assert!(ipv6_transform
        .transform_to_output(ipv6_fragment(0, true, b"abcdefghijklmnop"))
        .unwrap()
        .is_empty());
    let ipv6_error = ipv6_transform
        .transform_to_output(ipv6_fragment(1, false, b"QRSTUVWX"))
        .unwrap_err();
    assert_ambiguous_overlap_error(ipv6_error, "ip.defrag.ipv6.overlap");
    assert_eq!(ipv6_transform.emitted_count(), 0);
}

#[test]
fn defrag_contract_sniffer_conflicting_overlaps_do_not_emit_ambiguous_reassemblies() {
    let mut ipv4_sniffer = Sniffer::new(VecPacketSource::new([
        ipv4_fragment(0, true, b"abcdefghijklmnop"),
        ipv4_fragment(1, false, b"QRSTUVWX"),
    ]))
    .with(IpDefrag::new())
    .no_timeout();
    let ipv4_error = ipv4_sniffer.next_record().unwrap_err();
    assert_ambiguous_overlap_error(ipv4_error, "ip.defrag.ipv4.overlap");
    assert_eq!(ipv4_sniffer.yielded(), 0);

    let mut ipv6_sniffer = Sniffer::new(VecPacketSource::new([
        ipv6_fragment(0, true, b"abcdefghijklmnop"),
        ipv6_fragment(1, false, b"QRSTUVWX"),
    ]))
    .with(IpDefrag::new())
    .no_timeout();
    let ipv6_error = ipv6_sniffer.next_record().unwrap_err();
    assert_ambiguous_overlap_error(ipv6_error, "ip.defrag.ipv6.overlap");
    assert_eq!(ipv6_sniffer.yielded(), 0);
}

fn assert_ambiguous_overlap_error(error: WireError, expected_field: &str) {
    match error {
        WireError::Packet(CrafterError::InvalidFieldValue { field, reason }) => {
            assert_eq!(field, expected_field);
            assert!(reason.contains("ambiguous"));
        }
        other => panic!("expected ambiguous overlap error, got {other:?}"),
    }
}