>_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, per-flow process attribution via lsof, 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 via lsof | none |
| Mouse support | left/right/middle, scroll, hover tooltips | none |
| Tooltips on hover & right-click | 14 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.rsshells out tolsofand 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, Enter confirms. |
i | Open interface chooser — saves to config; restart to apply. |
// 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. |
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 — classicConfiguration — ~/.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 # 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 = []
Serialization lives in src/config/prefs.rs. The TOML schema is stable; missing keys fall back to defaults so old configs keep working across upgrades.
JSON streaming — --json
Runs the capture engine headless and writes one JSON snapshot per refresh interval to stdout. Pipe to jq, log to a file, ship to a dashboard. Each line is a complete {"flows": [...]} object — no TUI is started, no terminal control bytes are emitted.
sudo iftoprs --json | jq -c '.flows[] | select(.rate_2s > 1000000)' sudo iftoprs --json > /var/log/iftoprs.ndjson & sudo iftoprs --json -f "tcp port 443" | head -n 1 | jq .
Each flow record carries the 5-tuple (source, destination, ports, protocol), rate_2s / rate_10s / rate_40s in bytes/sec, cumulative TX / RX totals, resolved hostnames, port names, and (if available) the attributed PID + process name.
Process attribution — Tab view
Shown by default; toggle off with -Z at launch or in the TUI. Background poller in src/util/procinfo.rs calls lsof -i -n -P -F pcn, parses the result, and builds a (local_addr, local_port) → (pid, name) map cached in Arc<Mutex<...>>. 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 — 14 hover & right-click segments
Every segment of the header bar carries a rich contextual tooltip. 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.
App info
Version, build date, repository link, license.
Interface
Selected interface, MAC, IP, MTU, status.
Flow count
Active flows, pinned count, filter state.
Clock
Local time, capture start, elapsed seconds.
Sort mode
Active sort key, direction, freeze status.
Refresh rate
Current interval, cycle hint, frame count.
Theme
Active theme name, swatch row, cycle hint.
Filter
Active substring filter, hit count, clear hint.
Pause state
Paused / running, last frame age, resume hint.
BPF filter
Compiled BPF expression, packet match count.
Net filter
CIDR mask in effect, auto / manual source.
Bandwidth scale
Bar log10 range, threshold, alert state.
View mode
Flows / Processes tab, drill-down chain.
Help
Top keybinds, full help shortcut h / ?.
Flow rows themselves carry a right-click tooltip: TX / RX rates across the three windows, cumulative totals, attributed process, and a 40 s 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,256 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,072 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 — lsof 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 — 14 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.