Minimal Rust + eBPF CLI that observes outbound TCP and connected-UDP connections opened by known browser processes on Linux, and prints connection metadata to the terminal.
2026-07-23T18:42:13.000000000Z brave pid=4821 TCP 192.168.1.20:53144 -> 142.250.185.78:443
2026-07-23T18:42:15.000000000Z firefox pid=5190 TCP 192.168.1.20:43922 -> 104.18.32.47:443
2026-07-23T18:42:16.000000000Z brave pid=4821 UDP 192.168.1.20:51203 -> 142.250.185.78:443
UDP lines are connected-UDP sockets: QUIC/HTTP-3 traffic (port 443) and the browser's own async DNS lookups (port 53). As with TCP/TLS, payloads are encrypted and never visible; only connection metadata is printed.
browtrace attaches kprobe/kretprobe pairs to tcp_v4_connect/tcp_v6_connect (TCP) and __ip4_datagram_connect/__ip6_datagram_connect (connected UDP, falling back to the unprefixed wrappers on kernels without the inner symbols). The entry probe stashes the kernel struct sock * for the calling thread; the return probe reads connection fields from that socket only if connect() succeeded, emits one event over a RingBuf, and always clears the stashed entry. This is the same pattern used by bcc/libbpf-tools' tcpconnect. See docs/design.md for why this was chosen over syscall tracepoints or sock:inet_sock_set_state.
struct sock_common field offsets are resolved from the running kernel's BTF (/sys/kernel/btf/vmlinux) at startup and patched into the eBPF program before load, so the program doesn't depend on compile-time offset constants. On kernels without BTF it falls back to built-in offsets verified for typical x86_64 distro kernels.
Browser identification reads /proc/<pid>/comm for the thread-group leader rather than the kernel-side thread comm, since Chromium's connect() calls come from a dedicated IO thread (Chrome_ChildIOThread), not the browser's own process name.
1. eBPF program (requires nightly Rust + rust-src + bpf-linker):
rustup toolchain install nightly --profile minimal
rustup component add rust-src --toolchain nightly
cargo install bpf-linker --locked
RUSTFLAGS='-C debuginfo=2 -C link-arg=--btf' \
cargo +nightly build -p browtrace-ebpf --bin browtrace-ebpf \
--target=bpfel-unknown-none -Z build-std=core --releaseThis produces target/bpfel-unknown-none/release/browtrace-ebpf, the compiled eBPF object the userspace binary loads at runtime. A harmless unable to open LLVM shared lib ... dlopen failed linker message may appear; bpf-linker falls back to its statically linked LLVM and still produces a correct object.
2. Userspace CLI:
cargo build -p browtrace --releasesudo ./target/release/browtraceLoading and attaching the eBPF program requires root, or the Linux capabilities CAP_BPF + CAP_PERFMON (older kernels: CAP_SYS_ADMIN). The binary does not manage capabilities itself:
sudo setcap cap_bpf,cap_perfmon+ep ./target/release/browtrace- Build both crates as shown above.
- Run
sudo ./target/release/browtrace; confirm the startup banner appears. - In another terminal, launch a supported browser (e.g.
firefox) and visit a website. - Confirm connection lines appear with the browser's name, a plausible PID, and a destination IP:443 (or :80 for plain HTTP).
- Start an unrelated process (e.g.
curl https://example.com); confirm no line is printed for it. - Press Ctrl+C; confirm the process exits cleanly.
- Run as a non-root, non-capable user; confirm it fails with an actionable
permission deniederror rather than a silent failure or panic.
-
Kernel-internal hooks, not a stable ABI.
tcp_v4_connect/tcp_v6_connectare internal kernel functions. BTF-based offset resolution removes the field-layout risk on BTF-enabled kernels, but a kernel that changes these functions' signature or semantics still requires updating the program. Seedocs/design.mdfor why this tradeoff was made. -
HTTPS is only ever observed as encrypted connection metadata. No request path, header, cookie, or plaintext body is captured or displayed.
-
The non-BTF fallback IPv6 offset assumes
CONFIG_NET_NS=y. On a kernel built without network namespaces, the fallbackSKC_V6_DADDR_OFFSETwould read from the wrong location. This only matters on kernels without BTF; BTF-enabled kernels resolve the real offset. -
Linux only, fixed browser list:
firefox,chrome,chromium,brave,brave-browser. TCP and connected UDP are covered; UDP sent withoutconnect()(plain unconnectedsendto()) is not observed. -
Requires Linux ≥ 5.8 for
RingBuf. -
No LICENSE file is present in this repository, so no open-source license currently applies.
See contract.md for the full requirements and docs/design.md / docs/research.md for the design rationale and validation history.