crafter 0.3.2

Packet-level network interaction for Rust tools and agents.
Documentation
mod common;

use common::{
    local_ipv4, parse_ipv4_arg, print_help_if_requested, remote_ipv4, ExampleResult, EXAMPLE_IFACE,
};
use crafter::prelude::*;

fn main() -> ExampleResult<()> {
    if print_help_if_requested(
        "usage: cargo run --example tcp_options -- [--src IP] [--dst IP]\n\nBuild labeled TCP packets covering common options, SACK blocks, Fast Open, and generic options.",
    ) {
        return Ok(());
    }

    let src = parse_ipv4_arg("--src", local_ipv4())?;
    let dst = parse_ipv4_arg("--dst", remote_ipv4())?;

    println!("example: tcp_options");
    println!("mode: offline");
    let syn = syn_options(src, dst)?;
    inspect_tcp_packet("mss window-scale sack-permitted timestamp", &syn)?;
    inspect_tcp_packet("sack blocks", &sack_blocks(src, dst)?)?;
    inspect_tcp_packet("fast-open generic padding", &misc_options(src, dst)?)?;

    // Demonstrate an offline send plan. send_dry_run never opens a live socket;
    // it compiles the packet and reports what a live send would do over the
    // documentation interface, so this stays safe and deterministic.
    dry_run_plan(&syn)?;

    Ok(())
}

fn syn_options(src: std::net::Ipv4Addr, dst: std::net::Ipv4Addr) -> ExampleResult<Packet> {
    let tcp = Tcp::new()
        .sport(41000)
        .dport(443)
        .seq(1)
        .flags(TCP_FLAG_SYN)
        .tcp_option(TcpOption::mss(1460))?
        .tcp_option(TcpOption::window_scale(7))?
        .tcp_option(TcpOption::sack_permitted())?
        .tcp_option(TcpOption::timestamp(0x1020_3040, 0))?;

    Ok(Ipv4::new().src(src).dst(dst).id(0x5401) / tcp)
}

fn sack_blocks(src: std::net::Ipv4Addr, dst: std::net::Ipv4Addr) -> ExampleResult<Packet> {
    let tcp = Tcp::new()
        .sport(443)
        .dport(41000)
        .seq(10_000)
        .ack(20_000)
        .flags(TCP_FLAG_ACK)
        .tcp_option(TcpOption::sack(vec![
            TcpSackBlock::new(20_100, 20_400),
            TcpSackBlock::new(20_800, 21_000),
        ]))?;

    Ok(Ipv4::new().src(dst).dst(src).id(0x5402) / tcp)
}

fn misc_options(src: std::net::Ipv4Addr, dst: std::net::Ipv4Addr) -> ExampleResult<Packet> {
    let tcp = Tcp::new()
        .sport(41000)
        .dport(80)
        .seq(1)
        .flags(TCP_FLAG_SYN)
        .tcp_option(TcpOption::no_operation())?
        .tcp_option(TcpOption::fast_open([0xaa_u8, 0xbb, 0xcc, 0xdd]))?
        .tcp_option(TcpOption::generic(254, [0x01_u8, 0x02]))?
        .tcp_option(TcpOption::mss(1460))?;

    Ok(Ipv4::new().src(src).dst(dst).id(0x5403) / tcp / Raw::from("misc"))
}

fn inspect_tcp_packet(label: &str, packet: &Packet) -> ExampleResult<()> {
    let compiled = packet.compile()?;
    let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, compiled.as_bytes())?;
    let tcp = decoded
        .layer::<Tcp>()
        .expect("decoded TCP option packet should contain Tcp");
    let options = tcp.parsed_options()?;

    println!("packet: {label}");
    println!("summary: {}", packet.summary());
    println!("decoded summary: {}", decoded.summary());
    println!(
        "data offset: {} words ({} bytes)",
        tcp.data_offset_value(),
        tcp.header_len()
    );
    for (index, option) in options.iter().enumerate() {
        println!("tcp option {}: {:?}", index + 1, option);
    }
    println!("show:\n{}", decoded.show());
    println!("hexdump:\n{}", compiled.hexdump());

    Ok(())
}

fn dry_run_plan(packet: &Packet) -> ExampleResult<()> {
    let plan = packet.send_dry_run(SendOptions::new().iface(EXAMPLE_IFACE).network_layer())?;

    println!("dry-run plan");
    println!("interface: {}", plan.interface());
    println!("target: {:?}", plan.target());
    println!("compiled bytes: {}", plan.len());

    Ok(())
}