KO
|
EN
gitlite — search
Search
#python
#java
#python3
#arduino
#golang
#machine-learning
#rust
#html
#flask
#javascript
#seismology
#nodejs
WhiteProxy
★ 10
Open GitHub ↗
No description available.
Download README (.md)
Explore Similar Repositories
Smart_CV
:
AI-powered resume builder — paste a job description to optimize bullets with ATS keywords, choose from 9 styles, import PDFs, and export pixel-perfect A4. Runs entirely in your browser with your own API key.
InterviewPilot
:
把“简历 + 面试录屏 + 知识库”转成可执行复盘结果:真实问答、分析报告、模拟面试。
rednote-hackthon-opendoll
:
开源人形 OPENDOLL|为每个硅基生命创造属于自己的可爱面孔
shardofthedragon
:
Fun web projects
Website-v2
:
Teste test
// repository documentation
Was this content helpful?
★ 0
(0 ratings)
Select Rating:
★
★
★
★
★
Submit Feedback
Recent Feedback
×
Download README
Do you want to download the
README.md
file for
WhiteProxy
?
Download (.md)
# White Proxy **A pure-Go, cross-platform proxy + scanner + DPI-desync suite for defeating TLS-based censorship.** This document is a *whitepaper*, not a man page. It explains the adversary we model, the network-level primitives we exploit, and the system design that turns those primitives into a single daemon you can point a browser at. The goal is that an engineer who has never seen the source can reconstruct the system from this text alone. The code is the authority on details; this is the authority on *why*. White Proxy runs as a long-lived **daemon** that listens on `127.0.0.1:7080` and speaks SOCKS5 and HTTP-CONNECT on that one port. Behind it sit four cooperating engines: 1. a **routing engine** that races traffic across a continuously re-verified pool of "clean" edge IPs and caches the winner per destination; 2. an **MMDF** (MITM + Domain-Fronting) engine that terminates client TLS locally and re-handshakes outward under a *clean front SNI*; 3. a raw-socket **DPI-desync relay** that injects kernel-rejected decoy ClientHellos to flip the censor's verdict; and 4. a set of **scanners** that mine the clean IPs, SNIs, and proxies the other three consume. Everything is one statically-linked binary. No Python, no sidecar, no `pip install`, no files beside the binary — the ASN databases, the default SNI config, and the sub-server UI are all `go:embed`-ed. --- ## Table of contents 1. [The adversary (threat model)](#1-the-adversary-threat-model) 2. [Circumvention primitives](#2-circumvention-primitives) 3. [System architecture](#3-system-architecture) 4. [The routing engine](#4-the-routing-engine) 5. [MMDF — MITM + domain fronting](#5-mmdf--mitm--domain-fronting) 6. [The DPI-desync relay](#6-the-dpi-desync-relay) 7. [The scanners](#7-the-scanners) 8. [The subscription server](#8-the-subscription-server) 9. [Building and running](#9-building-and-running) 10. [Connecting clients](#10-connecting-clients) 11. [Data, config, and on-disk state](#11-data-config-and-on-disk-state) 12. [Measurement methodology](#12-measurement-methodology) 13. [Ethics and scope](#13-ethics-and-scope) --- ## 1. The adversary (threat model) Every design choice below follows from a small set of *measured* facts about how the censor's DPI is built. These were established by hundreds of controlled probes from a residential vantage inside the censored network (see §12). They have held across multiple regime shifts, so we treat them as architectural rather than incidental. ### 1.1 Two-lane network Traffic to the open internet is policy-routed into one of two lanes: - **Lane A (the intercept lane).** Destination prefixes the censor cares about are routed through a national-CDN ingress, past a DPI inspector, and out via a state-controlled egress pool (often a foreign transit tunnel). This lane *completes* TCP — you reach the destination — but the DPI watches the TLS handshake. - **Lane B (the default lane).** Everything else has its SYNs dropped at the edge. The destination is simply unreachable. The practical tell: a destination that is "TCP-unreachable" is on Lane B; one that is "TCP-OK but TLS-RST" is on Lane A and was *decided against* by the inspector. ### 1.2 The verdict is `(destination-IP, SNI)` — nothing else On Lane A the DPI's allow/deny decision depends **only** on the destination IP and the SNI string in the TLS ClientHello. Cipher suites, extension order, GREASE, key shares, ALPN, TLS version, and post-quantum key shares are **not** inspected. This is why fingerprint mimicry (uTLS) is unnecessary for the *verdict* — though we still use realistic fingerprints for other reasons. Two prefix policies coexist: - **tuple-strict** — default-deny; only an exact `(IP, service-domain)` pair is admitted (historically: Cloudflare customer prefixes, GitHub API edge). - **allowlist-fleet** — default-allow on the prefix minus a small explicit blocklist (historically: Google edge). Under the current relaxed regime the two classes have partly merged — most mainstream SNIs now admit on formerly tuple-strict prefixes — but a **political denylist** (`twitter.com`, `x.com`, `facebook`, `instagram`, `telegram.org`, `youtube.com`, `signal.org`, `pages.dev`, …) is RST'd on *every* prefix. ### 1.3 The block mechanism is RST-injection mid-handshake When the verdict is "deny," the censor injects a TCP RST *during the TLS handshake* — it does not silently drop. The RST's source TTL is consistent with a middlebox ~10 hops in (national-CDN intercept), not with the real destination. ### 1.4 No post-handshake re-inspection **This is the load-bearing fact.** Once a TLS handshake completes, the censor stops inspecting the flow. The inner carrier protocol (HTTP/2, WebSocket, VMess, Trojan, gRPC) needs no further evasion. If you can get the handshake through *once*, the rest of the session rides free. ### 1.5 The path preserves TTL and forwards bad-checksum packets - **TTL is preserved end-to-end.** NAT layers do not normalize it. A packet sent with TTL=6 expires at the DPI middlebox; a packet with TTL=250 reaches the destination. This makes TTL-staggered injection possible. - **Bad-checksum TCP segments leave the host and are dropped in-path, not repaired.** The DPI sees them on the wire *before* the drop point. (On Linux you must disable NIC checksum offload — `ethtool -K <iface> tx-checksumming off` — or the NIC fixes the checksum before it leaves the box.) ### 1.6 Other measured properties - **SNI matching is byte-equality against a lowercased allow-list, case-sensitive.** `HCAPTCHA.COM` ≠ `hcaptcha.com`. (A fingerprint, not a usable bypass.) - **A present-but-empty SNI extension triggers a parser bailout (timeout), not a clean allow.** An *absent* SNI extension is a valid ClientHello and is allowed. - **The censor reassembles TCP segments before matching** — naive SNI fragmentation does not work. - **PSK resumption cannot replay a denylisted SNI** — the RST lands before any session ticket is issued. - **DNS is hijacked selectively.** Plaintext UDP/53 works for neutral domains but returns a sinkhole address (`10.10.34.x`) for sensitive ones. Direct DoH (port 443 with the resolver's real SNI) is RST'd. Only *fronted* DoH survives (see §2.4). ### 1.7 The IP × SNI class model Folding the above together yields the model the whole system is built around. Every foreign IP and every SNI falls into a class: | IP class | Behavior | |---|---| | **dead** | TCP unreachable (Lane B). | | **gray** | Caps the connection after a small initial burst *unless* the SNI is whitelisted. **Almost every foreign IP — including essentially all CDN anycast — is gray.** | | **clean** | Not in the gray classifier; streams regardless of SNI. Vanishingly rare (≈1 per full prefix sweep; usually a stale prefix announcement the classifier missed). | | SNI class | Behavior | |---|---| | **white** | On the censor's whitelist; whitens any gray connection (e.g. `*.cloudflare.com`, many cheaply-registered "nim-baha" domains). | | **black** | RST'd at handshake (the political denylist). | | **grey** | Neither; only serves on a *clean* IP (e.g. your own custom worker domain). | The takeaway that drives the design: **chasing clean IPs is a losing game** (the `clean × grey` quadrant is structurally ~1-in-tens-of- thousands). The high-leverage move is **SNI-side** — pair any gray IP with a white SNI, or *spoof* a white SNI onto the wire so the censor whitens the flow. That is exactly what the desync relay and MMDF do. --- ## 2. Circumvention primitives These are the network-level tricks the system composes. Each is a direct exploitation of a fact from §1. ### 2.1 Bare-IP TLS (omit the SNI extension) The only universal carrier against a tuple-strict white-IP. Send a ClientHello with the SNI extension **completely absent** (not present-but-empty — that hits the parser-bailout timeout of §1.6). Servers accept it: Cloudflare's Universal SSL falls back to the prefix default cert, Google edges complete the handshake. In Go this is `tls.Config{ServerName: ""}` dialed to an IP literal. Useless on allowlist-fleet prefixes (where SNIs are allowed anyway) and on SNI-routed backends (you land on the wrong vhost). ### 2.2 `bad_csum` + cover-SNI (the workhorse spoof) This is the primitive that makes denylisted destinations reachable. It exploits §1.3 ∧ §1.4 ∧ §1.5 simultaneously: 1. Open a normal TCP connection to the destination IP (the 3-way handshake is **not** spoofed). 2. Sniff the post-handshake sequence/ack numbers off the wire. 3. Inject one or more **decoy** TLS ClientHellos that look like a legitimate continuation of the flow, but are deliberately **kernel-rejected** — bad TCP/IP checksum (`wrong_checksum`), or a sequence number far outside the window (`wrong_sequence`), etc. The decoy carries a **cover SNI**: a value known to be *admitted* for this destination IP (e.g. `www.hcaptcha.com` on a CF customer prefix, `www.google.com` on a Google edge). 4. The DPI sees the cover SNI on the wire and commits to "allow." The decoy never reaches the destination (the path drops the bad-checksum / out-of-window packet per §1.5). 5. Send the **real** ClientHello with the actual (denylisted) SNI. The censor has already decided and does not re-inspect (§1.4). The handshake completes. Per-attempt success is destination-dependent (≈90%+ on some Google edges, a few percent on busy CF anycast where the kernel's retransmit timer races the front's `decode_error` alert). Low rates are amortized by brute-force parallelism (§2.5). **Cover ClientHello size constraint:** the decoy's TLS record must be ≤ the path MSS (~1460 B). Oversized covers (notably post-quantum Chrome with an ML-KEM key share, ~1751 B) are silently truncated on a raw-socket send, the censor sees a partial record, and the spoof fails 100%. Keep cover fingerprints under the MSS. ### 2.3 `badcsum_ttl6` (checksum + low TTL) A sharper variant of §2.2: make the decoy *both* bad-checksum *and* TTL=6. The TTL=6 ensures the decoy expires at the DPI middlebox (internal hop ≤6) rather than relying solely on the checksum drop. Raises the censor-flip rate toward 100% on stubborn CF anycast. Long post-decoy sleeps (>50 ms) are *harmful* — fire 3–5 decoys back-to-back, then the real ClientHello with no delay. The TTL knob is **orthogonal** to the rejection method. Any `(method, TTL)` pair is valid; `badcsum_ttl6 == (wrong_checksum, TTL=6)`. ### 2.4 Fronted DoH (the only working name resolution) Because plaintext DNS is sinkholed and direct DoH is RST'd (§1.6), the system never uses the OS resolver. It resolves names by sending a DoH query to `1.1.1.1:443` wrapped in TLS with a **spoofed `www.microsoft.com` SNI** and an HTTP `Host: cloudflare-dns.com` header (with `InsecureSkipVerify`, since the cert won't match the fake SNI). The censor sees a benign Microsoft SNI; once past the SNI wall the genuine Cloudflare resolver answers truthfully. Bogon/ sinkhole answers are dropped; clean answers are cached briefly. On total DoH failure the system errors rather than dial a poisoned address. ### 2.5 Brute-force parallelism When per-attempt spoof rates are low, fire many connection attempts in parallel and let "first one through wins." §1.4 is what makes this safe to compose — a winner survives even though dozens of losing attempts were RST'd on adjacent 4-tuples. ### 2.6 MMDF fake-SNI fronting A higher-level primitive built on §2.4. Instead of spoofing a decoy packet, terminate the client's TLS locally and open a *fresh* outbound TLS connection to the **real destination backend** (resolved via fronted DoH) but with the `ServerName` rewritten to a clean front (`www.google.com`, `www.microsoft.com`). The client's original `Host:` header travels inside the encrypted tunnel, so the backend serves the correct content while the censor only ever saw the clean front SNI. Detailed in §5. (Classic *cross-domain* fronting — inner Host ≠ outer SNI on the same edge — is dead at Cloudflare's modern edge, which enforces Host↔SNI consistency. The fake-SNI variant survives because we dial the *real* backend, not the front's own edge.) --- ## 3. System architecture ### 3.1 Hard rules (the sandbox) The system is built on a few non-negotiable invariants. Reproducing them is reproducing the architecture: 1. **The daemon owns all long-lived state.** The proxy listener, routing engine, MMDF CA + profile registry, scan job manager, relay process, sub server, and config snapshot all live in one daemon process. The TUI and CLI are thin clients holding **zero** authoritative state. 2. **All IPC is JSON-RPC 2.0 over an AF_UNIX socket** (Linux/macOS native; Windows 10 1803+ supports AF_UNIX, so no named pipes). Streaming responses (event subscriptions, scan tails) are supported. 3. **Package boundaries are the API.** Cross-component calls go through stable library packages; internal packages are toolchain-private. 4. **Scanners are libraries, not subprocesses.** The daemon imports them directly and runs them as in-process pipelines — there is no stdin/JSONL wire protocol. 5. **One binary.** A single executable dispatches to every subcommand based on `argv[1]`. 6. **Embed, don't ship beside.** ASN DBs, default SNI config, and the sub-server HTML are baked in with `go:embed`. 7. **Cancellation is honored everywhere.** Every long-running goroutine threads a `context.Context` and tears down child processes (relay, masscan, nmap) on cancel. 8. **No telemetry, ever.** This runs in hostile networks; nothing phones home. ### 3.2 The single binary and its subcommands `argv[1]` selects an entry point. With no args, the TUI opens (auto-spawning a detached daemon if no socket answers). | Subcommand | Purpose | |---|---| | `daemon` | The long-lived daemon (RPC over AF_UNIX). | | `tui` | Interactive bubbletea TUI (default with no args). | | `ctl` | kubectl-style client: `status`, `down`, `config`, `events`, `route`, `scan`, `sub`, `relay`. | | `scan` | Proxy / SOCKS5 / HTTP discovery scanner. | | `whitescan` | White-IP TLS-admit scanner. | | `snicheck` / `snicheck-tui` | SNI accessibility probe (CLI / TUI). | | `routeverify` | Long-running stdin/stdout route-verify worker. | | `relay` | Standalone SNI-desync TCP relay. | The non-daemon `cmd/*` entry points are packages exposing a `Run()` that the dispatcher calls by name — there is exactly one `main()`. ### 3.3 Process topology ``` ┌──────────────┐ ┌──────────────┐ │ TUI client │ │ ctl client │ └──────┬───────┘ └──────┬───────┘ │ JSON-RPC 2.0 over AF_UNIX (streaming) └───────────┬────────┘ ▼ ┌──────────────────────────────────────────┐ │ daemon │ │ ┌────────────┐ ┌─────────┐ ┌────────┐ │ │ │ proxy │ │ MMDF │ │ scan │ │ │ │ listener │ │ manager │ │ jobs │ │ │ │ :7080 │ └─────────┘ └────────┘ │ │ └─────┬──────┘ ┌─────────┐ ┌────────┐ │ │ ▼ │ sub srv │ │ relay │ │ │ ┌────────────┐ │ :7070 │ │ :7777 │ │ │ │ route │ └─────────┘ └────────┘ │ │ │ engine │ │ │ └────────────┘ │ └──────────────────────────────────────────┘ │ │ │ edge IP pool front edges raw sockets (scan_*.json) (fronted DoH) (AF_PACKET/WinDivert) ``` A client request enters the proxy listener, which decides — per connection — whether to hand it to the MMDF manager (matched front domain), the route engine (everything else), or treat it as native. The desync relay is a *separate* listener (`:7777`) that clients can chain in front of their own VLESS/Trojan client; it is not in the default proxy path. ### 3.4 Per-user paths (XDG-style) Nothing is `cwd`-relative. Three dirs, created lazily: - **Config** (`~/.config/whiteproxy`) — JSON config, MMDF profile overrides. - **Cache** (`~/.cache/whiteproxy`) — the AF_UNIX socket, pidfile. - **Data** (`~/.local/share/whiteproxy`) — scan results, IP caches, MMDF CA + leaf certs, decision logs. --- ## 4. The routing engine The routing engine answers one question per destination host: *which endpoint do I dial, and with what SNI?* Its output is a `Decision`: either `FamilyNative` (dial the real host directly) or `FamilyWhite` (dial a specific `ip:port` from the pool, with a chosen SNI). ### 4.1 The decision pipeline For each new host the engine evaluates, in order: 1. **Policy overrides** — operator-set force-native / force-white / per-host IP bans. 2. **Prior match** — a Clash-format `ir.txt` rule list pins `.ir` and other domains to native routing. 3. **Cache hit** — an L1/L2 cache (below). On hit, return the cached endpoint immediately (sticky per host). 4. **Scan-admit lookup** — find candidate IPs from the scanned pool, in priority order: - **direct** — IPs whose scan record admitted this exact host; - **related** — IPs admitting a parent suffix; - **family** — CDN siblings (e.g. any Google edge for a Google host); - **blind** — a random sample of the pool. 5. **Wave-loop race** (below) across the shortlist. 6. First success wins and is promoted into the cache; total failure falls back to native. ### 4.2 The wave-loop race Rather than fire the whole pool at once, the engine runs a small number of **graduated waves** (default 3). Each wave: - takes a fresh shortlist (direct/family IPs first, then random blind candidates — default 24 per blind wave); - dials each candidate with a short timeout (≈1.5 s connect, ≈1.5 s TLS), optionally followed by a lightweight HTTP probe (≈1 s) used as a geofence check; - bounded concurrency (default 24), returns on the **first** verified winner (`K=1`); - a TLS-level verification (cert chain, hostname, ALPN) gates a "winner" — a TCP connect alone is not enough. Per-host race locks prevent two concurrent races for the same host. For geoblock-sensitive hosts the shortlist is restricted to IPs whose **exit country** (from the ASN DB + an exit-country probe) is known non-IR — an Iran-exit clean IP is useless for a geoblocked service. ### 4.3 The cache (the L1/L2 model) A single sticky cache with escalating TTLs, so a destination that keeps working keeps getting cheaper: | Tier | When set | TTL | |---|---|---| | **Hot (L1)** | immediately after a fresh race | ~60 s | | **Cold bump (L2)** | each cache *hit* | +5 min, capped at 30 min | | **Promote** | on a confirmed-good session close | +1 h, capped at 24 h | | **Negative** | on `MarkBad` (a route failed in use) | evict the `(host, endpoint)` pair for ~90 s | When a cached entry is within a refresh threshold (~4 s of expiry) of running out, the engine kicks off an **async** re-race so the next request never blocks. The cache is intentionally **sticky per host** — the same endpoint is reused until `MarkBad` evicts it, because stateful sessions break under per-request endpoint rotation. ### 4.4 Background liveness A background sweep re-probes a random sample of cached `(IP, SNI)` pairs every few minutes and marks dead IPs out of the pool. Because of the **ghost-cooldown** effect (an IP that just served you may "starve" on re-dial for many minutes; see §1.7), a single-touch probe has a high false-alive rate — so confirmation matters more than raw speed here. --- ## 5. MMDF — MITM + domain fronting MMDF (Man-in-the-Middle + Domain Fronting) is the engine for domains where IP routing alone fails but a clean front SNI succeeds. It is a native-Go TLS-MITM proxy — no xray sidecar — so the listener, cert chain, upstream dial, SNI substitution, and inner-Host rewriting all live in one process. ### 5.1 The CA and leaf certs The user installs a **persistent root CA** into their browser trust store *once*. Per connection, MMDF mints a leaf certificate for the requested inner SNI on demand, signed by the root, sharing one in-process ECDSA private key; leaves are cached in memory and on disk for warm restarts. Clients verify the *signature*, not key identity, so per-SNI minting is cheap. **Regenerating the CA breaks every client's trust** — it must be stable. ### 5.2 The profile registry A profile says which inner domains map to which front: ```go type Profile struct { Name string Domains []string // suffix match: host == d OR host endswith "."+d FrontSNI string // outbound TLS SNI; "" = use the client's SNI FrontHost string // host/IP to resolve for the dial; "" = use the inner host ForceALPN []string // when set, only these ALPN values are offered outbound Enabled bool } ``` Profiles are matched by **suffix, first-hit-wins**, so ordering is load-bearing (specific domains before catch-alls; do **not** alphabetize). Two modes: - **fake-SNI (`FrontHost == ""`)** — dial the **client's real backend** (resolved via fronted DoH) but rewrite the outbound SNI to a clean name. The leaf SAN check on the outbound is skipped (`InsecureSkipVerify`). This is what makes YouTube/Gemini/Instagram work: the real backend, keyed on the inner `Host`, serves the content, and the censor saw only the clean SNI. Most profiles use this. - **true-front (`FrontHost` set)** — dial a known front edge that routes on the inner `Host` (used for shared edges like Fastly). `ForceALPN` exists for **backend pickiness, not censorship** — some media backends are flaky on HTTP/2, so the outbound (and, mirrored, the client-facing inbound) is pinned to `http/1.1`. Skipping that mirror is a real bug: the browser offers `[h2, http/1.1]`, picks h2, and an h2 preface hits an http/1.1-only backend → 400. A representative default set: a **google-video** profile (`*.googlevideo.com`, front `www.google.com`, http/1.1, pinned edges), a broad **google** profile (front `www.google.com`, h2), a **fastly** true-front, and a **meta** profile (Facebook/WhatsApp/ Instagram, front `www.microsoft.com`). The list is *data*: it ships embedded but can be replaced at runtime by dropping a `mmdf_profiles.json` override in the config dir and reloading — no rebuild. ### 5.3 Per-connection flow 1. Client connects to the MMDF path (CONNECT-style). 2. MMDF reads the client ClientHello and extracts the inner SNI. 3. Suffix-match the inner SNI against the registry → a `Profile`. 4. Mint/lookup a leaf cert for the inner SNI; terminate the client TLS with it. 5. **Resolve the dial target via fronted DoH** (§2.4), never the OS resolver. fake-SNI resolves the *real inner host*; true-front resolves `FrontHost`. 6. Open an outbound TLS handshake to the resolved IP with `ServerName = Profile.FrontSNI` (and `ForceALPN` if set). 7. Pipe plaintext between the two TLS sessions — the inner HTTP request, `Host` header and all, rides inside the front's tunnel. An **excludes** anti-match list (applied *before* registry lookup) lets specific hosts skip MMDF and fall back to IP-pin routing with their real SNI — kept for future regime needs. --- ## 6. The DPI-desync relay The desync relay implements §2.2 / §2.3 as a standalone TCP proxy (default `:7777`). A client (your VLESS/Trojan client, or anything that speaks TLS) connects to it; the relay forwards bytes to a configured front IP, injecting decoy ClientHellos to flip the censor's verdict before replaying the real handshake. Putting the relay in front of a tunnel client both bypasses the SNI denylist on the first handshake *and* warms the censor's per-flow state so post-handshake shaping never engages (§1.4 / §1.7). ### 6.1 Per-connection sequence 1. **Accept** the client TLS session. 2. **Dial** the upstream front with a normal `connect()` — the 3WHS is not spoofed. 3. **Sniff** the post-3WHS seq/ack off the wire (the decoy must look like a legitimate continuation). 4. **Inject** N decoy ClientHellos (default 3): cover SNI = the admitted name for the front IP; rejection method + TTL as configured. 5. **Replay** the real ClientHello immediately, no sleep. 6. **Race** across pool members in parallel; first landed flow wins. ### 6.2 Rejection methods The method decides *how* the decoy is kernel-rejected. Names mirror sing-box's `spoof` feature for config portability: | Method | What's wrong on the decoy | Notes | |---|---|---| | `wrong_checksum` | invalid TCP or IP checksum | The workhorse; **requires NIC tx-checksum offload disabled** on Linux or the NIC repairs it. | | `wrong_sequence` | seq far outside the window | Cheap (no checksum recalc), reliably dropped on any interface. | | `wrong_acknowledgment` | invalid ack | Symmetric variant. | | `wrong_md5_sig` | bogus TCP-MD5 (RFC 2385) option | Silently path-dropped in the target network. | | `wrong_timestamp` | malformed/out-of-window TCP timestamp | Robust where offload can't be disabled. | The kernel's rejection is pure TCP-layer — win rates are flat across SNIs, confirming SNI content plays no role in the *drop* (only in the censor's *verdict*). ### 6.3 Per-platform mechanism The sniff and inject backends are the only platform-specific code: - **Linux** — sniff via `AF_PACKET` (`SOCK_RAW`, `ETH_P_ALL`); inject via `socket(AF_INET, SOCK_RAW, IPPROTO_TCP)` (the kernel writes the IP header). Needs `CAP_NET_RAW` (root) and offload disabled. - **Windows** — sniff and inject via the **WinDivert** kernel driver at the WFP layer (modern Windows has no raw-socket bad-checksum path). Needs Administrator; the driver ships bundled. - **macOS** — BPF (`/dev/bpf*`) sniff + BSD raw socket inject (the user must write the IP header). **Built but runtime-gated**: `Start`/`SelfTest` return `ErrNotSupported` pending entitlement and offload-behavior validation. - **All other platforms** — a stub returns `ErrNotSupported` so the daemon's `relay.*` RPCs compile everywhere. A loopback **self-test** sweep measures per-method × per-SNI success so the relay can auto-pick the best method for the current network. --- ## 7. The scanners Scanners are in-process library pipelines that mine the inputs the routing/MMDF/relay engines consume. They share a worker-pool engine with a cooperative pause gate, panic recovery, a stall watchdog, and a set of **speed profiles** (Throttled / Balanced / Aggressive / Turbo) that bound concurrency to the link. Results are written to siloed output files so one scanner's output never silently reclassifies another's. | Scanner | What it does | Output | |---|---|---| | **white** | The white-IP admit scanner. Stage 1 TCP liveness → stage 2 uTLS cert probe + cluster hint → stage 3 strict cert verify across an SNI panel → honeypot filter → exit-country probe for geoblock-sensitive admits. | `scan_*.json`: `{ip, port, sni, verdict ∈ accept/soft/reject/dead, exit_country}` | | **proxy** | Open-proxy discovery: TCP connect → SOCKS5/HTTP fingerprint → deep-validate (tunnel to a known endpoint, verify a real response, catch honeypots/MITM). | `{ip, port, protocol, latency, …}` | | **sni** | SNI accessibility probe: a DNS pipeline plus per-IP TLS+HTTP probing, aggregated to a per-SNI verdict (accessible / blocked / challenge / honeypot). | `{sni, profile_tag, verdict, trail}` | | **route** | Route verification: re-checks an `(IP, SNI)` pair completes TLS (and optional HTTP) exactly as the scanner saw — the engine's confirmation oracle. | `{ip, port, sni, tls_ok, http_ok, latency}` | | **speed** | Latency + up/down throughput over a found proxy; flags Telegram/Psiphon capability. | `SpeedResult` | | **targets** | Expands `ip` / `ip:port` / CIDR / `AS<n>` scopes into concrete `ip:port` endpoints (capped; default port 443). | endpoint stream | | **cache** | Persistent alive/dead IP cache with TTLs, so re-scans skip known-state IPs. | `alive_cache.json` | | **common** | The shared connect/fingerprint/validate engine + worker-pool orchestration. | — | A **verify pass is mandatory** for the white/route scanners because of ghost-cooldown (§4.4): single-touch alive rates overstate truth by ~100×. --- ## 8. The subscription server An in-daemon HTTP server (default `:7070`) that publishes the scanned/verified endpoints as ready-to-import client subscriptions. It auto-loads the latest scan results on daemon start and serves: - `GET /sub/v2ray` — base64 VLESS list; - `GET /sub/singbox` — a full sing-box JSON config; - `GET /sub/clash` — Clash/Mihomo YAML (with dialer-proxy chaining); - `GET /api/status` — JSON stats; - `POST /api/template` — add/remove a config template (loopback-only); - `GET /` — a minimal HTML landing page (embedded). The store manages templates, the white-IP pool, and fallback proxies; all mutations round-trip through `sub.*` RPCs on the daemon. --- ## 9. Building and running ```bash make # → build/whiteproxy (single binary) make whiteproxy-linux # cross-builds make whiteproxy-darwin make whiteproxy-windows make whiteproxy-all-os make wp-test # tests / vet / fmt / tidy: wp-test|wp-vet|wp-fmt|wp-tidy ``` ```bash ./build/whiteproxy # TUI (auto-spawns the daemon) ./build/whiteproxy daemon # daemon alone (default --proxy-listen=127.0.0.1:7080) ./build/whiteproxy daemon --proxy-listen="" # daemon, listener disabled ./build/whiteproxy ctl status # kubectl-style client ./build/whiteproxy ctl scan ... # status, down, events, scan.*, route.*, sub.*, relay.*, config.* ./build/whiteproxy help # full subcommand list ``` The TUI has two modes (toggle with `x`): - **white mode** — tabs: Scan / Monitor / Routing / Sub / Inspect / Settings. - **desync mode** (Linux/Windows live, macOS gated) — tabs: Control / Test / Relay. Root/Administrator is needed **only** for the desync relay (raw sockets / WinDivert). Everything else runs unprivileged. --- ## 10. Connecting clients The proxy multiplexes protocols on the single port `:7080` by sniffing the client's first bytes: | Client speaks | Configure as | |---|---| | SOCKS5 | `socks5://127.0.0.1:7080` | | HTTP CONNECT | `http://127.0.0.1:7080` | - **V2Ray / Xray / sing-box** — set the outbound to `socks` or `http` at `127.0.0.1:7080`. White Proxy becomes the *upstream* that does the actual escape; your existing Worker/VLESS config is unchanged. - **Browser** — any standard HTTP/HTTPS/SOCKS5 proxy field; a per-profile Firefox proxy is the least invasive. - **Tunnel client + relay chain** — point your VLESS/Trojan client's outbound at the desync relay (`127.0.0.1:7777`) to get the spoof primitive in front of it (§6). For MMDF to work, install the local CA (TUI MMDF pane, or `proxy.mmdf.installCA`) and restart the browser; without it, MMDF domains fall back to the routing path. --- ## 11. Data, config, and on-disk state All persistent state lives under the per-user **Data** dir; config under the **Config** dir; the socket and pidfile under **Cache**. Key artifacts: | Artifact | Purpose | |---|---| | `scan_*.json` | White-IP scan results — the routing pool. | | `alive_cache.json` | Alive/dead IP cache with TTLs. | | `ir.txt` | Clash-format prior pinning native-routed domains. | | `mmdf_ca.crt` / `mmdf_ca.key` | The persistent MMDF root CA. | | MMDF leaf cache | Per-SNI leaf certs (in-memory + on-disk). | | `mmdf_profiles.json` (config dir) | Optional runtime profile override. | | `decisions-YYYY-MM-DD.jsonl` | Per-connection routing decision log (tailed by the Monitor tab). | Config is a JSON snapshot owned by the daemon; clients read and mutate it via `config.*` RPCs. Numeric fields are clamped to safe ranges on load so a hand-edited config can't put the engine in a degenerate state. --- ## 12. Measurement methodology Every claim in §1 is empirical, not assumed. The reproducible method: - **Vantage matters absolutely.** Probes must originate *inside* the censored network on the *residential* path — not a VPN, not a datacenter. A single probe on the wrong path corrupts the model. In this project the residential path is reached through a network namespace that pins probes to the residential interface while a VPN stays up on the host for unrelated traffic. - **Distinguish the lanes.** Always separate "TCP unreachable" (Lane B) from "TCP-OK but TLS-RST" (Lane A verdict). They look similar to a browser and mean opposite things. - **Control your variables.** To prove the verdict is `(IP, SNI)`-only (§1.2), hold the IP fixed and sweep SNIs, then hold the SNI fixed and sweep fingerprints. To prove no post-handshake re-inspection (§1.4), complete a handshake via a spoof and then push sustained traffic. - **Verify, don't trust a single touch.** Ghost-cooldown (§4.4) makes single-shot results unreliable; every alive/clean claim needs a confirmation pass minutes later. - **Preflight on a neutral, known-reachable host** before a sweep, and never use a hijacked anycast (e.g. `1.1.1.1`) as a liveness oracle. The project keeps an indexed research vault (`research/`) where each finding is a numbered experiment with a one-line conclusion and a link to its raw results; the cross-cutting state (`NETWORK_STATE.md`, `INVARIANTS.md`, `PRIMITIVES.md`) is rewritten as the regime shifts. --- ## 13. Ethics and scope White Proxy exists to restore access for users whose open internet is filtered, throttled, or actively attacked. Use it on networks you own or are authorized to use. High-rate scanning of third-party infrastructure can trigger abuse complaints and may be illegal in some jurisdictions — understand your local rules before pointing a scanner at the public internet. The MMDF engine performs a deliberate TLS interception **of your own traffic on your own machine**. Do not install the local CA on devices you do not own. The system contains **no telemetry** and never reports targets, IPs, or usage off-host — by design, because it is built for hostile-network contexts where such a leak is dangerous.