crafter 0.3.0

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

libcrafter is a Rust workspace for packet-level network interaction. Its public crate, crafter, lets agents and Rust tools build protocol-correct packets, place them on real networks, decode what comes back, and act on what they observe.

This README is a progressive walkthrough: build your first packet, inspect and decode bytes, read and write pcap, plan a send, and reach for disposable endpoints and labs when traffic cannot live on the developer machine. Every snippet uses documentation address space (192.0.2.0/24, 198.51.100.0/24, 2001:db8::/32) and offline or dry-run defaults; live traffic is always an explicit opt-in.

Your first packet

A Packet is a typed stack of layers. Build one with / composition, then compile() to fill the dependent fields (lengths, protocol numbers, header lengths, checksums) that you did not set yourself:

use crafter::prelude::*;
use std::net::Ipv4Addr;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let packet = Ipv4::new()
        .src(Ipv4Addr::new(192, 0, 2, 10))
        .dst(Ipv4Addr::new(198, 51, 100, 20))
        / Icmpv4::echo_request().id(0x4242).seq(1)
        / Raw::from("hello");

    let compiled = packet.compile()?;

    println!("{}", packet.summary());      // one-line stack summary
    println!("{}", packet.show());         // full field-by-field view
    println!("{}", compiled.hexdump());    // the bytes on the wire
    Ok(())
}

compile() fills only what you left unset. Anything you set explicitly survives untouched — including values that are wrong on purpose — so the same builder emits both protocol-correct packets and deliberately malformed ones.

Inspect and decode

Reach into a packet by layer type, and decode raw bytes from an explicit link or network context. Decoding never guesses the entry point: you name the link type or network layer.

use crafter::prelude::*;

fn inspect(frame: &[u8], datagram: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
    // From an Ethernet frame:
    let packet = Packet::decode_from_link(LinkType::Ethernet, frame)?;
    if let Some(ip) = packet.layer::<Ipv4>() {
        println!("{} -> {}", ip.source(), ip.destination());
    }

    // From a bare IPv4 datagram:
    let l3 = Packet::decode_from_l3(NetworkLayer::Ipv4, datagram)?;
    println!("{}", l3.summary());
    Ok(())
}

A malformed enclosing header surfaces as a structured error with context, required, and available rather than a panic, and any next protocol the decoder does not model is preserved as a Raw payload when the header around it is valid. See docs/reference/api.md for the full decode contract and typed-layer accessors.

Capture and pcap

Read classic pcap files through the packet-wire API, apply a libpcap BPF filter string, and iterate records with a Sniffer:

use crafter::prelude::*;

fn read_pcap(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let source = PacketWire::pcap_file(path)
        .filter("tcp or udp")   // libpcap BPF filter string
        .open()?
        .source()?;

    for record in Sniffer::new(source).collect_records()? {
        println!("{}", record.packet().summary());
        println!("link type: {:?}", record.metadata().link_type());
    }
    Ok(())
}

Writing pcap is just as direct: compile packets, hand them to a pcap writer through PacketWire, and replay them later. The packet-wire layer covers classic pcap read/write, libpcap BPF filters, offline sniffing, and bounded live capture hooks. Full pcapng and a full BPF parser are not currently in scope. See docs/reference/wire.md for sources, writers, transmitters, and transform chains.

Send and receive

Sending is offline by default. SendRecv plans a send/receive without touching the wire; .dry_run() is the default-safe mode, so no packet leaves the host. The report still carries the attempts, the auto-derived reply filter, and any matched reply:

use crafter::prelude::*;
use std::net::Ipv4Addr;
use std::time::Duration;

