flowscope
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):
FlowExtractortrait + built-in extractors (5-tuple, IP-pair, MAC-pair) + decap combinators (VLAN, MPLS, VXLAN, GTP-U, GRE) +AutoDetectEncapcombinator +FlowLabelIPv6 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 |
tls |
TLS handshake observer (ClientHello/ServerHello/Alert) via TlsParser (SessionParser) — passive only, no decryption; TlsHandshakeParser aggregates a handshake into one event |
tls-fingerprints |
JA3 + JA4 client TLS 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 |
icmp |
ICMPv4/v6 message parser (IcmpParser — DatagramParser) |
pcap |
pcap file source for offline replay |
emit |
flowscope::emit — FlowEventCsvWriter + ZeekConnLogWriter (RFC-4180 quoting, conn.log headers) |
emit-ndjson |
adds FlowEventNdjsonWriter to emit; pulls in serde_json |
emit-eve |
adds EveJsonWriter — Suricata 7.x EVE JSON for Filebeat / Splunk / Tenzir / ECS pipelines (0.12) |
chrono |
Infallible From interop in both directions between Timestamp and chrono::DateTime<Utc> (0.12) |
file-hash |
flowscope::detect::file::{Sha256Sink, Md5Sink, FileType} — streaming hashes + magic-byte MIME classification (0.12) |
aggregate |
flowscope::aggregate — Histogram + Percentile (t-digest) for SLO baselining |
l7 |
Umbrella: http + tls + dns + icmp |
full |
All of the above (incl. tls-fingerprints, pcap, serde, observability, emit, emit-ndjson, emit-eve, aggregate, chrono, file-hash) |
Plus always-on modules that don't need a feature flag:
flowscope::driver— typedDriver<E>with per-parserSlotHandle<M, K>drain handles.Send + Syncsince 0.12 — drain from a tokio task on a separate thread while the driver runs on a dedicated capture thread.flowscope::KeyFields+flowscope::AnomalyFields(0.12) — structured key (src_ip/dest_port/proto_str/ …) and anomaly classification (anomaly_type/anomaly_event) accessors consumed by every emit writer (CSV / NDJSON / Zeek / EVE). Impls onFiveTupleKey+L4Proto(KeyFields) andAnomalyKind(AnomalyFields) ship out of the box; custom keys opt in.flowscope::detect::patterns(0.12) — three named detectors:BeaconDetector<K>(CV-composite, RITA-style),PortScanDetector<K>(Threshold Random Walk, Jung 2004),DgaScorer(bigram log-likelihood with embedded English baseline). Always-on; no Cargo feature gate.flowscope::detect::file(0.12,file-hashfeature) —Sha256Sink+Md5Sinkstreaming-hash sinks for reassembled payload windows, withFileTypemagic-byte classification (16 formats: PE / ELF / Mach-O / PDF / PNG / JPEG / GIF / WebP / ZIP / Gzip / Bzip2 / Xz / MP4 / MP3 / SQLite3 / Unknown). Bridges flowscope into DFIR / IR pipelines (VirusTotal hash matching, Suricatafile_md5ergonomics) without storing file bytes.- TLS ECH signal extraction (0.12) —
TlsClientHellogainsech_present/ech_config_id/sni_is_outerwhen extension0xfe0dis observed;TlsHandshakeaggregates anEchOutcome(NotOffered / Accepted / Rejected / Unknown). Outer SNI marked as cover-domain so downstream pipelines don't silently mis-attribute target identity. flowscope::correlate— cross-flow correlation primitives:TimeBucketedCounter,TimeBucketedSet,KeyIndexed,BurstDetector,TopK,Ewma,SequencePattern. All bucketed types shipnew_unboundedconvenience constructors (0.12).flowscope::detect—shannon_entropy,is_high_entropy,is_base64ish,is_hex_string,hamming_distance,ngram_distribution, plusdetect::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) withLayerParser+LayerStackzero-alloc fast path.
Quick start
[]
= { = "0.12", = ["full"] }
MSRV is Rust 1.88.
One builder chain, one typed slot handle per protocol. The
Driver<E> shape introduced in 0.11 + slot handles that are
Send + Sync since 0.12:
use ;
use ;
use ;
use PcapFlowSource;
use PacketView;
#
Per packet: driver.track_into appends flow-lifecycle events
into your events Vec; http.drain appends parsed messages
into your msgs Vec. Zero allocation at the surface in steady
state.
For per-flow user state on the central tracker, drop to
FlowDriver. For raw sync session/datagram primitives, see
FlowSessionDriver / FlowDatagramDriver. For deferred
extractor selection (consumer-built monitor chains), use
Driver::deferred() → .build_with(ext) (0.12).
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 PacketView;
use LayerKind;
#
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 AsyncCapture;
use FiveTuple;
use HttpParser;
use StreamExt;
# async
Status
0.12.0 — Cross-thread + structured-output + pre-1.0 debt retirement + named-detector / TLS-modernisation / DFIR-sinks cycle.
Headlines:
SlotHandle<M, K>isSend + Sync— pre-1.0 break. Backing storage moved fromRc<RefCell<Vec<…>>>toArc<crossbeam_queue::SegQueue<…>>(lock-free MPMC). Move handles to a tokio task, share viaArcwith multiple drainers, drain from a worker thread. Bench gate holds:track_into_5_slots: 0.000 allocs/pktin steady state.flowscope::emit::EveJsonWriterbehindemit-eve— Suricata 7.x EVE JSON for Filebeat / Splunk / Tenzir / ECS pipelines. Three event types:flow/anomaly/stats.Driver::deferred()+DeferredDriverBuilder::build_with(ext)— late extractor selection for consumer-built monitor chains. Compile-time guarantee preserved (no panickingbuild()).KeyFields/AnomalyFieldstrait split (plan 130) — pre-1.0 break. The previous monolithicAnomalyFieldstrait conflated 5-tuple key accessors with anomaly classification; split along the natural cleavage so the type system reflects what's a key property vs an anomaly property. Emit writers are now generic overK: KeyFields.flowscope::detect::patterns— three named detectors:BeaconDetector/PortScanDetector/DgaScorer. Always-on.- TLS ECH signal extraction — outer SNI flagged + ECH outcome aggregated; no silent target mis-attribution.
file-hashfeature —Sha256Sink+Md5Sink+ 16-format MIME classifier for DFIR / IR pipelines.Timestamp::write_iso8601/to_iso8601— alloc-free RFC 3339 / ISO 8601 rendering. Optionalchronofeature adds infallibleFrominterop in both directions.- Pre-1.0 feature pruning (plan 131) —
ja3+ja4collapsed intotls-fingerprints;tracing-messagesdeleted (per-message tracing is now always-on undertracing; filter at runtime viaEnvFilter). correlate::*::new_unboundedctors onTimeBucketedCounter,TimeBucketedSet,KeyIndexed.
0.11.0 — Zero-allocation cycle. Collapsed the closed-M
Driver<E, M> shape into the typed-slot-drain shape:
Driver<E> emits flow-lifecycle Event<K> only; per-parser
typed messages flow through SlotHandle<M, K> returned at
registration time. Driver::track_into with 5 HTTP slots:
0.000 allocs/packet in steady state. HTTP/1.1 GET parse:
28 → 7 allocs. Parser API break:
SessionParser/DatagramParser take &mut Vec<Self::Message>.
0.10.0 — DX polish + structured-output cycle. Modules:
flowscope::emit (CSV / NDJSON / Zeek conn.log),
flowscope::aggregate (Histogram / Percentile),
flowscope::detect (entropy + 10 signature recognizers),
flowscope::well_known (curated (proto, port) → label),
correlate extensions, parser ergonomics
(AccumulatingSessionParser / PerDatagramParser /
BufferedFrameDrain), exchange aggregators
(HttpExchangeParser / DnsExchangeParser).
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 and
docs/migration-0.11-to-0.12.md
for the 0.11 → 0.12 cheat sheet.
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, docs/eve-format.md for the
Suricata EVE schema mapping (0.12),
examples/README.md for a catalog of
runnable examples (port-scan detection, IoC extraction,
Zeek-style conn.log, EVE JSON, 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.