The eBPF-based network flow capture for Linux network interfaces. Captures network flows directly in the kernel using eBPF and exports them as structured logs. Works with any network interface - ethernet, VPN tunnels (WireGuard, OpenVPN), bridges, VLANs, etc.
- Kernel-space capture - Runs in the kernel, avoiding userspace packet drops even under high connection rates
- Efficient aggregation - Updates flow counters in-memory (hash map) rather than capturing every packet
- Periodic snapshots - Exports all flows at regular intervals (default 10s) and removes them from the map; new packets recreate the flow with fresh timestamps
- Smart flow lifecycle - Detects TCP connection close (
FIN/RST) and inactive flows (default 60s timeout) - 64-bit counters - No wraparound issues on high-volume traffic
- Drop counters - Tracks skipped packets (IP fragments, non-IPv4, parse errors) per CPU with zero contention
- Minimal overhead - Per-packet work is limited to header linearization, L3/L4 parsing, and a hash map update
Collected metrics per flow:
- Source/destination IP and port
- Protocol (TCP/UDP/other)
- Packet count
- Byte count (
skb->lenas reported at the TC layer; compare with packet-capture or interface counters only after validating the interface-specific L2/L3 semantics) - Duration (time between first and last packet in the flow record; flows with a single packet have
duration = 0becausefirst_seen == last_seen— this is expected for DNS responses, ICMP pings, TCP ACKs, and other single-packet flows, and is standard behavior across NetFlow/IPFIX exporters) - TCP flags (
SYN,FIN,RST, etc.)
Use cases:
- Real-time bandwidth monitoring per connection
- Detecting high connection rates or traffic spikes
- VPN tunnel traffic analysis (WireGuard, OpenVPN, IPsec)
- Network flow analysis without full packet capture overhead
- Feeding flow data to monitoring systems (Prometheus, Loki, InfluxDB, etc.)
See the Architecture diagram for a visual overview of the packet processing and export flow.
-
Attaches an eBPF program to the network interface using TCX (TC eXpress) hook on both ingress and egress
Note: TCX (TC eXpress) is a modern Linux kernel API introduced in kernel 6.6 for attaching eBPF programs to network interfaces at the Traffic Control (TC) layer. Compared to the classic TC hook (via
tcnetlink), TCX uses a dedicatedbpf_link-based attachment model that is more robust, supports ordering of multiple programs on the same hook, and integrates cleanly with the eBPF link lifecycle (auto-detach on process exit). -
For each packet (incoming and outgoing), extracts the 5-tuple (src IP, dst IP, src port, dst port, protocol)
-
Skips IP fragments to prevent incorrect flow identification
-
Updates flow statistics (packet count, byte count, timestamps, TCP flags) in a kernel hash map (an eBPF
BPF_MAP_TYPE_LRU_HASH(Least Recently Used) — a key/value store living in kernel memory, shared between the eBPF program and the Go userspace process) -
Every interval (default 10s), exports and deletes scanned flows from the map. At most
max-export-per-cycleflows are exported per cycle; remaining flows stay in the map until the next cycle. Each exported flow is classified for metrics purposes:- Active: Had recent packets within the timeout window
- Inactive: No packets for
timeoutseconds - Closed: TCP connection ended (
FIN/RSTseen)
-
Long-lived connections generate multiple records over time for real-time throughput visibility
-
Rate limiting: Configurable maximum flows exported per cycle (default 10,000) to prevent log flooding
flowcap uses the export interval as the active export cadence. On each cycle, it scans the flow map, exports up to max-export-per-cycle records, and deletes exported records. Packets for the same 5-tuple after deletion create a fresh flow entry.
timeout is a classification threshold for flows that are still present when scanned. In normal low-volume operation most exported records are active snapshots; inactive records appear when a flow stayed in the map long enough to exceed the timeout, commonly because export rate limiting left it for a later cycle.
closed records are TCP flows whose accumulated flags include FIN or RST. closed takes precedence over inactive for the exported metric label.
Flowcap records bytes from skb->len in the TC hook. That is the kernel packet length visible to the eBPF program at the attachment point, not a normalized application payload length.
Validated with a 1000-byte UDP payload:
lo:tcpdumpreported IPv4length 1028(20 IP + 8 UDP + 1000 payload), while Flowcap reportedbytes=2084 packets=2, or1042bytes per packet.wlp0s20f3Ethernet/Wi-Fi path:tcpdumpreported IPv4length 1028, while Flowcap reportedbytes=1042 packets=1.tun0L3 tunnel:tcpdumpreported IPv4length 1028for a 1000-byte ICMP payload, while Flowcap reportedbytes=1028 packets=1.wg0WireGuard tunnel:tcpdumpreported IPv4length 1028for a 1000-byte ICMP payload, while Flowcap reportedbytes=1028 packets=1for both request and reply records.
On loopback and the tested Ethernet/Wi-Fi path, Flowcap's TC-layer skb->len included a 14-byte link-layer header relative to tcpdump's IPv4 length. On the tested TUN and WireGuard interfaces, Flowcap matched tcpdump's IPv4 length exactly.
Do not assume it is directly comparable to NIC counters, tcpdump summaries, or another flow exporter without checking whether that tool includes link-layer overhead.
-
Linux kernel 6.6+ - Requires kernel 6.6 or newer at runtime due to TCX (TC eXpress) hook API
-
IPv4 only - IPv6 packets are silently skipped
-
Race window - Small window (~microseconds) between reading and deleting a flow; packets arriving in that window are captured in a new flow entry
-
Flow capacity - When map is full (default 16,384, max 262,144), LRU evicts least recently used flows without exporting them
-
IP fragmentation - All fragmented IP packets are skipped; only complete packets with full TCP/UDP headers are processed (counted via drop counters)
Note: This is a standard approach in flow capture tools. Only the first fragment contains TCP/UDP port headers — subsequent fragments cannot be matched to a flow without reassembly, which is impractical in eBPF. In practice, TCP is almost never fragmented (MSS negotiation, PMTU discovery) and UDP fragmentation is rare on modern networks (MTU 1500+).
-
Approximate flow count under rate limiting - The
flowcap_export_scan_flowsgauge reports flows observed during the last export scan, not the post-export map size. It is approximate because the eBPF program may concurrently insert or update flows while the Go exporter iterates the map. -
Interface state - Can attach to DOWN interfaces (captures start when interface comes UP); warning is logged at startup
-
Tunnel interfaces - Flowcap captures traffic as seen by the specified interface. For VPN tunnels (
tun0,wg0), this means decapsulated inner traffic; for physical interfaces (eth0), this means encrypted outer traffic
Requires:
Go 1.26+- for building the userspace programclang/LLVM 11+- for compiling eBPF C code to BPF bytecodeLinux kernel 6.6+ headers- for eBPF/TCX kernel API definitions
sudo apt install clang llvm linux-headers-$(uname -r) linux-libc-dev libc6-dev libbpf-devmakeThis compiles eBPF C code into Go bindings (via bpf2go) and builds the binary into bin/flowcap.
Run make help to list all available targets:
make generate # regenerate eBPF Go bindings via bpf2go
make tidy # sync Go module dependencies
make update-patch # update dependencies (patch only, safe)
make update-minor # update dependencies (minor + patch)
make fmt # format Go sources
make test # regenerate eBPF bindings and run all tests
make build # build the flowcap binary with version metadata
make build-release # build Linux amd64/arm64 release archives and checksums
make run # build and run flowcap (override with ARGS="...")
make clean # remove build artifactsNote:
make generatewrites its output (flow_bpfel.go,flow_bpfeb.goand the embeddedflow_bpf*.oobjects) into the project root, notbin/. These are generated build inputs: the.gofiles are part ofpackage main, sit next tomain.go, and are committed. The.oobjects are pulled in via relative//go:embed, ignored by git, and removed bymake clean.
The build embeds version metadata from git:
./bin/flowcap --versionv0.1.5 revision=2c73106 build_date=2026-05-17T07:45:32Z
Error: 'asm/types.h' file not found
Note: This happens because clang with BPF target looks for headers in
/usr/include/asm/, but Debian/Ubuntu stores them in/usr/include/x86_64-linux-gnu/asm/(multiarch layout). Thelinux-libc-devpackage should create a symlink between them, but on fresh installs or after upgrades the symlink is sometimes missing.
If you see this error during build, reinstall linux-libc-dev:
sudo apt install --reinstall linux-libc-devThis ensures the /usr/include/asm symlink is properly created.
If the symlink is still missing after reinstall, create it manually:
sudo ln -sf /usr/include/x86_64-linux-gnu/asm /usr/include/asmsudo ./bin/flowcap wg0Captures flows on wg0 and exports every 10 seconds to stdout.
sudo ./bin/flowcap [options] <interface>
Options:
-interval int
flow export interval in seconds (default 10, max 3600)
-timeout int
flow inactivity timeout in seconds (default 60, max 86400)
-max-flows int
maximum number of concurrent flows (default 16384, min 1024, max 262144)
-max-export-per-cycle int
maximum flows to export per cycle (default 10000, max equals max-flows)
-json
output in JSON format
-metrics-addr string
enable Prometheus metrics HTTP server at host:port (e.g. 127.0.0.1:9090)
-stats-file string
optional file for detailed statistics logging
--version
Print version and exit# Default settings (10s export interval, 60s inactivity timeout, 16384 max flows)
sudo ./bin/flowcap eth0
# Export every 5 seconds, 30s inactivity timeout
sudo ./bin/flowcap -interval 5 -timeout 30 wg0
# High-traffic interface with more flow capacity
sudo ./bin/flowcap -max-flows 131072 eth0
# High-traffic interface with higher export rate limit
sudo ./bin/flowcap -max-export-per-cycle 20000 eth0
# JSON output for log collectors (Promtail, Filebeat, etc.)
sudo ./bin/flowcap -json wg0 | tee -a /var/log/flows.json
# With statistics file for detailed logging
sudo ./bin/flowcap -stats-file /var/log/flowcap-stats.log wg0
# Enable Prometheus metrics endpoint
sudo ./bin/flowcap -metrics-addr 127.0.0.1:9090 wg0
# Print build version and exit
./bin/flowcap --version
# Export every minute for low-traffic interfaces
sudo ./bin/flowcap -interval 60 -timeout 300 tun0At startup, flowcap logs the embedded version metadata together with the selected interface and runtime options.
Text format is intended for human consumption. For machine parsing and monitoring integrations, use -json which provides fixed numeric fields.
Text (default):
<src_ip>:<src_port> -> <dst_ip>:<dst_port> proto=<protocol> packets=<count> bytes=<count> duration=<duration> flags=<tcp_flags>
JSON (-json flag):
{
"timestamp": 1709132400,
"src_ip": "192.168.1.10",
"src_port": 45678,
"dst_ip": "10.0.0.5",
"dst_port": 22,
"protocol": 6,
"packets": 50,
"bytes": 4096,
"duration_ns": 10000000000,
"duration_sec": 10.0,
"tcp_flags": "0x18"
}Use -stats-file to log per-cycle statistics to a separate file. The format follows the -json flag.
Text (default):
sudo ./bin/flowcap -stats-file /var/log/flowcap-stats.log wg0[2026-02-28 16:08:00] Exported: 150 active, 5 inactive, 3 closed | Total flows: 1523 | Bytes: 524288 | Packets: 4096 | Drops: fragments=0 non_ipv4=12 parse_err=0 linearize=0 map_full=0
[2026-02-28 16:08:10] Exported: 148 active, 2 inactive, 1 closed | Total flows: 1520 | Bytes: 412032 | Packets: 3200 | Drops: fragments=0 non_ipv4=8 parse_err=0 linearize=0 map_full=0
JSON (-json flag):
sudo ./bin/flowcap -json -stats-file /var/log/flowcap-stats.log wg0{"timestamp":1709132400,"active":150,"inactive":5,"closed":3,"total_flows":1523,"total_bytes":524288,"total_packets":4096,"drop_fragments":0,"drop_non_ipv4":12,"drop_parse_err":0,"drop_linearize":0,"drop_map_full":0}Systemd service unit, environment file configuration, and logrotate setup are documented in docs/deployment.md.
Prometheus metrics (with raw output example), Grafana queries, log collector configs (Promtail, Filebeat), and backend comparison are documented in docs/monitoring.md.
For a detailed architecture diagram, flow storage internals, eBPF program description, and drop counter documentation, see docs/architecture.md.
For a detailed comparison with other network monitoring tools (softflowd, Cilium Hubble, ntopng, tcpdump, Packetbeat, AWS VPC Flow Logs), see docs/comparison.md.