Nullroute is a concurrent proxy checker with a fully native desktop GUI (Rust + iced 0.14 — no WebView, no HTML/CSS/JS): it verifies HTTP, HTTPS, SOCKS4/4a and SOCKS5 proxies (with optional authentication), pulls additional candidates from configurable web sources and Telegram channels (MTProto proxy links), and classifies each live proxy's anonymity level.
The engine (proxy checking, sources, Telegram/MTProto, history) is UI-framework-agnostic; the interface itself has gone through two prior forms (a Go/Bubble Tea TUI, then a Rust/Tauri + HTML/CSS/JS GUI) before landing on this native iced rewrite. iced has no backdrop-filter-style blur, so the earlier "glass panel" look is approximated with flat semi-transparent panels + borders rather than reproduced pixel-for-pixel.
The window uses its own titlebar instead of the OS-drawn one (decorations: false): drag any blank part of the bar to move the window, —/□/× to minimize/maximize/close. Starts at 860×720, resizable down to 600×500. Theme is dark (oklch(0.19 0.004 260) background, white text) with a single accent color (oklch(0.65 0.19 41) → #ea5b18); the logo in the titlebar is drawn with iced::widget::canvas from the same path data as the source SVG, not a rasterized image — "null" renders in white, "route" in the accent color.
A language picker in the titlebar (EN/RU/ES/DE/FR/中文) switches every screen's UI strings via src/gui/i18n.rs, a plain key → per-language string table (no external i18n crate/format needed for a UI this size). The choice is persisted in sources.yaml (language field) and restored on next launch.
cargo run --release # builds and launches the app directly
cargo build --release # release binary → target/release/nullroute.exe
No Tauri CLI, no WebView2 runtime, no bundler — this is a single ordinary Rust binary.
Pushing a version tag (git tag v0.1.0 && git push origin v0.1.0) triggers .github/workflows/release.yml, which builds Windows, Linux, and macOS (Intel + Apple Silicon) binaries and attaches them to a GitHub Release.
The app opens on a Home screen with two entry points (fetch from sources/Telegram, or check a local file); every other screen is reached through a hover-expand sidebar on the left (collapses to an icon strip, widens to show labels while the pointer is over it) — this sidebar is the only navigation in the app, there are no in-page back buttons. Six sidebar entries: Home, Sources, Telegram, File Check, Results, History (Sources and Telegram both open the same merged page).
| Field | Default |
|---|---|
| Proxy list file(s) | proxy.txt |
| Parallel checks | 50 |
| Per-proxy timeout | 8s |
| Judge endpoints (enable/order) | all 3 built-ins |
Type into a field, click start check to start.
- Multiple proxy list files: the file field accepts comma- or newline-separated paths; all are read and merged (deduped) with fetched sources before a check starts.
- Judge endpoints: checkboxes for httpbin.org / postman-echo.com / api.ipify.org. Arbitrary custom URLs aren't supported — each built-in has its own hand-written response parser (see
judge.rs), so this only lets you enable/disable the 3 built-ins, not add new ones.
Fetches additional proxy candidates from URLs configured in sources.yaml (auto-created with a starter set on first run), merged with the file-check file(s) (deduped by host:port) when a check starts. The Telegram/MTProto section (see below) lives on the same page, underneath the web-sources list.
- Checkbox per source — click a row to enable/disable it; persisted back to
sources.yaml. - Type badge —
API(blue),GITHUB(purple),SCRAPE(orange). This is a display category — see the parsing caveat below. - Auto-refresh — click the toggle, then
[ − ]/[ + ]to adjust the interval (1–60 min, default 10); a countdown ticks down to the next automatic fetch, driven by iced's owntime::everysubscription (included at the top level regardless of which screen is showing) rather than a separate scheduler crate. - Fetch now — fetch immediately.
Parsing caveat: api/github sources are extracted generically (every scheme://[user:pass@]host:port or bare host:port token found in the response, tolerant of JSON or plain text). scrape sources are parsed with a real HTML parser (the scraper crate) reading each table row's IP and port cells separately and rejoining them — which correctly handles sites that render them in separate <td> cells with no colon between them, a case a regex-only extractor can never recover.
Harvests MTProto proxy links (tg://proxy?server=...&port=...&secret=...) posted in Telegram channels, using a login to your own Telegram account (via grammers, a pure-Rust MTProto client). The panel shows a step indicator (connect → verify → ready) and a status pill reflecting where you are in the flow.
- First-time setup: enter your API ID/hash (obtained yourself from my.telegram.org — never embedded in the app) and phone number, then the code Telegram sends you, then a 2FA password if your account has one enabled. Either of the code/password steps can be cancelled to bail back to the start. The session is saved locally (
telegram.session, a SQLite file) so you don't need to log in again on later launches. - Channels: add/remove usernames of channels that post proxies; each fetch scans their last 200 messages for proxy links.
- Fetch mtproto proxies now: scans the configured channels and merges found proxies into the next check, alongside the file-check file(s) and other sources.
- Log out: clears the saved session.
Checking caveat: MTProto has no HTTP judge to query, so these proxies get a best-effort TCP liveness probe (connect + send an obfuscated-transport-shaped header, confirm the connection doesn't immediately reset) instead of the elite/anonymous/transparent classification other schemes get — the results table shows them as alive/dead + latency only, anonymity blank.
- Filter tabs (
All/Alive/Dead/Elite/Anon/Trans) — click to filter the table. - Search box — filters by host:port substring.
- Protocol filter — narrow to one scheme (http/https/socks4/socks4a/socks5/mtproto).
- Row click — opens a details panel (full proxy string, protocol, status, anonymity, latency, error).
- Export — writes the currently filtered/sorted rows to a timestamped CSV file next to the app.
- Start/Stop button — click to stop an in-progress check, or start a new one (reusing the last-entered settings). Every proxy shows up immediately as
CHECKINGand flips toALIVE/DEADin place as results stream in. - Column headers — click to sort by that column; click again to reverse order.
- Sidebar panel — live counts for elite/anonymous/transparent among alive proxies, plus dead/checking counts.
- Progress bar — checked/total, animated.
- Sparkline — checks/sec, filled area + smoothed curve, drawn on an
iced::widget::canvas.
On completion (or after Stop), results are written to working.txt and results.csv, and a summary row is appended to history.jsonl.
A newest-first log of past check runs (when, file(s), total/alive/dead, average latency, concurrency) read from history.jsonl. Clear history truncates the log.
One proxy per line:
[scheme://][user:pass@]host:port
schemeis one ofhttp(default if omitted),https,socks4,socks4a,socks5.- Blank lines and lines starting with
#are ignored.
Examples:
1.2.3.4:8080
http://1.2.3.4:8080
socks5://user:pass@5.6.7.8:1080
socks4a://proxy.example.com:1080
working.txt— one working proxy per line, in the same normalized form as the input (scheme://[user:pass@]host:port).results.csv— one row per checked proxy:proxy,status,anonymity,latency_ms.
Each live proxy is checked against a judge endpoint (which echoes back the request headers/IP it received) and classified as:
- elite — no proxy-revealing headers, real IP not leaked.
- anonymous — reveals that a proxy is in use (
Via,X-Forwarded-For, etc.) but not your real IP. - transparent — leaks your real IP to the destination.
Judge endpoints are tried with fallback (httpbin.org → postman-echo.com → api.ipify.org) so a single endpoint being down/rate-limited doesn't cause false negatives.
src/
├── engine/ # checking logic — proxy parsing/dialing, judge classification, worker pool, I/O
│ ├── proxyaddr.rs # parses [scheme://][user:pass@]host:port (+ tg://proxy?... for mtproto); builds reqwest clients for http/https/socks5
│ ├── socks4.rs # hand-rolled SOCKS4/4a client (no crate covers it) + TLS-capable fetch over the tunnel
│ ├── judge.rs # judge endpoint fallback chain + elite/anonymous/transparent classification
│ ├── mtproto_check.rs # best-effort MTProto proxy liveness probe (no anonymity concept applies)
│ ├── worker.rs # concurrent checking (JoinSet + mpsc), streamed into the GUI as iced Task progress
│ └── io.rs # proxy.txt (BOM-safe) read, working.txt/results.csv write
├── sources/ # the Sources screen's backend
│ ├── api.rs / github.rs / scrape.rs # per-type fetchers
│ └── dedup.rs, config.rs
├── telegram/ # Telegram/MTProto proxy source
│ ├── client.rs # grammers-based connect/login/logout helpers + session persistence
│ ├── extract.rs # regex extraction of tg://proxy / t.me/proxy links from message text
│ └── scan.rs # per-channel message history scan → dedup
├── history.rs # check-run history log (history.jsonl)
├── paths.rs # resolves relative paths against the exe's own directory (works under `cargo run` too)
├── gui/ # the native iced interface — no code here talks to any engine internals directly
│ ├── mod.rs # App state, top-level Message enum, update/view/subscription wiring, run()
│ ├── theme.rs # custom iced::Theme::custom palette + shared container styles
│ ├── home_screen.rs # the Home screen (two entry-point cards)
│ ├── sidebar.rs # the hover-expand navigation sidebar
│ ├── icons.rs # embedded SVG icon lookup (src/assets/icons/*.svg)
│ ├── settings.rs, sources_screen.rs, telegram_screen.rs, results.rs, history_screen.rs
│ └── (each screen owns its own State/Message and update/view functions)
├── assets/
│ ├── fonts/JetBrainsMono-{Regular,Bold}.ttf
│ └── icons/*.svg # sidebar/home-screen icons, rendered via iced::widget::svg
├── lib.rs # pub mod engine/gui/history/paths/sources/telegram
└── main.rs # fn main() -> iced::Result { nullroute_lib::gui::run() }
Each screen's own async work (starting a check, fetching sources, connecting to Telegram) runs as an iced::Task — Task::sip/sipper for streaming progress (proxy-check results, per-source fetch progress), Task::perform for one-shot async calls — driven by iced's own executor (the tokio feature makes that executor a real tokio runtime, so the engine's existing tokio::spawn/reqwest/grammers code works unmodified).
- Rust (stable, MSVC toolchain on Windows) — that's it. No Tauri CLI, no WebView2, no bundler;
iced'swgpurenderer just needs a normal graphics driver (falls back to a software renderer if none is available).