Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

browtrace

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.

How it works

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.

Build

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 --release

This 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 --release

Run

sudo ./target/release/browtrace

Required permissions

Loading 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

Manual test procedure

  1. Build both crates as shown above.
  2. Run sudo ./target/release/browtrace; confirm the startup banner appears.
  3. In another terminal, launch a supported browser (e.g. firefox) and visit a website.
  4. Confirm connection lines appear with the browser's name, a plausible PID, and a destination IP:443 (or :80 for plain HTTP).
  5. Start an unrelated process (e.g. curl https://example.com); confirm no line is printed for it.
  6. Press Ctrl+C; confirm the process exits cleanly.
  7. Run as a non-root, non-capable user; confirm it fails with an actionable permission denied error rather than a silent failure or panic.

Limitations

  • Kernel-internal hooks, not a stable ABI. tcp_v4_connect/tcp_v6_connect are 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. See docs/design.md for 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 fallback SKC_V6_DADDR_OFFSET would 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 without connect() (plain unconnected sendto()) 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.

About

Minimal Rust + eBPF CLI that observes outbound TCP connections opened by known browser processes on Linux and prints connection metadata to the terminal.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages