A Rust network sniffer with an htop-style TUI. Captures live traffic from a network
interface and writes a Wireshark-compatible .pcap.

git clone https://github.com/jaymeklein/rust-wire && cd rust-wire
sudo apt install -y libpcap-dev # 1. build dependency
cargo install --path . # 2. build + install to ~/.cargo/bin
sudo setcap cap_net_raw,cap_net_admin=eip ~/.cargo/bin/rust-wire # 3. capture without root
rust-wire -L # list interfaces
rust-wire -i eth0 # capture to capture.pcap, press q to stopThat's it. Details on each step below.
Needed at build time to link against libpcap.so.
| Platform | Command |
|---|---|
| Debian / Ubuntu | sudo apt install -y libpcap-dev |
| Fedora | sudo dnf install -y libpcap-devel |
| Atomic Fedora (Bazzite, Silverblue, Bluefin, Kinoite) | see below |
| macOS | brew install libpcap |
/usr is read-only on the atomic images, so dnf isn't the install path. Two options:
Layer it on the host β keeps steps 2 and 3 exactly as written, since ~/.cargo/bin lives
under the writable /var/home:
sudo rpm-ostree install libpcap-devel
systemctl reboot # layering only takes effect on the next deploymentBuild in a Distrobox β the distro's own recommendation for development packages, as layered packages slow down (and can block) system upgrades:
distrobox create --name dev --image fedora:latest
distrobox enter dev
sudo dnf install -y libpcap-devel
cargo install --path .The binary lands in your shared ~/.cargo/bin but links against the container's
libpcap.so.1. Confirm the host has the runtime library before running it outside the
container:
ldconfig -p | grep libpcapIf it's missing, run rust-wire from inside the distrobox β containers share the host
network namespace, so the interfaces are all visible β or layer the package instead.
cargo install --path . puts rust-wire on your PATH (via ~/.cargo/bin, already there
if you used rustup) so you can run it from any directory. Verify with which rust-wire.
Prefer to work out of the repo instead? Use cargo build --release and invoke it by path as
./target/release/rust-wire.
Either way the binary is a copy β after changing the source, re-run the same command to update it, then redo step 3.
Opening a live interface needs elevated privileges. setcap grants just that capability to
the binary, so you never need sudo:
sudo setcap cap_net_raw,cap_net_admin=eip ~/.cargo/bin/rust-wire # installed
sudo setcap cap_net_raw,cap_net_admin=eip target/release/rust-wire # or built in-repoRebuilding replaces the file and drops the capability β re-run setcap after each build.
Skipping this step is fine; just prefix runs with sudo. See Troubleshooting
if that gives you command not found.
rust-wire [OPTIONS]
-i, --interface <NAME> Interface to capture on (default: first non-loopback up)
-L, --list-interfaces List interfaces and exit
-f, --filter <BPF> BPF filter, e.g. "tcp port 80"
-c, --count <N> Stop after N packets
--duration <DUR> Stop after duration (30s, 5m, 1h)
--max-file-size <SIZE> Stop when the pcap reaches this size (100k, 500M, 2G)
-s, --snaplen <BYTES> Bytes per packet to capture [default: 65535]
--promisc Enable promiscuous mode
--no-tui Disable TUI; use tcpdump-style stdout
--quiet No display; write pcap only
-o, --output <FILE> Output pcap [default: capture.pcap]
--max-talkers <N> Cap on unique srcβdst pairs tracked for top talkers
(bounds RAM on long/high-cardinality runs) [default: 1024]
# List interfaces
rust-wire -L
# Interactive TUI capture, everything on eth0, to capture.pcap
rust-wire -i eth0
# 5 seconds of ICMP on loopback, then exit
rust-wire -i lo -f "icmp" --duration 5s -o icmp.pcap
# Silent capture of 100 HTTP packets
rust-wire -i eth0 -f "tcp port 80" -c 100 --quiet -o http.pcap
# Stop after 100 MB of pcap OR 10 minutes, whichever first β safe for small disks
rust-wire -i eth0 --max-file-size 100M --duration 10m -o rolling.pcap
# High-cardinality trace (public webserver) with tight memory budget
rust-wire -i eth0 --max-talkers 256 --max-file-size 500M -o web.pcap
# Non-TTY: falls back to tcpdump-style, one line per packet
rust-wire -i lo | headThese assume you ran setcap; otherwise prefix each with sudo.
The TUI is selected automatically when stdout is a terminal (unless --no-tui or --quiet).
| Key | Action |
|---|---|
q / Esc |
quit and flush the pcap |
r |
start / stop recording to the pcap (see below) |
f / Ctrl-F |
edit the capture filter (see below) |
β β / k j |
move the cursor through the packet list |
PgUp / PgDn |
move a page at a time |
g / G |
jump to the newest / oldest buffered packet |
d / Enter |
show or hide the packet detail pane |
p |
freeze the packet list (capture and .pcap writing continue) |
The screen shows a Wireshark-style packet table β number, time, source, destination,
protocol, size, and a decoded Info summary β alongside live totals and pps/bps, a
protocol mix bar chart, top talkers, and a throughput sparkline. Selecting a row opens
its Ethernet / IP / transport fields in the detail pane at the bottom. Scrolling up
pins the cursor to that packet as new ones arrive; g resumes following the live tail.
The last 2000 packets stay scrollable. Panes drop out on narrow terminals: the sidebar below 104 columns, the detail pane below 24 rows.
r toggles whether packets reach the .pcap. Recording starts on, and a badge in
the header always says which way it is: β REC or β NOT RECORDING.
Stopping recording does not stop the capture β packets keep arriving, the table keeps
filling and the stats keep counting; they simply aren't written. That's the difference
between r and p: p freezes the display, r gates the file.
- Stopping flushes the pcap, so what's on disk is complete and openable in Wireshark while you're still watching the live traffic.
- Starting again appends to the same file, so one run can hold several recorded stretches with the quiet parts left out.
--max-file-sizeonly counts recorded bytes, so a run that's mostly not recording won't trip it.- If you never record β say you turned it off and then changed the filter β the output file is not created or truncated at all, and the closing summary says so.
The final summary reports recorded alongside packets whenever the two differ.
f (or Ctrl-F) opens a one-line BPF editor over the packet list, pre-filled with the
filter currently in force. Ctrl-F again applies it β so the whole edit is one key,
type, same key.
| Key | Action |
|---|---|
Ctrl-F / Enter |
apply the filter and restart the capture |
| Esc | close the editor, leaving the running capture alone |
| Tab | insert the highlighted completion |
β β / Ctrl-P Ctrl-N |
move through the completion list |
β β, Home / End, Ctrl-A / Ctrl-E |
move the caret |
Ctrl-W / Ctrl-U |
delete the previous word / the whole line |
Completion matches the word under the caret against the BPF vocabulary β tcp, udp,
host, src port, ip proto, portrange, less, greater, and the rest β each with
a one-line reminder of its syntax. Parentheses start a fresh word, so completion keeps
working inside (...) groups.
The filter is compiled before it is accepted: a typo is reported inline (β syntax error) and the editor stays open on your text. An empty expression means no filter β
capture everything.
Applying restarts the capture from scratch: stats, the packet list, the throughput
history, and the --count / --duration / --max-file-size budgets are all reset,
and the output pcap is overwritten as soon as the new run records a packet. Use -o
with a fresh path per run if you need to keep an earlier capture β or press r before
applying, which leaves the file alone until you record again.
Recording state is the one thing that carries across a restart: if you stopped recording before applying, it stays stopped.
Capture ends on whichever fires first: --count, --duration, --max-file-size, Ctrl-C,
or q / Esc in the TUI.
--max-talkers <N>caps the top-talkers table. When full, the lowest-bytes entry is evicted to make room for a new pair. Set to0to disable tracking entirely.- The kernel packet-ring buffer is 10 MB by default (hard-coded in
src/capture.rs; editbuffer_sizefor tighter memory budgets on small devices).
wireshark capture.pcap
tshark -r capture.pcap # or from the CLI
capinfos capture.pcaprust-wire: command not found β you built with cargo build --release instead of
cargo install --path .. Either install it, or run it as ./target/release/rust-wire.
sudo rust-wire says command not found, but rust-wire works β most distros configure
sudo with secure_path, which strips ~/.cargo/bin. Run setcap (step 3) so sudo isn't
needed, or use the full path: sudo ~/.cargo/bin/rust-wire ....
Permission denied opening the interface β setcap wasn't applied, or a rebuild wiped it.
Check with getcap ~/.cargo/bin/rust-wire and re-apply.
A shell alias won't help here β sudo doesn't expand aliases, and neither do scripts,
xargs, or watch. Use cargo install --path ..
- pcapng output format
- TLS keylog / decryption
- Custom dissectors beyond L3/L4
- Windows / Npcap install docs (should still build)