fn plan() -> Result<(), Box<dyn std::error::Error>> {
    let packet = Ipv4::new()
        .src(Ipv4Addr::new(192, 0, 2, 10))
        .dst(Ipv4Addr::new(198, 51, 100, 20))
        / Icmpv4::echo_request().id(0x4242).seq(1)
        / Raw::from("ping");

    // SendRecv derives the BPF reply filter from the packet automatically.
    let report = packet.send_recv_report(
        SendRecv::new()
            .iface("eth0")
            .network_layer()
            .dry_run()                     // offline default: no packet leaves
            .timeout(Duration::from_millis(250))
            .retries(1),
    )?;

    println!("attempts: {}", report.attempts());
    println!("reply filter: {}", report.effective_filter().unwrap_or(""));
    match report.reply() {
        Some(reply) => println!("reply: {}", reply.summary()),
        None => println!("reply: none"),
    }
    Ok(())
}

The live path is the same call with one explicit change — swap .dry_run() for .live():

use crafter::prelude::*;

fn live(packet: &Packet) -> Result<(), Box<dyn std::error::Error>> {
    let report = packet.send_recv_report(
        SendRecv::new()
            .iface("eth0")
            .network_layer()
            .live(),                       // explicit opt-in: real raw send
    )?;

    if let Some(reply) = report.reply() {
        println!("{}", reply.summary());
    }
    Ok(())
}

Live raw sends and captures require platform privileges, and you must be authorized to send and capture on the target network. Keep live work off the developer host — provision a disposable endpoint instead.

Tools: endpoint, lab, oracle, probe

The live path does not have to originate from your machine. Four modules under tools/ provision disposable network positions, run packet work from them, collect artifacts, and tear the resources down when the run is done:

  • endpoint — one disposable endpoint: doctor, create, exec, upload, download, collect artifacts, destroy.
  • lab — multi-endpoint lab sessions that coordinate several endpoints for one run.
  • oracle — packet-equivalence validation against reference backends (offline, pcap, and live modes).
  • probe — peer-behavior validation against a live peer using seeded profiles.

The stack layers as endpoint ← lab ← oracle/probe. Every invocation is offline by default; live traffic is an explicit opt-in. Start every provider-backed run with --dry-run:

tools/endpoint/run doctor --provider hetzner --exposure wan --dry-run
tools/lab/run doctor --provider hetzner --dry-run
tools/oracle/run live --provider hetzner --dry-run --profile smoke --seed 1 --count 10
tools/probe/run --provider hetzner --dry-run --profile smoke --seed 1 --count 10

Opt into live traffic only when an authorized human or agent has said so. The Hetzner provider reads HETZNER_API_TOKEN or HCLOUD_TOKEN from the environment; never commit credentials, public IPs, or captures from sensitive networks.

Protocol coverage

Every layer below is exported from crafter::prelude and slots into the same builder, decode, and summary surface. Unknown or unsupported next protocols are preserved as Raw payloads when the enclosing header is valid.

Layer Coverage Guide
Ethernet / VLAN Ethernet II and 802.1Q VLAN, Linux cooked capture, null/loopback
IEEE 802.11 Management, control, and data frames with radiotap and LLC/SNAP, EAPOL and RSN (802.11i) key-exchange fields; monitor-mode radiotap injection (transmit on the air) supported behind explicit live gates dot11
ARP Request/reply construction and decode arp
IPv4 DSCP/ECN, protocol labels, checksum status, typed options, fragment fields (no automatic reassembly) ipv4
IGMP IPv4 packet-layer membership queries/reports, IGMPv1/v2 compatibility, IGMPv3 query/report records, generic extensions, and multicast router discovery packet shapes; not a router, snooper, proxy, or scanner igmp
IPv6 Base header plus hop-by-hop, destination, fragment, routing, mobile-routing, and segment-routing extension headers ipv6
ICMPv4 / ICMPv6 ICMPv4 (with Icmp deprecated alias) and ICMP extensions (RFC 4884); ICMPv6 echo/errors, Neighbor Discovery (RFC 4861), MLD v1/v2, Extended Echo, experimental Node Information icmpv6
TCP Segment construction, typed options, checksums tcp
UDP UDP with options (RFC 9868) and checksum status udp
DNS EDNS(0), SVCB/HTTPS, DNSSEC record types dns
DHCPv4 Option overload, RFC 3396 long options, relay agent option 82, client identifiers, authentication, leasequery fields
BGP OPEN, UPDATE, KEEPALIVE, NOTIFICATION, ROUTE-REFRESH, path attributes, capabilities bgp
OSPF OSPFv2 Hello/DD/LSR/LSU/LSAck packets, Router/Network/Summary/AS-External/NSSA/Opaque (TE, RI) LSAs, null/simple/keyed-MD5/HMAC-SHA authentication, plus an OSPFv3 base layer; wire-level build and decode only (no state machine, SPF, or LSDB) ospf
IPsec ESP, AH, and IKEv2 (IKE header and payload set) with SA and transform primitives ipsec

