Skip to main content

network_core

A generic Go + Python library that extracts flow-level features from network captures. Designed to survive high-speed captures with real packet loss (including capture-side truncation and non-IP tail noise).

Author: rushi. Contributors: peter, levi.

What v0.8.0 changes

Schema bump (SchemaVersion=3). mergedFlows.csv gains five columns. Existing columns keep their positions; readers of the pre-v0.8.0 5-column schema continue to work if they use csv.DictReader or row.get(); strict-positional readers of columns [0..4] see the same values as before.

Full v0.8.0 header:

FlowId,FiveTuple,StartTime,EndTime,SNI,ALPN,DNSName,DNSDelta,DNSCandidates,SNIReason,ImplausibleTsSkipped

Column semantics

SNI

The hostname parsed from the flow's ClientHello (TLS on TCP, or QUIC v1 Initial). When the extractor could not observe a ClientHello but a prior flow with a live carry-map entry did, the inherited SNI lands here (see SNIReason=carry).

Value is nan when the extractor could not attribute a hostname — see SNIReason for the specific reason.

ALPN — CLIENT-OFFERED from THIS flow's ClientHello

The value is the FIRST-OFFERED ALPN entry from the ClientHello THIS flow parsed, or carried alongside the SNI from an earlier flow via §2 continuity carry / §3 QUIC CID linkage. nan if no CH was parsed and no carry succeeded.

  • TCP: tls_sni.ALPN[0] from the parsed CH.
  • QUIC: quic_sni.ALPN[0] from the parsed QUIC-CRYPTO-carried CH.
  • Carry: inherited from the prior flow's SNIInfo.

nan explicitly means "this flow's CH didn't parse and no carry supplied a value". It does NOT mean the client didn't offer an ALPN extension — some clients omit it entirely — but from the wire we cannot tell those cases apart.

The column is a client-side observation, not a negotiated protocol. For TLS 1.2 the server's negotiated choice is in the (cleartext) ServerHello; for TLS 1.3 it moves into EncryptedExtensions and is NOT recoverable. If you need the definitely-negotiated protocol you must decrypt the handshake — this extractor doesn't.

Behavior change vs 0.7.0 (rc6): removed the ServerHello cross-flow inference, the plaintext HTTP-version fallback, and the QUIC port-443 → h3 heuristic. All three could put a value in the column that the flow's own CH never offered. See CHANGELOG §Behavior changes for details.

DNSName / DNSDelta / DNSCandidates

Populated by the finalize step, not the Go extractor. The Go pass writes mergedFlows.csv with these columns empty and concurrently appends valid DNS observations to a temp dns_obs.csv. The Python finalize step (network_core.finalize) runs a polars join_asof at EOF, streams the enriched Flows.csv to a temp path, atomically renames it onto mergedFlows.csv, and deletes dns_obs.csv (--keep-dns-obs preserves it for debug).

Consumer contract: if you read mergedFlows.csv AFTER the finalize step (the normal case), these columns are populated. Raw Go-only output (e.g. during a live capture before finalize has run) has them EMPTY strings.

  • DNSName — the LATEST canonical A/AAAA name for this flow's server IP, answered BEFORE StartTime, from the full pcap DNS scan. If no answer exists before start, the EARLIEST answer after start (with a positive DNSDelta).
  • DNSDelta — signed seconds (answer_ts − StartTime). Negative when the answer preceded flow start (fresh). Positive when the answer arrived after (post-hoc; consumers decide whether to trust).
  • DNSCandidates — all distinct canonicals mapped to the server IP across the WHOLE pcap, |-joined, chronological.

SNIReason — one of 8 machine-friendly codes

