crafter 0.3.1

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

use super::ipv4::{extract_ipv4_fragment, Ipv4FragmentPassThroughReason};
use super::ipv6::{
    extract_ipv6_fragment, Ipv6FragmentPassThroughReason,
    IPV6_FRAGMENT_UNSUPPORTED_EXTENSION_SCOPE_NOTE,
};
use super::{IpDefrag, IpFragment};
use crate::wire::backend::pcap::PcapLinkType;
use crate::wire::record::PacketRecord;
use crate::wire::WireError;
use crate::{CrafterError, Ipv4, Raw, IPPROTO_IPV6_AH, IPPROTO_IPV6_FRAGMENT, IPPROTO_UDP};

fn raw_ip_record(bytes: Vec<u8>) -> PacketRecord {
    PacketRecord::new(Raw::from_bytes(&bytes))
        .with_pcap_link_type(PcapLinkType::RawIp)
        .with_captured_bytes(bytes)
}

fn pcap_record(bytes: Vec<u8>, link_type: PcapLinkType) -> PacketRecord {
    PacketRecord::new(Raw::from_bytes(&bytes))
        .with_pcap_link_type(link_type)
        .with_captured_bytes(bytes)
}

fn ipv4_fragment_bytes(flags_fragment: u16, total_len: u16, payload: &[u8]) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(20 + payload.len());
    bytes.push(0x45);
    bytes.push(0);
    bytes.extend_from_slice(&total_len.to_be_bytes());
    bytes.extend_from_slice(&0x1234u16.to_be_bytes());
    bytes.extend_from_slice(&flags_fragment.to_be_bytes());
    bytes.push(64);
    bytes.push(253);
    bytes.extend_from_slice(&[0, 0]);
    bytes.extend_from_slice(&[192, 0, 2, 1]);
    bytes.extend_from_slice(&[198, 51, 100, 2]);
    bytes.extend_from_slice(payload);
    bytes
}

fn ipv6_fragment_bytes(payload_len: u16, fragment_bytes: &[u8]) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(40 + fragment_bytes.len());
    bytes.extend_from_slice(&[0x60, 0, 0, 0]);
    bytes.extend_from_slice(&payload_len.to_be_bytes());
    bytes.push(IPPROTO_IPV6_FRAGMENT);
    bytes.push(64);
    bytes.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
    bytes.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
    bytes.extend_from_slice(fragment_bytes);
    bytes
}

fn ipv6_unsupported_ah_fragment_bytes() -> Vec<u8> {
    let mut bytes = ipv6_fragment_bytes(
        16,
        &[
            IPPROTO_UDP,
            0,
            0,
            1,
            0x10,
            0x20,
            0x30,
            0x40,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
        ],
    );
    bytes[6] = IPPROTO_IPV6_AH;
    bytes[40] = IPPROTO_IPV6_FRAGMENT;
    bytes
}

fn expect_buffer_too_short(error: CrafterError, context: &'static str) {
    match error {
        CrafterError::BufferTooShort {
            context: actual,
            required,
            available,
        } => {
            assert_eq!(actual, context);
            assert!(
                required > available,
                "BufferTooShort must require more bytes than are available"
            );
        }
        other => panic!("expected BufferTooShort for {context}, got {other:?}"),
    }
}

fn expect_invalid_field(error: CrafterError, field: &'static str) {
    match error {
        CrafterError::InvalidFieldValue {
            field: actual,
            reason,
        } => {
            assert_eq!(actual, field);
            assert!(!reason.is_empty());
        }
        other => panic!("expected InvalidFieldValue for {field}, got {other:?}"),
    }
}

fn expect_wire_packet_error(error: WireError) -> CrafterError {
    match error {
        WireError::Packet(error) => error,
        other => panic!("expected packet error, got {other:?}"),
    }
}

#[test]
fn truncated_ipv4_fragment_header_returns_buffer_too_short() {
    let mut bytes = ipv4_fragment_bytes(0x2000, 20, &[]);
    bytes.truncate(19);
    let record = raw_ip_record(bytes);

    let error = extract_ipv4_fragment(&record).unwrap_err();
    expect_buffer_too_short(error, "ipv4 header");

    let mut transform = IpDefrag::new();
    let error = transform.defrag_record(record).unwrap_err();
    expect_buffer_too_short(expect_wire_packet_error(error), "ipv4 header");
}

