flowscope 0.10.0

Passive flow & session tracking for packet capture (runtime-free, cross-platform)
Documentation

flowscope

crates.io docs.rs CI

Passive flow & session tracking for packet capture.

flowscope is a runtime-free, cross-platform Rust library for observing what's happening on the wire. It pairs with any source of &[u8] frames: netring (Linux AF_PACKET / AF_XDP), pcap files, tun/tap, eBPF, embedded — anywhere bytes show up.

No tokio, no futures, no async runtime in the core. (For tokio integration, see netring's AsyncCapture::flow_stream etc., which consume this crate's traits.)

What's here

PacketView   →   FlowExtractor   →   FlowTracker   →   Reassembler   →   SessionParser / DatagramParser
   ↑                                                                              ↓
   anything                                                              typed L7 messages

Core (always on):

  • FlowExtractor trait + built-in extractors (5-tuple, IP-pair, MAC-pair) + decap combinators (VLAN, MPLS, VXLAN, GTP-U, GRE) + AutoDetectEncap combinator + FlowLabel IPv6 augmentation.
  • FlowTracker — bidirectional flow accounting, TCP state machine, idle timeouts, LRU eviction.
  • Reassembler — sync per-(flow, side) hook for TCP byte streams.
  • SessionParser / DatagramParser — typed L7 message parsing per flow.

Protocol parsers + analysis modules (each behind its own feature):

Feature What you get
http HTTP/1.x request/response parsing via HttpParser (SessionParser); HttpExchangeParser aggregates a request/response pair into one HttpExchange event (0.10)
tls TLS handshake observer (ClientHello/ServerHello/Alert) via TlsParser (SessionParser) — passive only, no decryption; TlsHandshakeParser aggregates a handshake into one event
ja3 JA3 client fingerprinting (sub-feature of tls)
ja4 JA4 client fingerprinting (sub-feature of tls)
dns DNS message parser, per-flow query/response correlator. UDP via DnsUdpParser (DatagramParser); TCP via DnsTcpParser (SessionParser, RFC 1035 §4.2.2 length-framed); DnsExchangeParser aggregates query+response into one DnsExchange event (0.10)
icmp ICMPv4/v6 message parser (IcmpParserDatagramParser)
pcap pcap file source for offline replay
emit flowscope::emitFlowEventCsvWriter + ZeekConnLogWriter (RFC-4180 quoting, conn.log headers) (0.10)
emit-ndjson adds FlowEventNdjsonWriter to emit; pulls in serde_json (0.10)
aggregate flowscope::aggregateHistogram + Percentile (t-digest) for SLO baselining (0.10)
l7 Umbrella: http + tls + dns + icmp
full All of the above (incl. ja3, ja4, pcap, serde, observability, emit, emit-ndjson, aggregate)

Plus always-on modules that don't need a feature flag:

  • flowscope::correlate — cross-flow correlation primitives: TimeBucketedCounter, TimeBucketedSet, KeyIndexed, BurstDetector, TopK, Ewma, SequencePattern.
  • flowscope::detectshannon_entropy, is_high_entropy, is_base64ish, is_hex_string, hamming_distance, ngram_distribution, plus detect::signatures (10 magic-byte recognizers + registry).
  • flowscope::well_known — curated (L4Proto, port) → short-label table (~70 entries) for protocol-by-port labelling.
  • flowscope::layers — zero-copy per-packet layered view (Ethernet/VLAN/MPLS/IPv4/IPv6/ARP/TCP/UDP/ICMPv4/ICMPv6/ GRE/VXLAN/GTP-U) with LayerParser + LayerStack zero-alloc fast path.

Quick start

[dependencies]
flowscope = { version = "0.10", features = ["full"] }

MSRV is Rust 1.88.

One import, one builder chain, one iterator — the high-level Pipeline entry point introduced in 0.9.0:

use flowscope::prelude::*;
use flowscope::http::HttpParser;

# fn main() -> flowscope::Result<()> {
let mut pipeline = Pipeline::builder(FiveTuple::bidirectional())
    .session(HttpParser::default())
    .build();

for event in pipeline.run_pcap("trace.pcap")? {
    if let Event::Tcp(SessionEvent::Application { message, .. }) = event? {
        println!("{message:?}");
    }
}
# Ok(()) }

Pipeline bundles the common driver setup with sensible defaults (anomalies emitted, monotonic timestamps for offline replay). For per-flow user state, custom drainers, or multiple parsers per L4, drop to FlowSessionDriver / FlowDatagramDriver directly.

