A framework for secure and scalable network traffic analysis - https://netcap.io
Go
1,807
2,202 commits
updated Oct 9, 2026
Netcap (NETwork CAPture) converts network packets into structured, type-safe Protocol Buffer audit records — designed for security monitoring, forensic analysis, and machine learning. A single Go binary with 83 packet decoders, 40+ stream decoders, and 141+ audit record types, backed by a concurrent architecture and a built-in web UI.
Protocol hierarchy visualization in the Netcap web UI — more screenshots
Built-in React (Vite + TypeScript) dashboard in service mode with interactive visualizations:
See the Gallery for screenshots.
IsApprovedWorkstation), and time-of-day helpers (IsBusinessHours, HourOfDay)c2.beacon) detection; signed producer–consumer byte ratio per connection. See Hunting evidenceinternal/rules/examples/s7comm_hunt.yml-geoProviders dbip,geolite2 (default order)
or the Databases page; see GeoIP configuration.MatchesPattern (up to ~6× on miss-heavy detection traffic), see docs/hyperscan.mdAgents stream audit records to a collection server over mutually authenticated TLS 1.3, with pinned keys, acknowledged delivery and reconnects. See docs/distributed-collection.md.
Pre-built binaries are available on the Releases page. To build from source:
# Build (requires libpcap)
go build -o net ./cmd/net/
# Build without DPI (fewer C dependencies)
go build -tags=nodpi -o net ./cmd/net/
# Build with Hyperscan / Vectorscan acceleration for service probes
# (requires libhs via pkg-config; e.g. `brew install vectorscan` on macOS)
# See docs/hyperscan.md for details.
CGO_ENABLED=1 go build -tags hyperscan -o net ./cmd/net/
# Capture from PCAP file
./net capture -read traffic.pcap
# Live capture
sudo ./net capture -iface en0
# Service mode (starts web UI)
./net capture -read traffic.pcap --service
# Service mode with hot reload (development)
air
net capture enables capture-time network detections independently of baseline
approval. -network-detection=false disables them;
-network-detection-config policy.json loads bounded JSON thresholds, authorized
source IPs/CIDRs and versioned indicators (internal/networkdetect/config.go).
| Finding | Evidence / interpretation |
|---|---|
| DNS DGA and tunneling | Distinct randomized apex names or high-entropy TXT labels beneath one parent, capture-time counts and sampled names; behavioral suspicion |
| TCP scans and SMTP fan-out | Distinct destination endpoints or mail hosts; retries do not increase cardinality |
Periodic connections (c2.beacon) | Latest 8 TCP connection starts from one source to one service, mean interval ≥ 10 s, coefficient of variation ≤ 0.1 within 1 h; retransmitted SYNs ignored. Updates and monitoring are also periodic |
| ICMP tunneling | Repeated large, high-entropy echo requests; diagnostic traffic can look similar |
| SSH transfer and unusual ports | SSH banners, unique contiguous client bytes and service port; encrypted contents and authorization remain unknown |
| Stratum, IRC, OAST and Telegram | Protocol/destination observations; legitimate use is possible |
| C2, sinkhole and imposter | Configured domain/IP matches with source, version and capture-time validity; no live feed is bundled |
| Legacy cleartext-service ports | Payload not recognized as TLS/SSH on a legacy service port; port alone does not identify the application or prove credentials were sent |
| Analyst UI | /alerts exposes classification, measured values, scope, limits and original evidence export; /api/network-detection exposes coverage, overflow, skipped late events and stream gaps |
Defaults: 60 s windows, 300 s alert deduplication, 10 distinct DNS names/TCP endpoints/large echoes, 5 SMTP hosts and 10 MiB SSH client payload. State retains at most 4,096 keys, 128 values per window and 1,024 flows with 4 KiB prefixes per direction. A stream gap stops signature/transfer claims for that direction and is reported. Indicator validity uses Unix nanoseconds at capture time. A missing status file or zero configured indicators is displayed as unavailable coverage.
internal/networkdetect/testdata/live/ contains 15 real FlightSim CLI captures
at revision 3709d1c6905d9527885c62f66f49a955c7b0d191, generated with Docker
--network none and local DNS/API/SSH/SFTP/IRC/Stratum fixtures. C2 intelligence
is synthetic lab data; SSH qualification uses a 1 MiB transfer and a 512 KiB
threshold. testdata/cases.json separately labels synthetic source-shape and
benign controls. Scenario names never enter the detector. beacon has no
FlightSim capture and is synthetic only.
These fields turn individual records into answers for common hunts: which resolvers are slow or failing, which remote procedures were actually called and whether they succeeded, and which hosts upload more than they download.
| Record | Fields | Purpose and semantics |
|---|---|---|
| DNS | TransactionStatus, RTT, QueryTransmissions | Find slow, failing, retried or unanswered lookups. Queries and responses are paired at capture time on client/server endpoints, DNS ID and first question name/type/class, separately per capture context and VLAN. Status is query, retransmission, answered, late (> 30 s), unsolicited or reordered (response before query in capture order; RTT unavailable). DNS over TCP is framed per connection, including messages split across or coalesced within segments. State is capped at 65,536 UDP and 256 per-TCP-flow pending queries |
| DCERPC | ContextID, InterfaceName, OperationName, FaultStatus, CommunityID | Hunt on what a remote call did, not just which interface was opened. Requests and responses inherit the interface from the connection's Bind/AlterContext context, and responses the operation of their call. A request is an attempt; the Response or Fault carries the outcome. Attribution stops at a stream gap rather than guessing |
| Connection | ProducerConsumerRatio | Spot uploads and data staging. (client − server bytes) / (client + server bytes) in [-1, 1]: 1 only sent, -1 only received, 0 balanced or no bytes |
WebUI views:
c2.beacon.The bundled DCE/RPC rules match specific operations: DCSync
(IDL_DRSGetNCChanges), SAMR account enumeration, remote service creation,
remote registry writes and remote scheduled task/job creation. After Hours Data Transfer now applies its business-hours condition. Two rules were renamed for
what they measure: Symmetric External Connection (byte symmetry, not timing)
and DNS Multi-Question Message.
go test -race ./internal/networkdetect
go test -race -tags nodpi,noyara,nomagika ./internal/collector -run '^TestFlightSimCollectorReplay$'
The collector checks replay at 1/2/4/8 workers. The lab requires a 32 MiB
tcpdump buffer and a 2 s drain; tests/flightsim-lab/run.sh rejects kernel drops
and failed simulator modules before sealing capture hashes.
Answers "what else happened in this connection or because of this lookup"
without manual joins. Links are computed from the output directory when asked,
so they work for finished and live captures and do not depend on worker count.
With -capture-evidence (default true), packet ingress records a scope ledger
in capture-manifest.json. After finalization, the linker qualifies records by
capture RunID, local sensor, capture-wide interface index and VLAN stack. PCAPNG
interface numbering is normalized across sections. DNS resolution links remain
temporal candidates, not proof that a lookup caused a connection.
| Link | Basis |
|---|---|
| Same connection | Record has the connection's Community ID and a timestamp within the Connection observation's first–last packet span. A reused 5-tuple is a different observation, and only the latest snapshot of an observation is shown |
| Resolved by DNS | Latest DNS response whose A/AAAA answer is the connection's destination, sent to the connection's client, at most -evidence-links-window before the connection started |
| Connection to answer | From a DNS response: connections from the same client to an answered address starting within the window after it |
| Alert | Rule alert whose matched record carries the Community ID, within the same span |
| Same flow | Records with the Community ID but no containing Connection observation, within ± the window |
Per-packet records (TCP, TLSRecord, PacketContext, PKTAP) and records without a computed Community ID (fallback hashes) are not linked. Network-detection alerts carry evidence rather than a matched record, so they are not linked. Known Connection observations suppress fallback flow links when no unique observation contains the target. Duplicate type/Community ID/timestamp selectors are rejected; use the exact type and ordinal instead. Each directory's index has a 64 MiB accounted storage budget as well as its record limit.
| Scope status | Meaning |
|---|---|
verified | Community ID occurred in exactly one packet scope; cross-protocol links require the same scope |
ambiguous | Community ID occurred in multiple interfaces/VLANs; all links for it are withheld |
pending | Scope ledger is not finalized; scoped claims are withheld |
overflow | The 32,768-entry scope ledger overflowed; scoped claims are withheld globally |
missing | No packet scope for this record; links are withheld. Filtered offline inputs deliberately do not claim preserved interface metadata |
legacy | No scope ledger, including captures made with -capture-evidence=false; results are explicitly labelled candidates within the directory |
| Option | Default | Purpose |
|---|---|---|
-evidence-links / NC_EVIDENCE_LINKS | true | Serve /api/evidence/related; switchable at runtime in Settings → Features |
-evidence-links-window | 1h | DNS answer → connection window and Community ID window outside connections |
-evidence-links-max-records | 1000000 | Records indexed per output directory; beyond it results say they are truncated |
-evidence-links-max-links | 500 | Related records returned per query |
The WebUI shows the timeline in alert details (for rule alerts) and in expanded
Connections rows. The API selects a record by type+ordinal, by
observationId (Connection), by type+communityId+time (Unix ns), or by
the content-bound id returned by a previous query:
curl 'http://127.0.0.1:8080/api/evidence/related?observationId=<id>'
net investigate related -read out -type HTTP -ordinal 0
Each link states its kind and basis; notes explain truncation and when no
connection contains the record. References include the audit-file fileSha256
and an id derived from type, file digest, capture RunID, scope-manifest digest
and ordinal. captureManifestSha256 binds the selected capture/scope witness.
Replacing or extending
that file invalidates its old references rather than rebinding them. Index
construction rejects files changing during the scan. Linked record buttons
open another related-evidence timeline; Back returns to the original target.
Links are not available to capture-time rules:
records are written in a different order than they are captured, so a rule could
not see the same links at every worker count.
-dns-resolution-context (NC_DNS_RESOLUTION_CONTEXT, default false) enables
an independent capture-time snapshot on Connection records. The collector pairs
UDP DNS at serialized ingress and copies context before dispatch; workers never
query mutable cross-flow state. Fields 65–67 are DNSResolvedName,
DNSResolvedAt (UTC ns) and DNSResolutionState (resolved or unobserved;
empty when disabled). A new observation retains its first packet's context,
including after later answers or cache eviction.
Only paired, successful, non-truncated, single-question IN A/AAAA responses on
UDP port 53 with an ASCII direct
matching answer name contribute. Context expires at the answer TTL, capped at
1 h, and is isolated by capture-wide interface/VLAN and client/destination IP.
TCP DNS and CNAME chains are not contributors yet. The answer cache and pending
query table each cap at 65,536 entries. unobserved is missing context, not a
verdict that no resolution occurred.
type: Connection
expression: 'DNSResolutionMatches(DNSResolutionState, DNSResolvedName, DNSResolvedAt, TimestampFirst, "example.test", 60000000000)'
DNSResolutionMatches is a pure predicate on immutable record fields: exact
DNS name comparison (case/trailing-dot normalized), nonnegative bounded age,
and resolved state. Settings → Features has a separate switch applying to
the next analysis; expanded Connections rows display the snapshot. Go/Rust
qualification checks enabled/disabled fields and rule alerts at 1/2/4/8 workers.
Settings → Features lists optional features with their flag and environment
variable. GET /api/features returns them; POST /api/features {"name":"evidence-links","enabled":false} switches one until the process exits.
Query features (evidence linking) apply to the next request; capture features
(network detections) apply to the next analysis started from the WebUI.
capture -behavior and agent -behavior enable passive, sensor/interface/VLAN-
scoped observations. Capture defaults to <output>/Behavior.json; agents use
the user-config netcap/behavior/<interface>/ directory. Select an existing
baseline explicitly with -behavior-baseline <path>.
./net capture -read traffic.pcap -out capture -behavior -http localhost:8080
./net capture -iface en0 -out live -behavior -behavior-sensor office-router
| Contract | Behavior |
|---|---|
| Discovery | ARP/DHCP/NDP device bindings, TCP SYN service edges, DNS queries/resolvers and authoritative DHCP/IPv6 router-advertisement prefixes; -behavior-prefix supplies configured CIDRs |
| Learning | Defaults to 7 days of capture-time coverage and 100 observations; -behavior-learning and -behavior-min-samples tune the criteria |
| Approval | GET /api/behavior returns the snapshot; POST /api/behavior/change takes {action, ids, reason, version} for approve, pause, resume, relearn, reset, acknowledge, approve-changes, suppress or unsuppress; acknowledgement retains reviewed fact IDs/reason without changing trust, suppression or dedup |
| Monitoring | Unknown facts remain candidates; approval creates a new baseline version. Alerts include the expected/observed fact and original baseline identity in MatchedRecord |
| Bounds | -behavior-max-facts defaults to 10,000; overflow is visible and prevents approving incomplete learning |
| Persistence | Atomic checkpoints every second and on shutdown; a single-writer file lock prevents concurrent baseline mutation |
| Alerts | Alert.ncap.gz is appended and synced per alert, readable before capture finishes; malformed history is rejected without overwriting it |
| Live API | GET /api/alerts/stream emits SSE alerts with durable-history cursors; reconnect using Last-Event-ID. At most 8 readers; slow-client write deadline 10 s; replacement/truncation produces a gap |
| Health | /api/behavior/health and Coverage and health show observed sensor/interface/VLAN scopes, admitted packets/queue occupancy/drop counts, detector/storage errors, baseline/history bytes and agent ACK/pending/rejected/dropped batches. BehaviorHealth.json (schema 1, ≤64 KiB) retains the last sample at shutdown; unsupported counters remain null. Linux packet-socket kernel deltas accumulate with their sample timestamp; libpcap kernel counters are currently unavailable |
| WebUI | /behavior provides scoped inventory, candidates, explicit baseline decisions, evidence pivots, decision history and a bounded live feed; Device/Host/Connection details show labels, services, peers, country/ASN context and prefix-relative direction through /api/behavior/asset, then link to /behavior?asset=<address>. MAC/IP joins preserve sensor/interface/VLAN scope; context is capped at 64 records / 240 KiB of encoded rows |
| Later lateral evidence | /api/behavior/records?alertId=<id> supplements the unchanged early alert with retained Connection/SMB tuple/time candidates, zero-based record index and canonical protobuf SHA-256. TCP reset/FIN/SYN-ACK observations do not establish authentication; only readable explicit SMB AuthStatus is shown. Legacy records omit sensor/interface/VLAN, so candidates are never attributed to the alert's network scope. Capture-time lag is separate from unavailable audit emission latency. Scan limits: 10,000 records / 64 MiB per file, 1 MiB per frame and 64 displayed records; missing/corrupt files and truncation are visible |
| Service jobs | --service --behavior reaches helper and embedded capture paths. A configured baseline is copied into each new session; session decisions never modify the template |
| Lateral patterns | Distinct-target SMB fan-out, independent RDP attempts, novel internal SSH edges and inferred A→B→C sequences; retransmitted SYNs reuse their flow token |
| Traffic deviations | Frozen packet/byte models learned over at least 4 windows; idle windows included; monitoring does not adapt the approved model |
| Detector policy | -behavior-window, -behavior-fanout, -behavior-rdp-attempts, -behavior-approved-source, -behavior-deny-country, -behavior-deny-asn; /behavior displays geographic/admin policies and records reviewed geographic exceptions through selected-fact suppression |
| Snapshot compatibility | Schema 3 adds labels, prefix corrections and learned DHCP leases; schema 1/2 snapshots migrate without changing approved fact identity |
| Topology and inventory | /behavior includes a scoped, capped graph, persistent asset labels and CIDR corrections; original observations and alert evidence remain available |
| Benign changes | Learned DHCP-server leases explain reassignment; explicit binding approvals support failover; unknown DHCP servers cannot disable conflict indicators |
| Maintenance | -behavior-policy <JSON> supports source-specific UTC-nanosecond maintenance intervals; exemptions expire according to capture time |
| Endpoint delivery | Agent sensors default to their certificate fingerprint; alerts are synced locally before entering the bounded distributed queue |
| Corrupt baseline recovery | Startup rejects corrupt snapshots without overwriting them. Stop capture, retain the damaged file, restore a known-good approved snapshot to a separate writable path, and select it with -behavior-baseline; without a trusted backup, select a new path and explicitly relearn |
The synthetic packet-to-SSE gate (TestBehavioralPacketToSSELatency, 100 samples)
measured p95 51.85 ms including decode on Darwin ARM64 / Apple M5 Max, Go 1.27.0,
at 19.99 synthetic packets/s. TestBehavioralBrowserDelivery measured receipt to
React-render p95 99.10 ms in Chrome 154.0.8037.98, with 1,000 approved facts,
100 decisive SYNs over 5.998 s and 58,000 background SYNs at 9,586 packets/s;
zero fact/window overflow. Browser approval, reconnect, evidence and suppression
history are asserted against the real backend. TestBehavioralCollectorPressure
processed 100,000 stable SYNs through 4 protocol workers and audit persistence
in 0.406 s (246,310 packets/s), with zero missing observations or state overflow.
The browser workload uses synchronous synthetic ingress; the collector workload
uses an offline PCAP. Neither measures kernel-capture drops.
Run the opt-in gates after pnpm install --frozen-lockfile && pnpm build in
cmd/capture/webui/frontend; the browser gate also requires Google Chrome:
NETCAP_BEHAVIOR_BROWSER=1 go test -race ./cmd/capture/webui -run '^TestBehavioralBrowserDelivery$' -count=1 -timeout 120s
NETCAP_BEHAVIOR_PRESSURE=1 go test ./internal/collector -run '^TestBehavioralCollectorPressure$' -count=1 -timeout 90s
PCAP oracles in behavior_replay_test.go cover
discovery/DNS, rates, DHCP transitions, IPv6 conflicts, geography and lateral
patterns with equivalent evidence at 1/2/4/8 workers. These scoped
results do not establish latency on arbitrary traffic or hardware.
Endpoint visibility is limited to traffic at the selected interface; encrypted
SSH/RDP connection patterns do not establish authentication failures.
BenchmarkBaselineDurableStorage compares atomic JSON checkpoints with SQLite
WAL / synchronous=FULL on the same Mac (100 iterations, 2026-10-06):
| Facts | JSON checkpoint | SQLite full-state checkpoint | SQLite one-row commit |
|---|---|---|---|
| 1,000 | 5.003 ms | 0.650 ms | 0.051 ms |
| 10,000 | 10.987 ms | 6.473 ms | 0.033 ms |
Live state remains bounded in memory with portable JSON checkpoints every 1 s;
the measured 10,000-fact checkpoint costs 10.987 ms. The SQLite row result is a
lower bound for one observation, not a complete lifecycle/state-store port.
Alerts remain separately synced per event. TestCorruptBaselineRecoveryFromValidatedTemplate
verifies recovery preserves approval and both the damaged file and backup;
cmd/agent/behavior_test.go verifies TLS delivery of identical retained alert
evidence after a collector outage and local-engine restart.
internal/behavior/testdata/qualification/ pins fixture contract 1: schema-3
approved snapshots, monitoring PCAPs, exact detector/endpoint/evidence oracles
and SHA-256 manifests for discovery/DNS, address changes, lateral patterns,
rates and geography. The geography case includes synthetic DB-IP MMDBs.
NETCAP_BEHAVIOR_EXPORT=<absolute fresh directory> plus
NETCAP_BEHAVIOR_REFERENCE=<Go commit> exports the same corpus while running
go test ./internal/collector -run 'TestBehavioral.*(Oracle|WorkerCounts)$' -count=1.
Export refuses an existing case directory. Decision timestamps are generated at
approval; consumers verify the committed bytes against each manifest.
| Command | Description |
|---|---|
capture | Capture audit records from live interfaces or PCAP files; --service enables the web UI |
dump | Read and display audit record files in CSV, JSON, or table format |
label | Apply attack labels to audit records using Suricata or CSV mappings |
collect | Collection server for receiving data from distributed agents |
agent | Sensor agent for distributed capture on remote hosts |
proxy | HTTP/HTTPS reverse proxy with MITM traffic inspection |
export | Export audit records with Prometheus metrics exposure |
transform | Maltego OSINT transform plugin |
util | Utilities: timestamp conversion, interface listing, database generation, search indexing |
inject | Inline packet manipulation via NFQueue (Linux) |
investigate | Bounded flow rankings and packet-evidence ZIP exports |
split | Split audit record files |
net investigate flows --read Connection.ncap.gz \
--start-ns "$START_NS" --end-ns "$END_NS" \
--filter 'InSubnet(SrcIP, "192.0.2.0/24")' --group-by srcIP
net investigate packet-evidence --read exercise.pcapng \
--bpf 'host 192.0.2.1 and tcp port 80' --out evidence.zip
net investigate collect-flows --listen 127.0.0.1:2055 --out ./fresh-output --duration 1m
net investigate exported-flows --read ./fresh-output/FlowExports.jsonl \
--exporter 192.0.2.10:50000 --format netflow-v9 --domain 7 \
--start-ns "$START_NS" --end-ns "$END_NS" --time-basis receive
START_NS and END_NS are inclusive UTC nanosecond integers. The WebUI API
exposes the same flow engine at GET /api/flows/query with startNs, endNs,
filter, groupBy, sortBy and limit. Connection downloads accept
format=evidence, a TCP/UDP protocol, and optional paired time bounds.
flows --window-mode overlap|contained|start|end selects interval semantics.
Optional --bucket-ns emits uniformly interpolated byte/rate estimates, capped
at 4,096 bins; raw group counters remain whole-observation counts. Reports also
include byte shares and nearest-rank size/packet/duration distributions. The
Investigation Evidence page exposes both controls and labels estimated values.
| Output | Interpretation |
|---|---|
| Flow report | ObservationID and SnapshotSequence reconcile cumulative records; counters cover whole observations overlapping the window. Rates divide bytes by summed observation durations. |
| Legacy records | Repeated tuples without snapshot semantics fail as ambiguous; no guessed deduplication or session count. |
evidence.zip | packets.pcapng plus manifest.json: source/output SHA-256, source packet ranges, interface mapping, truncation counts and selection. Packet options, secrets and source statistics remain in the original capture. |
| File records | CompletenessReason reports extraction/decoding errors and observed stream loss. StreamMissingBytes is not loss attributed to the extracted file. |
Exports refuse replacement and fail on truncation or exceeded limits. Packet
exports cap captured packets at 1 MiB and PCAPNG blocks/metadata at 16 MiB;
flow queries cap records at 4 MiB and decoded input at 256 MiB.
The 38 book-derived workflow families are qualified in
testdata/investigation-coverage.json, with exact fixture references and mandatory
integration commands. Coverage is scoped to those fixtures and declared limits;
external endpoint, proxy and organizational evidence remains explicitly external.
capture --flow-exports normalizes UDP NetFlow v5/v9, IPFIX and sFlow v5 on
--flow-export-ports (default 2055,4739,6343,9995,9996). It records raw
datagrams, normalized observations and issues in FlowExports.jsonl before
worker dispatch. FlowExportsHealth.json binds final health to the record-file
SHA-256. Existing flow artifacts are refused; use a fresh output directory.
| Export interpretation | Contract |
|---|---|
| Templates | Collector-owned, exporter/collector/domain scoped; 30-minute expiry, 128 domains, 1,024 templates, 256 fields/template. |
| Sampling | Counts remain as reported; no automatic expansion. Reports reject mixed sampling settings. |
| Time | flow requires exported start/end; receive selects datagram capture/receipt time. sFlow does not establish flow start/end. |
| Gaps | Missing templates, malformed data and sequence discontinuities produce issue events and partial health. |
| Ranking | Requires exporter, format and domain. Cumulative counts and incomplete counters are excluded and counted explicitly. |
| Coverage | sFlow counter/unknown sample formats remain in raw datagrams; unsupported options scopes are not applied globally. |
capture --conns also writes stream-evidence/stream-*/{client.bin,server.bin,manifest.json}.
These bytes contain no ANSI markup. The manifest preserves per-direction offsets,
capture times, TCP gaps and UDP datagram boundaries. A gapped stream must not be
parsed as contiguous data; the legacy display transcript remains separate.
capture --capture-evidence writes capture-manifest.json with input/configuration
hashes, ingress/admission/drop counters and nullable kernel statistics. Add
--retain-packets --packet-segment-mb 32 --packet-retention-mb 512 for rotating
PCAPNG retention. Expired segments remain recorded as tombstones. A fresh output
directory is required; retention failure is an error, not successful collection.
The Investigation evidence page (/investigation-evidence) displays this
coverage, scoped flow reports and directional stream downloads.
net investigate exchange --spec exchange.json executes one bounded TCP/UDP
experiment. Byte fields use JSON base64; Framing supports length-prefix,
delimiter and fixed. Response variables capture fresh negotiation values.
TLS verifies certificates and hostnames; optional rootCAFile and paired
clientCertificateFile/clientKeyFile configure experiment trust and mTLS.
net investigate protocol-fields --spec grammar.json --read client.bin --compare other-client.bin
validates framed streams against declared byte/UTF-8/signed/unsigned fields and
reports changes by frame ordinal and field name. Fields include offsets, raw
bytes and hypothesis descriptions. Frame insertions can shift alignment; a
successful parse does not prove the hypothesized application semantics.
net investigate tls-capture --read tls.pcapng --key-log secrets.log --stream 0
uses installed tshark for offline TLS dissection. Output binds input, key-log
and plaintext hashes to the tool version; key material is not included. Node
ordering follows tshark's reported endpoints, not an assumed client role.
NETCAP_REQUIRE_TLS_ADAPTER=1 go test ./internal/protocoltest -run TestTLSCaptureAdapter
requires the TLS 1.2/1.3 adapter fixtures instead of skipping when tshark is absent.
net investigate protocol-proxy --spec proxy.json --listen 127.0.0.1:9000
handles one framed TCP connection. Mutations select direction/frame and byte
range, with optional length-prefix repair, drop, duplication and delay. Output
records original/transmitted bytes. Both tools enforce time/frame/byte limits;
response matches are protocol observations, not proof of endpoint effects.
| Investigation command | Qualified workflow |
|---|---|
protocol-server | Bounded TCP/TLS or single-peer UDP server emulation |
protocol-access | Role/state/resource matrix with observed reset controls |
protocol-generate, protocol-corpus | Deterministic bounded grammar/field/state/byte cases |
protocol-fuzz, protocol-reproduce | Valid controls, minimized marker-positive cases and exact reproduction |
protocol-boundary | Target rejection separated from harness timeout/budget-stop |
protocol-triage | Hashed debugger/process/routing evidence import; impact remains unverified |
sensor-seal, sensor-import, sensor-prune | HMAC-bound output bundles, receiver-authorized sensor namespaces and expiry; transport remains caller-owned |
Use each command's --help for the versioned JSON/input contract. FTP data
extraction waits for control/data readers to drain; FTPDataHealth.json records
associations and exclusions. TCPReassemblyHealth.json records checksum policy
and rejection counts. Both sidecars are shown in the Investigation Evidence page
and retained by Pro projects.
Pre-built images are available for multiple configurations:
| Image | Description |
|---|---|
| Alpine | Minimal image with full DPI support |
| Alpine (nodpi) | Lightweight, no DPI dependencies |
| Ubuntu | Full-featured Ubuntu-based image |
| Service | Web UI service mode image |
See the docker/ directory for all Dockerfiles and build variants.
Contributions welcome — from protocol decoder additions to core framework improvements.
Development Setup:
Please use the bug report template for issue reports.
Netcap is licensed under the GNU General Public License v3, which is a very permissive open source license, that allows others to do almost anything they want with the project, except to distribute closed source versions. This license type was chosen with Netcap's research purpose in mind, and in the hope that it leads to further improvements and new capabilities contributed by other researchers on the long term.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
233 followers · starred Sep 2026
23 followers · starred Dec 2018
42 followers · starred Dec 2018
69 followers · starred Aug 2020
A framework for secure and scalable network traffic analysis - https://netcap.io
Go
1,807
2,202 commits
updated Oct 9, 2026
Netcap (NETwork CAPture) converts network packets into structured, type-safe Protocol Buffer audit records — designed for security monitoring, forensic analysis, and machine learning. A single Go binary with 83 packet decoders, 40+ stream decoders, and 141+ audit record types, backed by a concurrent architecture and a built-in web UI.
Protocol hierarchy visualization in the Netcap web UI — more screenshots
Built-in React (Vite + TypeScript) dashboard in service mode with interactive visualizations:
See the Gallery for screenshots.
IsApprovedWorkstation), and time-of-day helpers (IsBusinessHours, HourOfDay)c2.beacon) detection; signed producer–consumer byte ratio per connection. See Hunting evidenceinternal/rules/examples/s7comm_hunt.yml-geoProviders dbip,geolite2 (default order)
or the Databases page; see GeoIP configuration.MatchesPattern (up to ~6× on miss-heavy detection traffic), see docs/hyperscan.mdAgents stream audit records to a collection server over mutually authenticated TLS 1.3, with pinned keys, acknowledged delivery and reconnects. See docs/distributed-collection.md.
Pre-built binaries are available on the Releases page. To build from source:
# Build (requires libpcap)
go build -o net ./cmd/net/
# Build without DPI (fewer C dependencies)
go build -tags=nodpi -o net ./cmd/net/
# Build with Hyperscan / Vectorscan acceleration for service probes
# (requires libhs via pkg-config; e.g. `brew install vectorscan` on macOS)
# See docs/hyperscan.md for details.
CGO_ENABLED=1 go build -tags hyperscan -o net ./cmd/net/
# Capture from PCAP file
./net capture -read traffic.pcap
# Live capture
sudo ./net capture -iface en0
# Service mode (starts web UI)
./net capture -read traffic.pcap --service
# Service mode with hot reload (development)
air
net capture enables capture-time network detections independently of baseline
approval. -network-detection=false disables them;
-network-detection-config policy.json loads bounded JSON thresholds, authorized
source IPs/CIDRs and versioned indicators (internal/networkdetect/config.go).
| Finding | Evidence / interpretation |
|---|---|
| DNS DGA and tunneling | Distinct randomized apex names or high-entropy TXT labels beneath one parent, capture-time counts and sampled names; behavioral suspicion |
| TCP scans and SMTP fan-out | Distinct destination endpoints or mail hosts; retries do not increase cardinality |
Periodic connections (c2.beacon) | Latest 8 TCP connection starts from one source to one service, mean interval ≥ 10 s, coefficient of variation ≤ 0.1 within 1 h; retransmitted SYNs ignored. Updates and monitoring are also periodic |
| ICMP tunneling | Repeated large, high-entropy echo requests; diagnostic traffic can look similar |
| SSH transfer and unusual ports | SSH banners, unique contiguous client bytes and service port; encrypted contents and authorization remain unknown |
| Stratum, IRC, OAST and Telegram | Protocol/destination observations; legitimate use is possible |
| C2, sinkhole and imposter | Configured domain/IP matches with source, version and capture-time validity; no live feed is bundled |
| Legacy cleartext-service ports | Payload not recognized as TLS/SSH on a legacy service port; port alone does not identify the application or prove credentials were sent |
| Analyst UI | /alerts exposes classification, measured values, scope, limits and original evidence export; /api/network-detection exposes coverage, overflow, skipped late events and stream gaps |
Defaults: 60 s windows, 300 s alert deduplication, 10 distinct DNS names/TCP endpoints/large echoes, 5 SMTP hosts and 10 MiB SSH client payload. State retains at most 4,096 keys, 128 values per window and 1,024 flows with 4 KiB prefixes per direction. A stream gap stops signature/transfer claims for that direction and is reported. Indicator validity uses Unix nanoseconds at capture time. A missing status file or zero configured indicators is displayed as unavailable coverage.
internal/networkdetect/testdata/live/ contains 15 real FlightSim CLI captures
at revision 3709d1c6905d9527885c62f66f49a955c7b0d191, generated with Docker
--network none and local DNS/API/SSH/SFTP/IRC/Stratum fixtures. C2 intelligence
is synthetic lab data; SSH qualification uses a 1 MiB transfer and a 512 KiB
threshold. testdata/cases.json separately labels synthetic source-shape and
benign controls. Scenario names never enter the detector. beacon has no
FlightSim capture and is synthetic only.
These fields turn individual records into answers for common hunts: which resolvers are slow or failing, which remote procedures were actually called and whether they succeeded, and which hosts upload more than they download.
| Record | Fields | Purpose and semantics |
|---|---|---|
| DNS | TransactionStatus, RTT, QueryTransmissions | Find slow, failing, retried or unanswered lookups. Queries and responses are paired at capture time on client/server endpoints, DNS ID and first question name/type/class, separately per capture context and VLAN. Status is query, retransmission, answered, late (> 30 s), unsolicited or reordered (response before query in capture order; RTT unavailable). DNS over TCP is framed per connection, including messages split across or coalesced within segments. State is capped at 65,536 UDP and 256 per-TCP-flow pending queries |
| DCERPC | ContextID, InterfaceName, OperationName, FaultStatus, CommunityID | Hunt on what a remote call did, not just which interface was opened. Requests and responses inherit the interface from the connection's Bind/AlterContext context, and responses the operation of their call. A request is an attempt; the Response or Fault carries the outcome. Attribution stops at a stream gap rather than guessing |
| Connection | ProducerConsumerRatio | Spot uploads and data staging. (client − server bytes) / (client + server bytes) in [-1, 1]: 1 only sent, -1 only received, 0 balanced or no bytes |
WebUI views:
c2.beacon.The bundled DCE/RPC rules match specific operations: DCSync
(IDL_DRSGetNCChanges), SAMR account enumeration, remote service creation,
remote registry writes and remote scheduled task/job creation. After Hours Data Transfer now applies its business-hours condition. Two rules were renamed for
what they measure: Symmetric External Connection (byte symmetry, not timing)
and DNS Multi-Question Message.
go test -race ./internal/networkdetect
go test -race -tags nodpi,noyara,nomagika ./internal/collector -run '^TestFlightSimCollectorReplay$'
The collector checks replay at 1/2/4/8 workers. The lab requires a 32 MiB
tcpdump buffer and a 2 s drain; tests/flightsim-lab/run.sh rejects kernel drops
and failed simulator modules before sealing capture hashes.
Answers "what else happened in this connection or because of this lookup"
without manual joins. Links are computed from the output directory when asked,
so they work for finished and live captures and do not depend on worker count.
With -capture-evidence (default true), packet ingress records a scope ledger
in capture-manifest.json. After finalization, the linker qualifies records by
capture RunID, local sensor, capture-wide interface index and VLAN stack. PCAPNG
interface numbering is normalized across sections. DNS resolution links remain
temporal candidates, not proof that a lookup caused a connection.
| Link | Basis |
|---|---|
| Same connection | Record has the connection's Community ID and a timestamp within the Connection observation's first–last packet span. A reused 5-tuple is a different observation, and only the latest snapshot of an observation is shown |
| Resolved by DNS | Latest DNS response whose A/AAAA answer is the connection's destination, sent to the connection's client, at most -evidence-links-window before the connection started |
| Connection to answer | From a DNS response: connections from the same client to an answered address starting within the window after it |
| Alert | Rule alert whose matched record carries the Community ID, within the same span |
| Same flow | Records with the Community ID but no containing Connection observation, within ± the window |
Per-packet records (TCP, TLSRecord, PacketContext, PKTAP) and records without a computed Community ID (fallback hashes) are not linked. Network-detection alerts carry evidence rather than a matched record, so they are not linked. Known Connection observations suppress fallback flow links when no unique observation contains the target. Duplicate type/Community ID/timestamp selectors are rejected; use the exact type and ordinal instead. Each directory's index has a 64 MiB accounted storage budget as well as its record limit.
| Scope status | Meaning |
|---|---|
verified | Community ID occurred in exactly one packet scope; cross-protocol links require the same scope |
ambiguous | Community ID occurred in multiple interfaces/VLANs; all links for it are withheld |
pending | Scope ledger is not finalized; scoped claims are withheld |
overflow | The 32,768-entry scope ledger overflowed; scoped claims are withheld globally |
missing | No packet scope for this record; links are withheld. Filtered offline inputs deliberately do not claim preserved interface metadata |
legacy | No scope ledger, including captures made with -capture-evidence=false; results are explicitly labelled candidates within the directory |
| Option | Default | Purpose |
|---|---|---|
-evidence-links / NC_EVIDENCE_LINKS | true | Serve /api/evidence/related; switchable at runtime in Settings → Features |
-evidence-links-window | 1h | DNS answer → connection window and Community ID window outside connections |
-evidence-links-max-records | 1000000 | Records indexed per output directory; beyond it results say they are truncated |
-evidence-links-max-links | 500 | Related records returned per query |
The WebUI shows the timeline in alert details (for rule alerts) and in expanded
Connections rows. The API selects a record by type+ordinal, by
observationId (Connection), by type+communityId+time (Unix ns), or by
the content-bound id returned by a previous query:
curl 'http://127.0.0.1:8080/api/evidence/related?observationId=<id>'
net investigate related -read out -type HTTP -ordinal 0
Each link states its kind and basis; notes explain truncation and when no
connection contains the record. References include the audit-file fileSha256
and an id derived from type, file digest, capture RunID, scope-manifest digest
and ordinal. captureManifestSha256 binds the selected capture/scope witness.
Replacing or extending
that file invalidates its old references rather than rebinding them. Index
construction rejects files changing during the scan. Linked record buttons
open another related-evidence timeline; Back returns to the original target.
Links are not available to capture-time rules:
records are written in a different order than they are captured, so a rule could
not see the same links at every worker count.
-dns-resolution-context (NC_DNS_RESOLUTION_CONTEXT, default false) enables
an independent capture-time snapshot on Connection records. The collector pairs
UDP DNS at serialized ingress and copies context before dispatch; workers never
query mutable cross-flow state. Fields 65–67 are DNSResolvedName,
DNSResolvedAt (UTC ns) and DNSResolutionState (resolved or unobserved;
empty when disabled). A new observation retains its first packet's context,
including after later answers or cache eviction.
Only paired, successful, non-truncated, single-question IN A/AAAA responses on
UDP port 53 with an ASCII direct
matching answer name contribute. Context expires at the answer TTL, capped at
1 h, and is isolated by capture-wide interface/VLAN and client/destination IP.
TCP DNS and CNAME chains are not contributors yet. The answer cache and pending
query table each cap at 65,536 entries. unobserved is missing context, not a
verdict that no resolution occurred.
type: Connection
expression: 'DNSResolutionMatches(DNSResolutionState, DNSResolvedName, DNSResolvedAt, TimestampFirst, "example.test", 60000000000)'
DNSResolutionMatches is a pure predicate on immutable record fields: exact
DNS name comparison (case/trailing-dot normalized), nonnegative bounded age,
and resolved state. Settings → Features has a separate switch applying to
the next analysis; expanded Connections rows display the snapshot. Go/Rust
qualification checks enabled/disabled fields and rule alerts at 1/2/4/8 workers.
Settings → Features lists optional features with their flag and environment
variable. GET /api/features returns them; POST /api/features {"name":"evidence-links","enabled":false} switches one until the process exits.
Query features (evidence linking) apply to the next request; capture features
(network detections) apply to the next analysis started from the WebUI.
capture -behavior and agent -behavior enable passive, sensor/interface/VLAN-
scoped observations. Capture defaults to <output>/Behavior.json; agents use
the user-config netcap/behavior/<interface>/ directory. Select an existing
baseline explicitly with -behavior-baseline <path>.
./net capture -read traffic.pcap -out capture -behavior -http localhost:8080
./net capture -iface en0 -out live -behavior -behavior-sensor office-router
| Contract | Behavior |
|---|---|
| Discovery | ARP/DHCP/NDP device bindings, TCP SYN service edges, DNS queries/resolvers and authoritative DHCP/IPv6 router-advertisement prefixes; -behavior-prefix supplies configured CIDRs |
| Learning | Defaults to 7 days of capture-time coverage and 100 observations; -behavior-learning and -behavior-min-samples tune the criteria |
| Approval | GET /api/behavior returns the snapshot; POST /api/behavior/change takes {action, ids, reason, version} for approve, pause, resume, relearn, reset, acknowledge, approve-changes, suppress or unsuppress; acknowledgement retains reviewed fact IDs/reason without changing trust, suppression or dedup |
| Monitoring | Unknown facts remain candidates; approval creates a new baseline version. Alerts include the expected/observed fact and original baseline identity in MatchedRecord |
| Bounds | -behavior-max-facts defaults to 10,000; overflow is visible and prevents approving incomplete learning |
| Persistence | Atomic checkpoints every second and on shutdown; a single-writer file lock prevents concurrent baseline mutation |
| Alerts | Alert.ncap.gz is appended and synced per alert, readable before capture finishes; malformed history is rejected without overwriting it |
| Live API | GET /api/alerts/stream emits SSE alerts with durable-history cursors; reconnect using Last-Event-ID. At most 8 readers; slow-client write deadline 10 s; replacement/truncation produces a gap |
| Health | /api/behavior/health and Coverage and health show observed sensor/interface/VLAN scopes, admitted packets/queue occupancy/drop counts, detector/storage errors, baseline/history bytes and agent ACK/pending/rejected/dropped batches. BehaviorHealth.json (schema 1, ≤64 KiB) retains the last sample at shutdown; unsupported counters remain null. Linux packet-socket kernel deltas accumulate with their sample timestamp; libpcap kernel counters are currently unavailable |
| WebUI | /behavior provides scoped inventory, candidates, explicit baseline decisions, evidence pivots, decision history and a bounded live feed; Device/Host/Connection details show labels, services, peers, country/ASN context and prefix-relative direction through /api/behavior/asset, then link to /behavior?asset=<address>. MAC/IP joins preserve sensor/interface/VLAN scope; context is capped at 64 records / 240 KiB of encoded rows |
| Later lateral evidence | /api/behavior/records?alertId=<id> supplements the unchanged early alert with retained Connection/SMB tuple/time candidates, zero-based record index and canonical protobuf SHA-256. TCP reset/FIN/SYN-ACK observations do not establish authentication; only readable explicit SMB AuthStatus is shown. Legacy records omit sensor/interface/VLAN, so candidates are never attributed to the alert's network scope. Capture-time lag is separate from unavailable audit emission latency. Scan limits: 10,000 records / 64 MiB per file, 1 MiB per frame and 64 displayed records; missing/corrupt files and truncation are visible |
| Service jobs | --service --behavior reaches helper and embedded capture paths. A configured baseline is copied into each new session; session decisions never modify the template |
| Lateral patterns | Distinct-target SMB fan-out, independent RDP attempts, novel internal SSH edges and inferred A→B→C sequences; retransmitted SYNs reuse their flow token |
| Traffic deviations | Frozen packet/byte models learned over at least 4 windows; idle windows included; monitoring does not adapt the approved model |
| Detector policy | -behavior-window, -behavior-fanout, -behavior-rdp-attempts, -behavior-approved-source, -behavior-deny-country, -behavior-deny-asn; /behavior displays geographic/admin policies and records reviewed geographic exceptions through selected-fact suppression |
| Snapshot compatibility | Schema 3 adds labels, prefix corrections and learned DHCP leases; schema 1/2 snapshots migrate without changing approved fact identity |
| Topology and inventory | /behavior includes a scoped, capped graph, persistent asset labels and CIDR corrections; original observations and alert evidence remain available |
| Benign changes | Learned DHCP-server leases explain reassignment; explicit binding approvals support failover; unknown DHCP servers cannot disable conflict indicators |
| Maintenance | -behavior-policy <JSON> supports source-specific UTC-nanosecond maintenance intervals; exemptions expire according to capture time |
| Endpoint delivery | Agent sensors default to their certificate fingerprint; alerts are synced locally before entering the bounded distributed queue |
| Corrupt baseline recovery | Startup rejects corrupt snapshots without overwriting them. Stop capture, retain the damaged file, restore a known-good approved snapshot to a separate writable path, and select it with -behavior-baseline; without a trusted backup, select a new path and explicitly relearn |
The synthetic packet-to-SSE gate (TestBehavioralPacketToSSELatency, 100 samples)
measured p95 51.85 ms including decode on Darwin ARM64 / Apple M5 Max, Go 1.27.0,
at 19.99 synthetic packets/s. TestBehavioralBrowserDelivery measured receipt to
React-render p95 99.10 ms in Chrome 154.0.8037.98, with 1,000 approved facts,
100 decisive SYNs over 5.998 s and 58,000 background SYNs at 9,586 packets/s;
zero fact/window overflow. Browser approval, reconnect, evidence and suppression
history are asserted against the real backend. TestBehavioralCollectorPressure
processed 100,000 stable SYNs through 4 protocol workers and audit persistence
in 0.406 s (246,310 packets/s), with zero missing observations or state overflow.
The browser workload uses synchronous synthetic ingress; the collector workload
uses an offline PCAP. Neither measures kernel-capture drops.
Run the opt-in gates after pnpm install --frozen-lockfile && pnpm build in
cmd/capture/webui/frontend; the browser gate also requires Google Chrome:
NETCAP_BEHAVIOR_BROWSER=1 go test -race ./cmd/capture/webui -run '^TestBehavioralBrowserDelivery$' -count=1 -timeout 120s
NETCAP_BEHAVIOR_PRESSURE=1 go test ./internal/collector -run '^TestBehavioralCollectorPressure$' -count=1 -timeout 90s
PCAP oracles in behavior_replay_test.go cover
discovery/DNS, rates, DHCP transitions, IPv6 conflicts, geography and lateral
patterns with equivalent evidence at 1/2/4/8 workers. These scoped
results do not establish latency on arbitrary traffic or hardware.
Endpoint visibility is limited to traffic at the selected interface; encrypted
SSH/RDP connection patterns do not establish authentication failures.
BenchmarkBaselineDurableStorage compares atomic JSON checkpoints with SQLite
WAL / synchronous=FULL on the same Mac (100 iterations, 2026-10-06):
| Facts | JSON checkpoint | SQLite full-state checkpoint | SQLite one-row commit |
|---|---|---|---|
| 1,000 | 5.003 ms | 0.650 ms | 0.051 ms |
| 10,000 | 10.987 ms | 6.473 ms | 0.033 ms |
Live state remains bounded in memory with portable JSON checkpoints every 1 s;
the measured 10,000-fact checkpoint costs 10.987 ms. The SQLite row result is a
lower bound for one observation, not a complete lifecycle/state-store port.
Alerts remain separately synced per event. TestCorruptBaselineRecoveryFromValidatedTemplate
verifies recovery preserves approval and both the damaged file and backup;
cmd/agent/behavior_test.go verifies TLS delivery of identical retained alert
evidence after a collector outage and local-engine restart.
internal/behavior/testdata/qualification/ pins fixture contract 1: schema-3
approved snapshots, monitoring PCAPs, exact detector/endpoint/evidence oracles
and SHA-256 manifests for discovery/DNS, address changes, lateral patterns,
rates and geography. The geography case includes synthetic DB-IP MMDBs.
NETCAP_BEHAVIOR_EXPORT=<absolute fresh directory> plus
NETCAP_BEHAVIOR_REFERENCE=<Go commit> exports the same corpus while running
go test ./internal/collector -run 'TestBehavioral.*(Oracle|WorkerCounts)$' -count=1.
Export refuses an existing case directory. Decision timestamps are generated at
approval; consumers verify the committed bytes against each manifest.
| Command | Description |
|---|---|
capture | Capture audit records from live interfaces or PCAP files; --service enables the web UI |
dump | Read and display audit record files in CSV, JSON, or table format |
label | Apply attack labels to audit records using Suricata or CSV mappings |
collect | Collection server for receiving data from distributed agents |
agent | Sensor agent for distributed capture on remote hosts |
proxy | HTTP/HTTPS reverse proxy with MITM traffic inspection |
export | Export audit records with Prometheus metrics exposure |
transform | Maltego OSINT transform plugin |
util | Utilities: timestamp conversion, interface listing, database generation, search indexing |
inject | Inline packet manipulation via NFQueue (Linux) |
investigate | Bounded flow rankings and packet-evidence ZIP exports |
split | Split audit record files |
net investigate flows --read Connection.ncap.gz \
--start-ns "$START_NS" --end-ns "$END_NS" \
--filter 'InSubnet(SrcIP, "192.0.2.0/24")' --group-by srcIP
net investigate packet-evidence --read exercise.pcapng \
--bpf 'host 192.0.2.1 and tcp port 80' --out evidence.zip
net investigate collect-flows --listen 127.0.0.1:2055 --out ./fresh-output --duration 1m
net investigate exported-flows --read ./fresh-output/FlowExports.jsonl \
--exporter 192.0.2.10:50000 --format netflow-v9 --domain 7 \
--start-ns "$START_NS" --end-ns "$END_NS" --time-basis receive
START_NS and END_NS are inclusive UTC nanosecond integers. The WebUI API
exposes the same flow engine at GET /api/flows/query with startNs, endNs,
filter, groupBy, sortBy and limit. Connection downloads accept
format=evidence, a TCP/UDP protocol, and optional paired time bounds.
flows --window-mode overlap|contained|start|end selects interval semantics.
Optional --bucket-ns emits uniformly interpolated byte/rate estimates, capped
at 4,096 bins; raw group counters remain whole-observation counts. Reports also
include byte shares and nearest-rank size/packet/duration distributions. The
Investigation Evidence page exposes both controls and labels estimated values.
| Output | Interpretation |
|---|---|
| Flow report | ObservationID and SnapshotSequence reconcile cumulative records; counters cover whole observations overlapping the window. Rates divide bytes by summed observation durations. |
| Legacy records | Repeated tuples without snapshot semantics fail as ambiguous; no guessed deduplication or session count. |
evidence.zip | packets.pcapng plus manifest.json: source/output SHA-256, source packet ranges, interface mapping, truncation counts and selection. Packet options, secrets and source statistics remain in the original capture. |
| File records | CompletenessReason reports extraction/decoding errors and observed stream loss. StreamMissingBytes is not loss attributed to the extracted file. |
Exports refuse replacement and fail on truncation or exceeded limits. Packet
exports cap captured packets at 1 MiB and PCAPNG blocks/metadata at 16 MiB;
flow queries cap records at 4 MiB and decoded input at 256 MiB.
The 38 book-derived workflow families are qualified in
testdata/investigation-coverage.json, with exact fixture references and mandatory
integration commands. Coverage is scoped to those fixtures and declared limits;
external endpoint, proxy and organizational evidence remains explicitly external.
capture --flow-exports normalizes UDP NetFlow v5/v9, IPFIX and sFlow v5 on
--flow-export-ports (default 2055,4739,6343,9995,9996). It records raw
datagrams, normalized observations and issues in FlowExports.jsonl before
worker dispatch. FlowExportsHealth.json binds final health to the record-file
SHA-256. Existing flow artifacts are refused; use a fresh output directory.
| Export interpretation | Contract |
|---|---|
| Templates | Collector-owned, exporter/collector/domain scoped; 30-minute expiry, 128 domains, 1,024 templates, 256 fields/template. |
| Sampling | Counts remain as reported; no automatic expansion. Reports reject mixed sampling settings. |
| Time | flow requires exported start/end; receive selects datagram capture/receipt time. sFlow does not establish flow start/end. |
| Gaps | Missing templates, malformed data and sequence discontinuities produce issue events and partial health. |
| Ranking | Requires exporter, format and domain. Cumulative counts and incomplete counters are excluded and counted explicitly. |
| Coverage | sFlow counter/unknown sample formats remain in raw datagrams; unsupported options scopes are not applied globally. |
capture --conns also writes stream-evidence/stream-*/{client.bin,server.bin,manifest.json}.
These bytes contain no ANSI markup. The manifest preserves per-direction offsets,
capture times, TCP gaps and UDP datagram boundaries. A gapped stream must not be
parsed as contiguous data; the legacy display transcript remains separate.
capture --capture-evidence writes capture-manifest.json with input/configuration
hashes, ingress/admission/drop counters and nullable kernel statistics. Add
--retain-packets --packet-segment-mb 32 --packet-retention-mb 512 for rotating
PCAPNG retention. Expired segments remain recorded as tombstones. A fresh output
directory is required; retention failure is an error, not successful collection.
The Investigation evidence page (/investigation-evidence) displays this
coverage, scoped flow reports and directional stream downloads.
net investigate exchange --spec exchange.json executes one bounded TCP/UDP
experiment. Byte fields use JSON base64; Framing supports length-prefix,
delimiter and fixed. Response variables capture fresh negotiation values.
TLS verifies certificates and hostnames; optional rootCAFile and paired
clientCertificateFile/clientKeyFile configure experiment trust and mTLS.
net investigate protocol-fields --spec grammar.json --read client.bin --compare other-client.bin
validates framed streams against declared byte/UTF-8/signed/unsigned fields and
reports changes by frame ordinal and field name. Fields include offsets, raw
bytes and hypothesis descriptions. Frame insertions can shift alignment; a
successful parse does not prove the hypothesized application semantics.
net investigate tls-capture --read tls.pcapng --key-log secrets.log --stream 0
uses installed tshark for offline TLS dissection. Output binds input, key-log
and plaintext hashes to the tool version; key material is not included. Node
ordering follows tshark's reported endpoints, not an assumed client role.
NETCAP_REQUIRE_TLS_ADAPTER=1 go test ./internal/protocoltest -run TestTLSCaptureAdapter
requires the TLS 1.2/1.3 adapter fixtures instead of skipping when tshark is absent.
net investigate protocol-proxy --spec proxy.json --listen 127.0.0.1:9000
handles one framed TCP connection. Mutations select direction/frame and byte
range, with optional length-prefix repair, drop, duplication and delay. Output
records original/transmitted bytes. Both tools enforce time/frame/byte limits;
response matches are protocol observations, not proof of endpoint effects.
| Investigation command | Qualified workflow |
|---|---|
protocol-server | Bounded TCP/TLS or single-peer UDP server emulation |
protocol-access | Role/state/resource matrix with observed reset controls |
protocol-generate, protocol-corpus | Deterministic bounded grammar/field/state/byte cases |
protocol-fuzz, protocol-reproduce | Valid controls, minimized marker-positive cases and exact reproduction |
protocol-boundary | Target rejection separated from harness timeout/budget-stop |
protocol-triage | Hashed debugger/process/routing evidence import; impact remains unverified |
sensor-seal, sensor-import, sensor-prune | HMAC-bound output bundles, receiver-authorized sensor namespaces and expiry; transport remains caller-owned |
Use each command's --help for the versioned JSON/input contract. FTP data
extraction waits for control/data readers to drain; FTPDataHealth.json records
associations and exclusions. TCPReassemblyHealth.json records checksum policy
and rejection counts. Both sidecars are shown in the Investigation Evidence page
and retained by Pro projects.
Pre-built images are available for multiple configurations:
| Image | Description |
|---|---|
| Alpine | Minimal image with full DPI support |
| Alpine (nodpi) | Lightweight, no DPI dependencies |
| Ubuntu | Full-featured Ubuntu-based image |
| Service | Web UI service mode image |
See the docker/ directory for all Dockerfiles and build variants.
Contributions welcome — from protocol decoder additions to core framework improvements.
Development Setup:
Please use the bug report template for issue reports.
Netcap is licensed under the GNU General Public License v3, which is a very permissive open source license, that allows others to do almost anything they want with the project, except to distribute closed source versions. This license type was chosen with Netcap's research purpose in mind, and in the hope that it leads to further improvements and new capabilities contributed by other researchers on the long term.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
233 followers · starred Sep 2026
23 followers · starred Dec 2018
42 followers · starred Dec 2018
69 followers · starred Aug 2020