#[test]
fn truncated_ipv6_fragment_header_returns_buffer_too_short() {
    let bytes = ipv6_fragment_bytes(4, &[IPPROTO_UDP, 0, 0, 1]);
    let record = raw_ip_record(bytes);

    let error = extract_ipv6_fragment(&record).unwrap_err();
    expect_buffer_too_short(error, "ipv6 fragment header");

    let mut transform = IpDefrag::new();
    let error = transform.defrag_record(record).unwrap_err();
    expect_buffer_too_short(expect_wire_packet_error(error), "ipv6 fragment header");
}

#[test]
fn fragment_payload_length_mismatches_return_structured_errors() {
    let ipv4 = raw_ip_record(ipv4_fragment_bytes(0x2000, 32, &[0xaa; 8]));
    let error = extract_ipv4_fragment(&ipv4).unwrap_err();
    expect_buffer_too_short(error, "ipv4 packet");

    let ipv6 = raw_ip_record(ipv6_fragment_bytes(8, &[IPPROTO_UDP, 0, 0, 1]));
    let error = extract_ipv6_fragment(&ipv6).unwrap_err();
    expect_buffer_too_short(error, "ipv6 packet");
}

#[test]
fn impossible_ipv4_fragment_offset_returns_invalid_field_value() {
    let payload = [0x5a; 16];
    let record = PacketRecord::new(
        Ipv4::new()
            .src(Ipv4Addr::new(192, 0, 2, 1))
            .dst(Ipv4Addr::new(198, 51, 100, 2))
            .protocol(253)
            .fragment_offset(0x1fff)
            / Raw::from_bytes(payload),
    );
    let mut transform = IpFragment::new(28);

    let error = transform.fragment_record(record).unwrap_err();
    expect_invalid_field(expect_wire_packet_error(error), "ipv4.fragment_offset");
}

#[test]
fn unsupported_wrappers_pass_through_without_panic() {
    let bytes = ipv4_fragment_bytes(0x2000, 28, &[0xaa; 8]);
    let record = pcap_record(bytes.clone(), PcapLinkType::Ieee80211);

    let extracted = extract_ipv4_fragment(&record).unwrap();
    assert_eq!(
        extracted.pass_through().map(|pass| pass.reason()),
        Some(Ipv4FragmentPassThroughReason::UnsupportedWrapper)
    );

    let mut defrag = IpDefrag::new();
    let output = defrag.defrag_record(record.clone()).unwrap();
    assert_eq!(output.len(), 1);
    assert_eq!(
        output.records()[0].metadata().captured_bytes(),
        Some(bytes.as_slice())
    );

    let mut fragment = IpFragment::new(1280);
    let output = fragment.fragment_record(record).unwrap();
    assert_eq!(output.len(), 1);
    assert_eq!(
        output.records()[0].metadata().captured_bytes(),
        Some(bytes.as_slice())
    );
}

#[test]
fn unsupported_ipv6_fragment_scope_remains_traceable_pass_through() {
    let bytes = ipv6_unsupported_ah_fragment_bytes();
    let record = raw_ip_record(bytes);

    let extracted = extract_ipv6_fragment(&record).unwrap();
    assert_eq!(
        extracted.pass_through().map(|pass| pass.reason()),
        Some(Ipv6FragmentPassThroughReason::UnsupportedExtensionChain)
    );

    let mut defrag = IpDefrag::new();
    let output = defrag.defrag_record(record).unwrap();
    assert_eq!(output.len(), 1);
    let traces = output.records()[0].metadata().transforms();
    assert_eq!(traces.len(), 1);
    assert_eq!(traces[0].name(), "ip-defrag");
    assert_eq!(
        traces[0].note(),
        Some(IPV6_FRAGMENT_UNSUPPORTED_EXTENSION_SCOPE_NOTE)
    );
}