What The F*ck Internet — a live, visual network-path diagnostic that pinpoints exactly where your connection dies, and tells you how to fix it.
The Wi-Fi icon shows full bars. The browser says "Offline". Stop guessing which hop is broken. Watch the whole path light up.
This is a ground-up rewrite of the archived
wtfi. The old version printed a flat
checklist and left you to correlate it. wtfi2 draws the connection as a
topology diagram, streams every hop live, and reasons about the root
cause so the answer is one line, not ten.
- Topology, not a checklist. Your connection is rendered as a chain —
You → Wi-Fi → Gateway → Uplink → Internet → DNS → Portal— with the break marked where it actually happens. - Root-cause reasoning. It correlates the hops ("IPs reachable but names aren't → DNS-only outage") into a single verdict plus a concrete fix, and it hedges honestly with a confidence level.
- Catches "up but unusable". Every hop can answer and still be dropping a quarter of your packets. wtfi measures loss and jitter per hop, so the classic full-bars-nothing-loads network is diagnosed instead of being reported as healthy — and it says whether the loss is your Wi-Fi or your uplink.
- Live dashboard.
wtfi -wre-probes continuously with real-time latency sparklines and a signal gauge, so you can walk around and find the dead zone. - Says what it measured. Every verdict carries the method behind it — how many probes, to where, over how long — so you can tell a thorough measurement from a single timed-out packet instead of taking the headline on trust.
- No sudo required. Signal, noise, gateway RTT, dual-stack reachability, DNS benchmarking, the TTL sweep and captive-portal detection all work unprivileged.
The live dashboard (wtfi -w) is the recording at the top: it re-probes every
four seconds, and the verdict follows the network as it breaks and recovers.
The one-shot report (wtfi -v) shows every hop with what it measured and how:
The Uplink node only joins the chain when something upstream is already
broken — a healthy run never pays for the TTL sweep. When it does appear, it is
what turns "your ISP is down" into a hop number:
The recording and both screenshots come from --demo, which replays scripted sweeps on documentation addresses (RFC 5737) so no real network appears. Regenerate them with the tapes in docs/.
Homebrew (macOS and Linux):
brew install kanywst/tap/wtfi2Prebuilt binary via the install script (macOS and Linux):
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/kanywst/wtfi2/releases/latest/download/wtfi2-installer.sh | shFrom crates.io (requires Rust 1.88+):
cargo install wtfi2Or from source:
git clone https://github.com/kanywst/wtfi2
cd wtfi2
cargo install --path .The repo ships a flake, so you can run it without installing anything:
nix run github:kanywst/wtfi2 # one-shot report
nix run github:kanywst/wtfi2 -- -w # live dashboardA development shell with the toolchain and the network tools wtfi shells out to
(ip, iw, ping, traceroute):
nix develop
cargo run -- -wTo install it into a flake-based NixOS system, add the flake as an input and pull in the overlay it exposes:
# flake.nix
inputs.wtfi2.url = "github:kanywst/wtfi2";
outputs = { nixpkgs, ... }@inputs: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [ ./configuration.nix ];
};
};# configuration.nix
{ inputs, pkgs, ... }:
{
nixpkgs.overlays = [ inputs.wtfi2.overlays.default ];
environment.systemPackages = [ pkgs.wtfi2 ];
}inputs.wtfi2.nixosModules.default wires up the same overlay for you if you
prefer imports = [ inputs.wtfi2.nixosModules.default ];. On Linux the package
falls back to its own ip, iw, ping and traceroute when the system has
none.
The installed command is wtfi in every case. The crate builds anywhere but only diagnoses on macOS and Linux — see Platform support.
wtfi # one-shot report: diagram + verdict
wtfi -w # live dashboard (q to quit, r to re-probe now)
wtfi -v # verbose: every metric for every hop
wtfi --json # machine-readable output for scripts/CI
wtfi --no-color # plain text, no ANSI
wtfi --timeout 5 # give up on the sweep sooner and report what landed
wtfi --record # also append this sweep to the history file (works with -w)
wtfi --history # the last 20 recorded sweeps (--history 100, --history --json)Recording is off unless you pass --record. Each finished sweep becomes one JSON line in $XDG_STATE_HOME/wtfi/history.jsonl (~/.local/state/wtfi/history.jsonl when that is unset, on macOS too; WTFI_HISTORY overrides the path). wtfi -w --record also starts its latency sparklines from the last recorded sweeps, so a dead zone you walked through yesterday is still on the graph today. The file trims itself to its newer half past 4 MiB.
The exit code reflects health, so scripts can branch on it:
| Code | Meaning |
|---|---|
| 0 | All good |
| 1 | Degraded (warnings) |
| 2 | Broken |
| 3 | Unsupported platform |
| 4 | Nothing could be measured |
The codes and the --json shape are the only things scripts are told they can depend on, so tests/cli.rs drives the real binary and pins both: the code is always one of the five above, it always agrees with the reported status, every hop has settled, and first_break / verdict.source always name a hop that is really in the path.
Code 4 is deliberately distinct from 2: it means the OS tools wtfi reads the network through wouldn't run, so nothing was observed either way. A script should retry on 4 rather than treat it as an outage.
Probes fan out concurrently and stream their results into the path as they complete:
- Host — what address this machine actually holds: IPv4 and prefix, routable IPv6, whether DHCP answered at all or IPv4 autoconfiguration took over, whether the gateway is even inside your subnet, and whether your lease is behind carrier-grade NAT.
- L2 Link — Wi-Fi RSSI, noise, SNR, channel, PHY mode, security and tx rate, graded into a signal quality.
- L3 Gateway — resolves the default route and sends a short ICMP burst to the router, measuring RTT, packet loss and jitter, and flagging VPN/tunnel interfaces and sub-1500 MTU.
- Uplink — when, and only when, something upstream is already broken, a bounded ICMP TTL sweep turns the single edge between your gateway and the internet back into hops. Where the replies stop decides whether the break is on your own line (modem/ONU, WAN cable) or inside your ISP's network — and names the last address that answered, so you have something to quote.
- WAN — real TCP handshakes to three independent anycast operators (Cloudflare, Google, Quad9) over IPv4 and IPv6, which exposes asymmetric blackholing without needing raw ICMP and separates "the internet is unreachable" from "this network blocks that address". It then repeats the handshake against the quickest one to measure loss and jitter on the uplink itself.
- DNS — benchmarks the system resolver against Cloudflare and Google to separate "DNS is down" from "your resolver is just slow".
- Captive portal — a plain-HTTP hotspot check that catches login-page interception before you think you're online.
The diagnosis engine then walks the completed chain, finds the first break (everything downstream is collateral), and turns it into the verdict. With no break at all it moves on to quality: a hop that answers but loses ≥ 25% of a burst counts as degradation, and where that loss starts decides whether the verdict blames your Wi-Fi, your router or your ISP.
Built for 2026, single self-contained binary:
| Layer | Choice |
|---|---|
| Language | Rust (edition 2024, MSRV 1.88) |
| TUI | ratatui 0.30 immediate-mode + crossterm 0.29 |
| Async | tokio with a select!-driven render loop |
| DNS | hickory-resolver 0.26 |
| HTTP | reqwest 0.12 (rustls) |
| CLI | clap 4 |
The OS-specific data acquisition sits behind a Platform trait, so the whole
diagnostic stack is portable — only a thin module per OS touches the system:
CoreWLAN, route and scutil on macOS; ip, iw and resolv.conf on Linux.
macOS-first (validated on macOS 26). Link telemetry comes from CoreWLAN
(objc2-core-wlan) — instant, no sudo — with system_profiler as a fallback.
Since macOS 14 the OS redacts the SSID and BSSID unless the calling app holds
Location permission, so those show as hidden in an unsigned build, while RSSI,
noise, channel, PHY and everything that actually matters for diagnosis remain
available.
Linux reads the same facts without root: rtnetlink through ip -j for routes,
addresses and tunnels, nl80211 through iw for signal, frequency, bitrate and
noise, and /etc/resolv.conf (or systemd-resolved's upstream list behind the
127.0.0.53 stub) for resolvers. Nothing is redacted there, so the SSID and
BSSID show as-is. Without iw it falls back to /proc/net/wireless for the
signal; without ip, to /proc/net/route. Prebuilt binaries cover x86_64 and
aarch64 (glibc).
On any other OS there is no platform module yet, so wtfi refuses up front — one line on stderr and exit 3 — rather than probing and reporting your network as broken. The crate still compiles everywhere, which is what keeps cargo install and the docs.rs build honest.
- Real SSID/BSSID via Location authorization in a signed
.appbundle (CoreWLAN telemetry already lands instantly today).