Code Semantics
clienthello Wire ClientHello parsed (TCP or QUIC). SNI populated.
carry Inherited via observed-continuation (TCP idle-split or QUIC CID linkage). SNI populated.
no_clienthello TLS/QUIC transport observed, no CH message reached the parser.
ch_parse_failed CH bytes present but unparseable (gap, truncated, retransmit mismatch, QUIC CRYPTO gap). Specific pathology in flow_pathology[].specific_detail.
unlinkable_migration QUIC connection migration to an unseen CID (short-header-only, zero-length CID, ambiguous CID, CID-collision). Specific pathology in flow_pathology[].specific_detail.
carry_refused TCP continuation candidate refused (FIN/RST in gap, or seq-advance past hard-cap). Specific pathology in flow_pathology[].specific_detail.
unsupported_version Non-v1 QUIC (draft, v2, unknown).
not_tls Flow carries no TLS/QUIC at all (HTTP:80, DHCP, mDNS, STUN…). Empty SNI is the CORRECT value here — NOT loss.

ImplausibleTsSkipped

Per-flow count of packets dropped by the timestamp-sanity gate (implausible ts: pre-2020 or > now+24h). Non-zero flows are flagged in the diagnostics sidecar.

The diagnostics sidecar

mergedFlows.diagnostics.json sits next to mergedFlows.csv. Every key listed below is REQUIRED — consumers may reject a diagnostics blob with UN-listed keys.

  • session_start_utc, session_end_utc
  • capture_span_from_plausible_packets_s — capture length as measured by plausible-ts packets only. The yt_7 truncated tail never widens this.
  • n_pcap_records_read, n_skipped_non_ip, n_skipped_implausible_ts, n_dropped_all_implausible_flows
  • n_dns_answers_accepted, n_dns_answers_rejected_qr, n_dns_answers_rejected_source, n_dns_answers_rejected_txnid
  • n_dns_stale_seconds_hist — [0-60, 60-300, 300-3600, 3600-inf] age of the DNS answer that produced each flow's DNSName. Written by the Python finalize.
  • n_dns_post_hoc — count of flows whose ONLY DNS answer arrived after StartTime (positive DNSDelta).
  • schema_version — matches MergedFlowsSchemaVersion.
  • producer_version — the network_core binary's version string (network_core --version). Brad's oracle reads this to identify which extractor produced the artifact.
  • flow_pathology — array of per-flow finer-grained failure details. Each entry: {flow_id, reason, specific_detail, extra}. reason matches the coarse SNIReason column; specific_detail names WHICH concrete pathology hit (gap_in_clienthello, short_header_no_length_context, etc.).

CLI

network_core <pcap> <flows.csv> <packets.csv> [dns_obs.csv] [diagnostics.json]
network_core --version

Default output paths (when the last two args are omitted): <flows>.dns_obs.csv and <flows>.diagnostics.json.

Pass - as <pcap> to read a pcap or pcapng stream from stdin. Useful for piping a decompressor or a remote fetch without staging the file to disk:

zstdcat capture.pcap.zst | network_core - flows.csv packets.csv

Stdin mode is bit-for-bit equivalent to file input; the reader detects pcap vs pcapng from the first four magic bytes.

Python API

import network_core
print(network_core.__version__)  # "0.8.0"

# read a finalized mergedFlows.csv
from network_core import get_connections_from_csv
conns = get_connections_from_csv("mergedPackets.csv", "mergedFlows.csv")

CHANGELOG

See CHANGELOG.md.

Metadata

Release files for network-core 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for network-core 0.8.0
File Interpreter ABI Platform
network_core-0.8.0-py3-none-any.whl Python 3 none any Details

Release files / network_core-0.8.0-py3-none-any.whl

Download URL network_core-0.8.0-py3-none-any.whl
Size 11.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
7866da7de15fda345cdeaf20a0b830c27f81c7b9787c6223c29610af1e2c80af
BLAKE2b-256 checksum
How to use checksums
ab85057d59932847205923e641e2e3c6db5bff33104ccec97817a508c37a7acc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.11

Release history Release notifications | RSS feed

This release

0.8.0 This release

1 release file

0.7.1

1 release file

0.7.0

1 release file

0.6.9

1 release file

0.6.8

1 release file

0.6.7

1 release file

0.6.6

1 release file

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.9

2 release files

0.5.8

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

1 release file

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page