Skip to content

RootWire 6.0.0

Latest

Choose a tag to compare

@EONRaider EONRaider released this 09 Sep 14:09
· 29 commits to master since this release
9f79919

RootWire's own dependency, NETProtocols,
is bumped to its latest release (2.2.1); no source changes were needed
— the only breaking change since RootWire's 1.3 floor (removal of
Packet.payload) is a name RootWire never called.

Changed

  • BREAKING: Capture timestamps are now kernel-sourced nanoseconds
    end-to-end instead of a userspace time.time() reading. Live capture
    enables SO_TIMESTAMPNS and reads the kernel's own arrival timestamp
    via recvmsg's ancillary data instead of a time.time() call after
    recv returns, which included scheduler and interpreter latency
    between the frame's arrival and Python observing it. DecodedFrame.timestamp
    is now an int (nanoseconds since epoch), not a float (seconds).
    This changes two on-the-wire contracts: the NDJSON timestamp field
    is now an integer nanosecond value rather than a float epoch-seconds
    value, and -w now writes nanosecond-precision classic pcap files
    (magic 0xA1B23C4D) instead of microsecond-precision (0xA1B2C3D4)
    — a long-established pcap variant every major tool (Wireshark,
    tcpdump, tshark) already reads, and the only way the improved
    capture precision survives to disk. Replay (-r) still reads both
    precisions losslessly (#59).
  • BREAKING (library API): -i/--interface is now repeatable —
    -i eth0 -i wlan0 captures on both interfaces concurrently, merged
    into one decoded stream, each frame tagged with the interface it
    actually arrived on. Existing single-use invocations (-i eth0) are
    unaffected. Live capture is now backed by an asyncio event loop
    internally (one raw socket per requested interface, multiplexed via
    add_reader); the public rootwire.capture.capture() sync generator
    is replaced by rootwire.capture.capture_async(), an async
    generator with a different signature — a break only for code
    embedding RootWire as a library and calling capture() directly,
    not for any CLI usage (#61).

Added

  • --filter now also accepts arbitrary filter expressions
    (tcp and port 80, host 1.2.3.4, not arp, ...), compiled to cBPF
    by a small hand-rolled compiler — no libpcap dependency, keeping
    RootWire's zero-dependency, from-scratch posture. Supported grammar:
    protocols tcp/udp/icmp/arp/ip/ip6; host/port, each
    optionally prefixed with src/dst; and/or/not; parentheses
    — anything outside that grammar is a clear compile-time error, never
    a best-effort guess. Correctness is validated by interpreting both
    this compiler's output and real tcpdump -dd bytecode against the
    project's captured-frame corpus and asserting they always agree on
    accept/reject for every frame — not by matching tcpdump's exact
    instruction sequence, which its own decades-old peephole optimizer
    makes impractical to reproduce by hand past a single bare primitive
    (#63).
  • --filter {tcp,udp,arp,ip6}: attach a kernel-side classic-BPF
    (cBPF) filter to the capture socket (SO_ATTACH_FILTER), the same
    mechanism tcpdump itself uses, so non-matching frames are dropped
    in the kernel and never copied to userspace. This release ships a
    small, canned set of pre-compiled programs, each verified
    byte-for-byte against tcpdump -dd <expression>. Mutually exclusive
    with -r — replay has no socket to attach a kernel filter to (#62).
  • Diagnose IPv4 frames whose total_length is smaller than the header
    itself — a length field that cannot be correct. The frame is flagged
    [!] Malformed on screen, carries a malformed_length field in NDJSON
    output, and counts toward the statistics malformed tally; upper layers
    are still decoded and shown. A total_length of 0 is exempt, being the
    large-send offload (TSO) sentinel found in locally captured frames
    rather than corruption (#56).
  • Handle SIGTERM as a clean shutdown, the same as Ctrl-C: flush every
    output, print the stats summary, and exit 0. Previously only SIGINT
    was handled — a service manager's stop (or plain kill) hit Python's
    default disposition, which terminates immediately and skips output
    flushing entirely, losing the tail of a -w capture and the run
    summary (#60).

Security

  • Payload display (-d) now neutralizes terminal control characters.
    Packet payloads are attacker-controlled, and the previous rendering
    passed ESC and other control bytes straight to the terminal, allowing a
    crafted frame to inject ANSI escape sequences. Non-printable characters
    (C0/C1 controls, DEL, and Unicode format characters such as the
    bidirectional overrides) are now shown as visible \xNN/\uXXXX
    escapes; printable text and newlines are unchanged (#53).

Fixed

  • Refuse -r FILE -w FILE when both refer to the same file. The writer
    truncated its target before the lazy replay reader had read a byte, so
    replaying and writing the same path destroyed the capture; it is now
    rejected up front, before anything is opened (#52).
  • A -w target that cannot be opened (bad directory, permission denied)
    now reports a clear write-specific error instead of a raw traceback,
    and no longer risks being misreported as a capture-privilege problem
    (#54).
  • The end-of-run statistics summary is no longer printed while an
    unexpected exception is propagating, where it made a crash look like a
    clean run (#54).