Per-packet introspection

The 0.9 flowscope::layers module exposes a zero-copy view of a frame with both direct accessors and a dynamic walk:

use flowscope::PacketView;
use flowscope::layers::LayerKind;

# fn ex(pv: PacketView<'_>) -> flowscope::Result<()> {
let layers = pv.layers()?;

// Direct accessors — the common case.
if let Some(tcp) = layers.tcp() {
    println!("seq={} window={}", tcp.seq(), tcp.window());
}
if let Some(vlan) = layers.vlan() {
    println!("vid={}", vlan.vid());
}

// Dynamic walk — "show me everything".
for layer in layers.iter() {
    println!("{} ({}B)", layer.kind(), layer.bytes().len());
}
# Ok(()) }

Custom protocols

For an end-to-end example of writing a SessionParser for your own wire format — including the synchronous offline pcap path via FlowSessionDriver — see examples/length_prefixed_pcap.rs. The example demonstrates a length-prefixed binary protocol (PSMSG-shaped) with two variable-length markers and is paired with a deterministic pcap fixture under tests/fixtures/length_prefixed/.

Tokio integration

flowscope itself is runtime-free. To consume a live capture into a stream of FlowEvent / SessionEvent via tokio, use netring:

use netring::AsyncCapture;
use flowscope::extract::FiveTuple;
use flowscope::http::HttpParser;
use futures::StreamExt;

# async fn ex() -> Result<(), Box<dyn std::error::Error>> {
let mut s = AsyncCapture::open("eth0")?
    .flow_stream(FiveTuple::bidirectional())
    .session_stream(HttpParser::default());
while let Some(evt) = s.next().await { /* ... */ }
# Ok(()) }

Status

0.10.0 — Combined 0.9 + 0.10 cycle release. DX polish + structured-output cycle on top of the 0.9 surface plus the centerpiece plan 116 unified Driver<E, M> + Event<K, M>. Modules: flowscope::emit (CSV / NDJSON / Zeek conn.log writers), flowscope::aggregate (Histogram / Percentile), flowscope::detect (entropy primitives + 10-protocol signature recognizers), flowscope::well_known (curated (proto, port) → label table), correlate extensions (TimeBucketedSet / BurstDetector / TopK / Ewma), parser ergonomics (AccumulatingSessionParser / PerDatagramParser / BufferedFrameDrain), exchange aggregators (HttpExchangeParser / DnsExchangeParser), rustdoc landing pages on every L7 module + 9 new HTTP accessors, and a quick-win helper sweep across Timestamp / FlowStats / EndReason / LayerKind / Layer / LayerStack / KeyIndexed. Centerpiece — driver+event unification (Driver<E, M> + Event<K, M> replacing the 6-driver / 4-event surface) — shipped at flowscope::driver_unified::{Driver, DriverBuilder, Event, Pipeline, PipelineBuilder} alongside the legacy types for migration. The legacy types remain shipped in 0.10; deletion is queued for the next major release (plan 117).

0.9.0 (also in flight) — biggest release since 0.1: high-level Pipeline entry point, public flowscope::layers per-packet view (with tunnel walking

  • zero-alloc fast path), unified flowscope::Error, flowscope::correlate cross-flow primitives, FlowMultiSessionDriver composite driver, SegmentBufferReassembler OOO TCP hole-fill, JA4 TLS fingerprint + TlsHandshakeParser aggregator, FlowTracker::with_auto_sweep for live/offline parity, flowscope::prelude, MSRV bumped to 1.88.

0.8.0 was the last point release: serde wire-format lock, ICMP correlation helpers, programmatic flow termination, per-flow snapshot iterator, multi-protocol monitor recipe.

Core flow APIs (FlowExtractor, FlowTracker, Reassembler, SessionParser, DatagramParser) are settled; public structs and enums are #[non_exhaustive] so future variants and fields are additive. See CHANGELOG.md for the release history.

See docs/getting-started.md for a hello-world, docs/concepts.md for the conceptual model, docs/recipes.md for worked patterns, docs/observability.md for metrics + tracing, examples/README.md for a catalog of 25+ runnable examples (port-scan detection, IoC extraction, Zeek-style conn.log, TLS handshake inventory, per-packet inspection, NDJSON export, custom protocols, …), and CHANGELOG.md for the per-release feature list and migration recipes.

License

MIT OR Apache-2.0, your choice.