IP fragmentation and reassembly are explicit IpFragment / IpDefrag wire transforms, not automatic decode-time behavior. TCP stream reassembly, full pcapng, full BPF parsing, and a complete TCP/IP stack are not currently in scope.

Examples

Rust examples live under crafter/examples/ and build against the public crafter crate. They are offline or dry-run unless an example is explicitly live-gated:

cargo build -p crafter --examples
cargo run -p crafter --example hello_world          # build, compile, hexdump
cargo run -p crafter --example decode_bytes         # decode entry points
cargo run -p crafter --example pcap_read            # pcap + Sniffer
cargo run -p crafter --example send_recv_icmp       # dry-run send/receive
cargo run -p crafter --example dns_query -- --name example.com

By category:

  • Core packet model — hello_world, packet_building, packet_inspection, decode_bytes, custom_registry.
  • Net workflows — send_plan, send_packet, send_recv_icmp, network_ping, batch_send, batch_send_recv, interface_helpers, ip_ranges.
  • Pcap and sniffing — pcap_write, pcap_read, wire_pcap_sniffer, wire_transform_chain, ip_defrag_offline, ip_fragment_offline, wpa_decrypt_offline.
  • Protocols — arp_who_has, dns_query, dhcp_discover, icmpv4_error, icmpv6_echo, vlan, dot11_beacon_rsn, ipsec_esp, bgp_session.

The full annotated table, with safety modes and commands, is in docs/reference/examples.md.

Documentation

  • docs/README.md is the documentation index.
  • docs/guide/ — per-protocol wire coverage for everyday packet work (IPv4, IGMP, IPv6, TCP, UDP, ARP, ICMPv6, DNS, BGP, OSPF, 802.11, IPsec); UDP, ARP, ICMPv6, IGMP, and OSPF now have their own guides.
  • docs/reference/ — the public API (api.md), the wire I/O layer (wire.md), and the example catalog (examples.md).
  • docs/operations/ — live, provider-backed, and manual testing workflows (validation, probes, lab sessions, endpoints).
  • docs/operations/tools.md — tools overview tying the endpoint, lab, oracle, and probe modules together.
  • CHANGELOG.md records release scope and boundaries.

Validation

Local validation does not require provider credentials:

cargo test --workspace
cargo doc --workspace --no-deps

The full local release gate is:

.agents/scripts/check-crafter-release --static

Provider-backed validation starts with dry-runs against the oracle, probe, and endpoint runners before any live invocation. Oracle modes, backends, and CI expectations are documented in docs/operations/validation.md.

Publishing

The final maintainer release checklist, including crates.io guardrails, is in docs/operations/release.md.

Run package-content checks before preparing a release:

.agents/scripts/check-crafter-release --package-only

Run the full local release gate before declaring the branch ready:

.agents/scripts/check-crafter-release --static

Prepare and validate a candidate version with the guarded release helper:

.agents/scripts/prepare-crafter-release --validate VERSION

For agent-assisted publishing, use the repo-local agent-cargo-publish skill. It runs the release gate, performs the guarded cargo publish -p crafter --dry-run --locked, summarizes the crate/version/commit and package contents, and requires explicit ask-tool approval before the real upload.

The maintainer publish entrypoint is:

.agents/scripts/publish-crafter-release VERSION

Publishing to crates.io is a final maintainer action. The publish script and skill enforce confirmation; do not run a real publish from unattended automation.

License

This project is licensed under the MIT License. See LICENSE.