>_IFTOPRS — JACK INTO THE PACKET STREAM
A neon-drenched terminal UI for real-time bandwidth monitoring. Live libpcap capture with BPF filters, per-flow sliding-window averages (2 s / 10 s / 40 s), reverse-DNS + port-name resolution, native per-flow process attribution (no external tools), hover + right-click tooltips, mouse + keyboard navigation, 31 cyberpunk color themes, --json NDJSON stream for headless pipelines, configurable bandwidth alerts, sparkline history per flow, and auto-restart on transient capture errors. macOS & Linux. Built in Rust with ratatui + crossterm + pcap.
Quickstart
Install from crates.io or build from source. Raw packet capture needs root or cap_net_raw:
# install from crates.io cargo install iftoprs # from source git clone https://github.com/MenkeTechnologies/iftoprs cd iftoprs && cargo build --release # launch (needs root for raw capture) sudo iftoprs # auto-detect default interface sudo iftoprs -i en0 # specific interface sudo iftoprs -f "tcp port 443" # BPF filter — HTTPS only sudo iftoprs -F 10.0.0.0/8 -B # filter private net, show bytes sudo iftoprs -Z # show process names per flow # headless / pipelines sudo iftoprs --json # NDJSON stream to stdout sudo iftoprs --json | jq '.flows[0]' # shell completions iftoprs --completions zsh > _iftoprs iftoprs --completions bash > iftoprs.bash iftoprs --completions fish > iftoprs.fish
Full install + dependency matrix lives in the README. On Linux apt install libpcap-dev before cargo build. On macOS libpcap ships with the system — no install needed.
Why iftoprs — against the classic tool
| Feature | iftoprs | iftop (C, 2002) |
|---|---|---|
| Memory safety | Rust — no UB, no leaks | raw C |
| Async capture loop | tokio + mpsc | blocking pcap_loop |
| Per-flow process attribution | PID + name, native (libproc / /proc) | none |
| Mouse support | left/right/middle, scroll, hover tooltips | none |
| Tooltips on hover & right-click | 10 segment tooltips + flow drill-down | none |
| Per-flow sparkline (40 s) | inline ▁▂▃▅▇█ + tooltip | none |
| Color themes | 31 — live chooser | 4 hardcoded colors |
| JSON / NDJSON output | --json to stdout | none |
| Bandwidth alerts | configurable threshold + bell + flash | none |
| Auto-restart on transient errors | exponential backoff | none |
| Pinned / bookmarked flows | F — ★ floats to top | none |
| Live filter (hostname/IP) | / — substring match | none |
| Clipboard export | y — selected flow | none |
| NDJSON-pipeable export | e — full snapshot to file | none |
| Shell completions | zsh / bash / fish / elvish / powershell | none |
| TOML config + auto-save | ~/.iftoprs.conf | readline-style settings |
| Single static binary | cargo install — one binary | autotools build |
Capture engine
The capture pipeline is built around three concerns — read packets, parse to flow tuples, survive transient errors:
- libpcap —
pcap::Captureopens the device with the configured BPF filter, snaplen, and promiscuous flag. The capture thread lives insrc/capture/sniffer.rs. - async hand-off — raw packets flow over a
tokio::sync::mpscchannel to the parser task; the UI thread reads parsed flow updates from a second channel. Lossy capture (kernel drop) is logged but never panics. - parser —
src/capture/parser.rswalks Ethernet/IPv4/IPv6/TCP/UDP/ICMP headers, tracks 5-tuples, classifies protocol, and folds bytes into the per-flow counters. - auto-restart — transient pcap errors trigger an exponential-backoff reopen instead of crash. v2.22.2 release notes (
76f42105bd) wired this in. - sliding windows — bandwidth is tracked over 2 s / 10 s / 40 s averages with a per-flow ring buffer in
src/data/history.rs. Cumulative + peak counters live alongside.
Flow tracker
The flow model lives in src/data/flow.rs and the tracker in src/data/tracker.rs. Every flow is keyed by (src, dst, src_port, dst_port, protocol); counters split into tx and rx directions based on the configured interface IP / network filter.
- 5-tuple keys — protocols cover TCP / UDP / ICMP / Other (ARP, raw IP, etc.).
- three windows — 2 s yellow, 10 s green, 40 s cyan; sort cycles through them with
1/2/3. - peak + cumulative — toggle the cumulative row with
U. - process attribution — an opt-out background poller in
src/util/procinfo.rsreads the kernel socket table natively (libprocon macOS,/procon Linux) and maps socket → PID + process name. Joined into the flow on render. - DNS resolution — async reverse-DNS cache in
src/util/resolver.rs; failed lookups never block the UI. - port names —
/etc/services-style mapping from the same resolver.
CLI flags
Defined in src/config/cli.rs via clap derive macros. Generate shell completions with --completions SHELL.
// CAPTURE
| Flag | Description |
|---|---|
-i, --interface NAME | Network interface to monitor (auto-detect default gateway interface if absent). |
-f, --filter EXPR | BPF filter expression, e.g. "tcp port 80", "host 10.0.0.1". |
-F, --net-filter CIDR | IPv4 network filter, e.g. 192.168.1.0/24. Auto-detected from interface if omitted. |
-p, --promiscuous | Enable promiscuous mode — capture all traffic on the segment. |
// DISPLAY
| Flag | Description |
|---|---|
-n, --no-dns | Disable DNS hostname resolution — raw IPs only. |
-N, --no-port-names | Disable port-to-service resolution — numeric ports only. |
-b, --no-bars | Disable bar graph display. |
-B, --bytes | Display bandwidth in bytes instead of bits. |
-P, --hide-ports | Hide ports alongside hosts. |
-Z, --no-processes | Hide the process column (shown by default). |
// OUTPUT
| Flag | Description |
|---|---|
--json | Stream NDJSON snapshots to stdout instead of running the TUI. Pipe to jq, append to log, feed into a dashboard. |
// SYSTEM
| Flag | Description |
|---|---|
-l, --list-interfaces | List available capture interfaces and exit. |
--list-colors | Preview all 31 color themes with swatches and exit. |
--completions SHELL | Generate shell completions: zsh / bash / fish / elvish / powershell. |
-h, --help | Display help and exit. |
-V, --version | Display version and exit. |
// EXAMPLES
Keybind matrix
Defined in src/ui/app.rs. Every toggle that changes display state writes through to ~/.iftoprs.conf immediately — preferences survive across runs without explicit save.
// DISPLAY MODS
| Key | Action |
|---|---|
Tab | Switch view — Flows / Processes. |
n | Toggle DNS resolution. |
N | Toggle service-name resolution. |
t | Cycle line display — two-line / one-line / sent-only / recv-only. |
p | Toggle port display. |
Z | Toggle process display. |
b | Cycle bar style — gradient / solid / thin / ascii. |
B | Toggle bytes / bits. |
T | Toggle hover tooltips (right-click still works). |
U | Toggle cumulative totals row. |
P | Pause / resume display — shows overlay. |
x | Toggle border chrome. |
g | Toggle column header. |
f | Cycle refresh rate — 1 s / 2 s / 5 s / 10 s. |
// SORT
| Key | Action |
|---|---|
1 | Sort by 2 s average. |
2 | Sort by 10 s average. |
3 | Sort by 40 s average. |
< | Sort by source name. |
> | Sort by destination name. |
o | Freeze current sort order. |
r | Reverse sort order. |
// NAVIGATION
| Key | Action |
|---|---|
j ↓ | Select next flow. |
k ↑ | Select previous flow. |
Ctrl+D | Half-page down. |
Ctrl+U | Half-page up. |
G End | Jump to last. |
Home | Jump to first. |
Esc | Deselect / clear process filter / close overlay. |
Enter | Drill into selected process (Processes tab). |
// FILTER
| Key | Action |
|---|---|
/ | Enter filter mode — live substring match on hostname / IP. |
0 | Clear filter. |
Enter | Confirm filter. |
Esc | Cancel filter. |
Ctrl+W | Delete word in filter prompt. |
Ctrl+K | Kill to end of line in filter prompt. |
// CHOOSERS
| Key | Action |
|---|---|
c | Open color-theme chooser — live preview, j/k navigates, scroll cycles, Enter confirms, Esc/q/c cancels. |
C | Open custom-theme editor — 6 palette slots, j/k select slot, h/l ±1 color, H/L ±10, s/Enter name + save, Esc/q cancel. |
i | Open interface chooser — j/k navigates, Enter saves to config (restart to apply), Esc/q cancels. |
// ACTIONS
| Key | Action |
|---|---|
y | Copy selected flow to clipboard. |
F | Pin / unpin selected flow — ★ floats to top. |
e | Export all flows to ~/.iftoprs.export.txt. |
// MOUSE
| Input | Action |
|---|---|
| Left click | Select flow row. |
| Right click (flow) | Tooltip — TX / RX rates, totals, process, sparkline. |
| Right click (header) | Instant segment tooltip — persistent until dismissed. |
| Middle click | Pin / unpin flow. |
| Mouse move | Dismiss flow tooltip. |
| Scroll up / down | Navigate flows; cycles themes in chooser. |
| Hover header bar | Segment tooltip after 1 s delay; auto-hides after 3 s. |
// GENERAL
| Key | Action |
|---|---|
h ? | Toggle help HUD. |
q | Disconnect — saves preferences. |
Ctrl+C | Force disconnect. |
Display modes
Three display dimensions cycle through fixed sets of values; each cycle key writes the new value through to ~/.iftoprs.conf immediately. The enums live in src/ui/app.rs (LineDisplay, BarStyle) and the refresh ladder in the input handler.
// LINE DISPLAY — t
| Mode | What it shows |
|---|---|
TwoLine | Send row and receive row stacked per flow (default). |
OneLine | Send + receive condensed onto a single row per flow. |
SentOnly | Only the transmit direction. |
RecvOnly | Only the receive direction. |
// BAR STYLE — b
| Style | Glyph set |
|---|---|
Gradient | Color-graded fill (default). |
Solid | Solid block fill. |
Thin | Thin-line fill. |
Ascii | Plain ASCII fill for terminals without box-drawing glyphs. |
// REFRESH RATE — f
Cycles 1s → 2s → 5s → 10s and wraps. The refresh interval also sets the sparkline cadence, so the visible sparkline window is 40 × refresh_rate seconds. Persisted as refresh_rate in the config.
Color themes — 31 built-in
Defined in src/config/theme.rs. Press c in the TUI for the live chooser with side-by-side swatch preview. Selection is persisted to ~/.iftoprs.conf. Outside the TUI: iftoprs --list-colors prints every theme with swatches.
NeonSprawl — defaultAcidRainIceBreakerSynthWaveRustBeltGhostWireRedSectorSakuraDenDataStreamSolarFlareNeonNoirChromeHeartBladeRunnerVoidWalkerToxicWasteCyberFrostPlasmaCoreSteelNerveDarkSignalGlitchPopHoloShiftNightCityDeepNetLaserGridQuantumFluxBioHazardDarkwaveOverlockMegacorpZaibatsuIftopcolor — classic// CUSTOM THEME EDITOR — C
Beyond the 31 builtins, press C in the TUI to open the custom-theme editor (handled in src/main.rs / src/ui/app.rs). A theme is six 256-color palette indices; the editor seeds them from the active theme (or the active custom theme if one is selected):
j/k— move between the six color slots.h/l— decrement / increment the selected slot by 1 (wrapping); the palette applies live so you see the change immediately.H/L— decrement / increment by 10 for coarse jumps.s/S/Enter— switch to naming mode, type a name (up to 20 chars, withLeft/Right/Backspaceediting), thenEnterto save.Esc/q— cancel the editor and restore the previous theme.
Saved themes are written to ~/.iftoprs.conf under custom_themes with the active one tracked in active_custom_theme (serialized in src/config/prefs.rs), so they survive restarts alongside the builtin selection.
Configuration — ~/.iftoprs.conf
TOML file at ~/.iftoprs.conf. Auto-created on first run; every TUI toggle writes through immediately. The repo ships iftoprs.default.conf as a documented reference.
# Color theme (see --list-colors for all 31 options) theme = "NeonSprawl" # Network interface to capture on (overridden by -i flag) # interface = "en0" # DNS and port resolution dns_resolution = true port_resolution = true # Display toggles show_ports = true show_bars = true show_border = true show_header = true show_processes = true show_cumulative = false use_bytes = false # Hover tooltips on header segments (right-click always works) hover_tooltips = true # Bar style: Gradient, Solid, Thin, Ascii bar_style = "Gradient" # Refresh rate in seconds (1, 2, 5, or 10) refresh_rate = 1 # Alert threshold in bytes/sec (0.0 = disabled) # 125000.0 = 1 Mbit/s # 1250000.0 = 10 Mbit/s # 125000000.0 = 1 Gbit/s alert_threshold = 0.0 # Pinned flows — persisted automatically when you press F pinned = [] # Custom themes (six 256-color indices) — written when you save with C. # Omitted entirely until at least one custom theme exists. # [custom_themes.mytheme] # c1 = 51 # c2 = 201 # active_custom_theme = "mytheme"
Serialization lives in src/config/prefs.rs. Every key carries a serde default, so missing keys fall back to sane values and old configs keep working across upgrades. The interface, custom_themes, and active_custom_theme keys are skipped when empty (skip_serializing_if), so a fresh config never writes them.
JSON streaming — --json
Runs the capture engine headless and writes one JSON snapshot per refresh interval to stdout (the run_json_mode path in src/main.rs). Pipe to jq, log to a file, ship to a dashboard. No TUI is started, no terminal control bytes are emitted. Each line is one complete JsonSnapshot object:
{
"timestamp": "2026-06-21T12:00:00-04:00", // RFC 3339, chrono::Local
"flow_count": 3,
"total_sent": 123456, // cumulative bytes, all flows
"total_recv": 654321,
"peak_sent": 98765.0, // peak rate, bytes/sec
"peak_recv": 54321.0,
"flows": [
{
"src": "host.example.com:443", // resolved host:port string
"dst": "10.0.0.5:51234",
"src_port": 443,
"dst_port": 51234,
"protocol": "TCP", // TCP / UDP / ICMP / Other
"sent_2s": 81234.0, "recv_2s": 4096.0,
"sent_10s": 72100.0, "recv_10s": 3800.0,
"sent_40s": 65000.0, "recv_40s": 3500.0,
"total_sent": 1048576, "total_recv": 65536,
"process_name": "firefox", // omitted if unknown
"pid": 4821 // omitted if unknown
}
]
}
The sent_* / recv_* rate fields are emitted in bits/sec by default (each byte rate is multiplied by 8); pass -B / --bytes to emit them in bytes/sec instead. process_name and pid are skipped when attribution is unavailable (serde skip_serializing_if), so consumers must treat them as optional. The capture engine, parser, and tracker are identical to TUI mode — only the renderer is replaced.
sudo iftoprs --json | jq -c '.flows[] | select(.sent_2s > 1000000)'
sudo iftoprs --json > /var/log/iftoprs.ndjson &
sudo iftoprs --json -f "tcp port 443" | head -n 1 | jq .
sudo iftoprs --json -B | jq '.flows[] | {dst, recv_2s}' # byte rates
Process attribution — Tab view
Shown by default; toggle off with -Z at launch or in the TUI. Background poller in src/util/procinfo.rs reads the kernel socket table natively — libproc (proc_listpids + proc_pidfdinfo) on macOS, /proc/net + /proc/[pid]/fd on Linux — and builds a (local_port, proto) → (pid, name) map cached in Arc<Mutex<...>>. No external binary is spawned. The flow renderer joins against the cache on every paint.
- Flows tab (default) — per-flow process column.
- Processes tab — press
Tab— aggregates bandwidth per process across all of that process's flows. - Drill-down — press
Enteron a process row to filter the Flows tab to that process.Escclears the filter. - Cost — the poller runs at the refresh-rate interval, not per-packet; even on busy hosts the overhead stays measurable but small.
Tooltips — 10 header segments + flow drill-down
The status header bar is split into pipe-delimited segments; each one carries a rich contextual tooltip built by header_segment_tooltip in src/ui/app.rs. Hover triggers after 1 s with a 3 s auto-hide; right-click triggers instantly and persists until dismissed. Disable hover (right-click still works) with T. The cards below describe the actual fields each segment emits — live values, not placeholders.
App (iftoprs)
Version (v2.22.9), description, author, repo, crate, license, install command, runtime platform (OS + arch), build stack, config path, tracked-flow count.
Interface
Selected interface (or auto-detected), mode (auto gateway vs -i manual), DNS / port-name / port-display state, promisc note, active-connection count, i chooser hint, pcap::Device::list() source.
Flows
Total / visible / hidden / pinned / unique-process counts, total + combined TX/RX, peak TX/RX + peak total, cumulative TX/RX, current view tab, j/k · / · F key hints.
Clock
Local time, date + weekday, timezone + UTC offset, Unix epoch seconds, refresh interval, chrono::Local::now() source.
Sort
Active sort key (2s / 10s / 40s / src / dst), direction (normal vs reversed), freeze status, first + last flow, sorted-flow count, rate / host key hints, r reverse hint.
Refresh rate
Current interval, description, next (f) / prev rate, cycle order 1s→2s→5s→10s, ~30 fps render note, paused state, capture-thread source, prefs key.
Theme
Active theme display name, 31 builtin palette count, custom-theme count, total available, bar style, c chooser + C editor hints, --list-colors hint, prefs keys.
Filter
Active substring filter, length, matched / hidden counts, match type (case-insensitive host/IP), cursor position, nav keys, edit keys (Ctrl+a/e/b/f), delete keys (Ctrl+w/u/k), 0 clear hint.
Pause state
Paused vs live, effect (stats frozen), refresh rate, tracked-flow count, P resume / pause hint — layout differs depending on whether capture is paused.
Help
Open / close keys (h / H / ? / Esc), 3-column keybind layout, category list, mouse hint, e export, y copy, q quit.
Any other segment text falls through to a generic ▶ Header tooltip that echoes the raw segment label. Flow rows themselves carry a separate right-click tooltip: TX / RX rates across the three windows, cumulative totals, attributed process, and a 40-sample sparkline of recent bandwidth (▁▂▃▅▇█).
Sparkline history
Per-flow rolling ring buffer of 40 samples in src/data/history.rs. Rendered two ways:
- Inline below the selected row when sparkline display is enabled (40 s window, ▁▂▃▅▇█ block characters).
- Inside the right-click tooltip for any flow — not only the selected one.
Samples are folded at the refresh-rate cadence, so the visible window is 40 × refresh_rate seconds. At default 1 s refresh that's a 40 s window; at 10 s refresh it stretches to 400 s.
Bandwidth alerts
Set alert_threshold in ~/.iftoprs.conf (bytes / sec, 0.0 disables). When any flow's instantaneous rate crosses the threshold:
- Border flashes red for a beat.
- Terminal bell (
\x07) fires. - Status bar shows
⚠ ALERT: hostname rate/s.
# in ~/.iftoprs.conf alert_threshold = 1250000.0 # 10 Mbit/s alert_threshold = 125000000.0 # 1 Gbit/s
Tests & CI
2,275 test functions across the production crate and integration suite. CI runs on every push and PR to main via GitHub Actions.
| Job | Command | Notes |
|---|---|---|
| Format | cargo --locked fmt --all --check | No pcap link — no libpcap-dev needed. |
| Clippy | cargo clippy --all-targets --locked -- -D warnings | Linux installs libpcap-dev via apt. |
| Test | cargo build --locked && cargo test --locked | Ubuntu + macOS matrix, fail-fast: false. |
Integration tests in tests/integration.rs launch the built binary via the CARGO_BIN_EXE_iftoprs environment variable — not cargo run — so CLI output is read directly from the process and stays reliable in CI. The toolchain is pinned through rust-toolchain.toml with stable + rustfmt + clippy so local and CI runs share the same compiler.
Local checks — identical to CI:
cargo --locked fmt --all --check cargo clippy --all-targets --locked -- -D warnings cargo test --locked
Crate layout
Single binary, eight modules, ~21k lines of production Rust:
src/main.rs
Entry point: CLI parse, capture spawn, TUI bootstrap, signal handling. 1,135 lines.
src/capture/
sniffer.rs — pcap loop + restart. parser.rs — Ethernet/IP/TCP/UDP/ICMP decode. ~3.1k lines.
src/data/
flow.rs 5-tuple model + counters. tracker.rs — flow store. history.rs sparkline ring. ~3.5k lines.
src/ui/
app.rs — TUI state + input. render.rs — ratatui paint. ~6.3k lines.
src/config/
cli.rs — clap derive. theme.rs — 31 themes. prefs.rs — TOML auto-save. ~4.5k lines.
src/util/
format.rs — rate/byte formatters. resolver.rs — async DNS + port names. procinfo.rs — native socket→PID poll. ~2.8k lines.
tests/integration.rs
479 binary-driven integration tests. 5,258 lines.
completions/
Shipped zsh completion file (_iftoprs). Other shells generated on demand.
Full per-file breakdown is in the engineering report.
Dependencies — 15 direct
| Crate | Version | Role |
|---|---|---|
ratatui | 0.30 | TUI rendering framework. |
crossterm | 0.29 | Terminal events + manipulation, mouse capture. |
pcap | 2.4 | Packet capture via libpcap (system). |
tokio | 1.51 | Async runtime, mpsc channels. |
clap | 4.6 | CLI argument parsing (derive). |
clap_complete | 4 | Shell completion generation. |
dns-lookup | 3.0 | Reverse DNS resolution. |
regex | 1.12 | Pattern matching for filters. |
chrono | 0.4 | Time operations, clock segment. |
anyhow | 1.0 | Error handling. |
serde | 1.0 | Config + JSON serialization. |
serde_json | 1.0 | NDJSON streaming output. |
toml | 1.1 | Config file format. |
dirs | 6.0 | Home-directory detection for config / export paths. |
Platform support
- macOS —
libpcapships with the system;sudo iftoprsworks out of the box. - Linux — install
libpcap-dev(Debian / Ubuntu) orlibpcap-devel(Fedora / RHEL). Run as root or grantcap_net_raw,cap_net_admin:sudo setcap cap_net_raw,cap_net_admin=eip $(which iftoprs)
- Rust toolchain — pinned to stable, Rust 1.85+ (edition 2024).
- Architectures — x86_64 + aarch64 verified on both macOS and Linux.
Repository & links
- Source — GitHub repo.
- Crate — crates.io (
cargo install iftoprs). - Rust API docs — docs.rs.
- Issues — issue tracker.
- CI status — GitHub Actions.
- Engineering report — report.html (LOC breakdown, subsystem map, dependency table, test surface).
- README — full README on GitHub.