feat: Mode::Full + batch tunnel client (#94)

Adds a new `mode: full` that tunnels ALL traffic end-to-end through Apps Script → a remote tunnel node. Browser does TLS directly with the destination. No MITM, no CA installation needed on the client device.

Ships as part of the 3-PR series: #93 (tunnel-node service + CodeFull.gs, merged) + this (Rust-side Mode::Full + batch tunnel client) + #95 (Android UI dropdown, now rolled into this PR post-rebase).

### Architecture
- Client → mhrv-rs → script.google.com (Apps Script fetch) → tunnel-node on user's VPS → real destination
- Apps Script is the transport to reach the VPS; works even when the ISP blocks direct VPS IPs
- Batch multiplexer collects data from all active sessions and ships one Apps Script request per tick

### Safety properties of this merge
- AppsScript + GoogleOnly dispatch paths are **unchanged**; Full mode is an additive branch at the top of `dispatch_tunnel`.
- `tunnel_client.rs` is a new isolated module (387 LOC).
- `tunnel_request()` is a new method on `DomainFronter`, no change to `relay()` / `relay_parallel_range()`.
- Config: additive `Mode::Full` variant + validation tests (2 new); existing validation rules untouched.
- Local build: clean compile. `cargo test --quiet`: 75 passed (73 → 75 with 2 new config tests).

### Closes
Unblocks the feature requested in #61, #69, #100, #105, #110, #111, #113, #116.

### Testing
vahidlazio has iterated on prior review feedback. End-to-end testing with a real tunnel-node deployment will follow post-merge from @Feiabyte (volunteered in #61). Post-merge CI will exercise compile + full test matrix across all targets; any regression caught there gets a fast-follow fix.
This commit is contained in:
vahidlazio
2026-04-24 11:48:56 +02:00
committed by GitHub
parent 259431b44f
commit b73bbe2106
13 changed files with 847 additions and 40 deletions
@@ -90,19 +90,11 @@ class MhrvVpnService : VpnService() {
// `ForegroundServiceDidNotStartInTimeException`. Every `stopSelf()`
// path below MUST therefore happen after a `startForeground()`
// call — otherwise the user-visible symptom is "the app crashes
// the instant I tap Start". See issue #73: user configured
// google_only mode (no deployment ID needed), which tripped the
// old early-return-before-startForeground branch.
//
// We call startForeground immediately here with the notification
// used by the normal running state; if we bail out below, we
// tear the foreground service down in an orderly way.
// the instant I tap Start". See issue #73.
startForeground(NOTIF_ID, buildNotif(cfg.listenPort))
// Deployment ID + auth key are only required in apps_script mode.
// google_only mode (bootstrap / Telegram-only use cases) runs
// with neither. Closes #73 regression where google_only users
// hit this branch and crashed on startForeground timeout.
// google_only (bootstrap) and full (tunnel) modes run without them.
val needsAppsScriptCreds = cfg.mode == Mode.APPS_SCRIPT
if (needsAppsScriptCreds && (!cfg.hasDeploymentId || cfg.authKey.isBlank())) {
Log.e(TAG, "Config is incomplete — can't start proxy in apps_script mode")