feat: full tunnel mode — no certificate needed#86
feat: full tunnel mode — no certificate needed#86vahidlazio wants to merge 1 commit intotherealaleph:mainfrom
Conversation
|
Read through the draft — ambitious and interesting design. Leaving comments so you can factor in feedback before marking ready for review: On the "no CA needed" claim: the tunnel node is effectively a remote proxy that the Apps Script layer can reach. If the client does TLS directly with the destination, the tunnel node acts as a plain TCP forwarder (since it sees encrypted bytes). That's a legitimate shape — same model as an HTTPS CONNECT proxy — and does remove the CA requirement for that flow. Worth being explicit in the PR body that this trades MITM for requiring the user to stand up a tunnel node (VPS with an always-on Rust service), which is a different operational burden. Architecture question: why does traffic need to go through Apps Script at all in this mode? If the user already has a tunnel node on a VPS, Merge conflicts: the PR shows CONFLICTING against main. Since it touches Scope: 2586-line additions is a lot for one PR. If the design holds up, it'd be easier to review as three merges: (1) tunnel-node service + docker + README, (2) Rust-side Mode::Full + tunnel_client.rs, (3) Android UI surface for the new mode. Up to you. Leaving this in draft until you address the above. Happy to review each chunk as it lands. [reply via Anthropic Claude | reviewed by @therealaleph] |
1- the assumption is that the design is only suitable for scenarios when only |
1b761c9 to
8d81e58
Compare
New "full" mode that tunnels ALL traffic (TCP, TLS, HTTP) through
Apps Script to a remote tunnel node, eliminating the need for CA
certificate installation on the client device.
Architecture:
Client → mhrv-rs (no MITM) → [domain-fronted TLS] → Apps Script
→ [HTTP] → Tunnel Node (Cloud Run / VPS) → [real TCP] → Internet
Components:
- tunnel-node/: standalone Rust HTTP server that bridges tunnel
requests to real TCP connections. Docker-ready for Cloud Run or
any VPS. Supports single-op and batch modes.
- assets/apps_script/CodeFull.gs: Apps Script with tunnel forwarding
(single + batch) alongside existing HTTP relay functionality.
- src/tunnel_client.rs: batch multiplexer that collects data from
all active sessions and sends ONE Apps Script request per tick,
reducing N parallel calls to 1. Connects run in parallel across
multiple script deployments via round-robin.
- src/domain_fronter.rs: TunnelResponse, BatchOp structs and
tunnel_request() / tunnel_batch_request() methods reusing the
existing domain-fronted connection pool.
Config: "mode": "full" with same script_id/auth_key as apps_script.
Multiple script_ids are round-robined for parallel connects and
quota distribution.
UI: Mode dropdown updated in both Android (Compose) and desktop
(egui) with "Full tunnel (no cert)" option.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8d81e58 to
8337b58
Compare
|
Split into 3 smaller PRs per review feedback:
Also created #92 as a standalone fix for the |
Full Tunnel Mode — No Certificate Needed
New
"mode": "full"that tunnels all traffic (TCP, TLS, HTTP) end-to-end through Apps Script to a remote tunnel node. The browser does TLS directly with the destination server — the proxy never sees plaintext. No CA certificate installation required on the client device.Why Apps Script? Why not connect to the tunnel node directly?
If the user's ISP/network allows direct connections to a VPS, then
upstream_socks5pointing at that VPS is simpler and faster — no Apps Script hop, no 2s round-trip overhead. The Apps Script layer exists specifically for DPI bypass: when the network blocks direct connections to any non-whitelisted IP (VPS, xray, v2ray) but allows Google traffic through. Domain fronting throughscript.google.commakes the tunnel look like normal Google HTTPS to network inspection.The full mode is for the scenario: "I can't reach my VPS directly, but I can reach Google."
Why a separate tunnel node? Why can't Apps Script do it alone?
Google Apps Script can only make HTTP requests (
UrlFetchApp.fetch()). It cannot open raw TCP sockets, maintain persistent connections, or handle binary protocols like TLS handshakes. When we tunnel raw bytes (e.g. a TLS ClientHello), Apps Script has no way to forward those bytes to a destination server — it only speaks HTTP.The tunnel node solves this: it's a lightweight server that holds real TCP connections on behalf of the client. Apps Script acts as a relay — it receives base64-encoded bytes from the client via domain-fronted HTTPS, POSTs them to the tunnel node over HTTP, and the tunnel node writes them to the actual TCP socket. Response bytes flow back the same way.
Trade-offs vs apps_script mode
Full mode trades latency and operational complexity (standing up a tunnel node) for zero certificate requirements, full protocol support, and end-to-end encryption.
Architecture
Recommended: deploy the tunnel node on Google Cloud Run. Since Apps Script runs on Google's infrastructure, keeping the tunnel node on the same network minimizes the
UrlFetchApphop latency — this is the single largest contributor to round-trip time.Components
1. Tunnel Node (
tunnel-node/)Standalone Rust HTTP server that bridges HTTP tunnel requests to real TCP connections. Each client TCP connection becomes a "session" with a UUID.
Protocol —
POST /tunnel(single op):{"k":"auth","op":"connect","host":"example.com","port":443} → {"sid":"uuid","eof":false} {"k":"auth","op":"data","sid":"uuid","data":"base64"} → {"sid":"uuid","d":"base64","eof":false} {"k":"auth","op":"close","sid":"uuid"} → {"sid":"uuid","eof":true}Protocol —
POST /tunnel/batch(multiple ops in one request):{"k":"auth","ops":[ {"op":"data","sid":"uuid1","d":"base64"}, {"op":"data","sid":"uuid2","d":"base64"}, {"op":"close","sid":"uuid3"} ]} → {"r":[{...},{...},{...}]}Batch processes all active sessions in one HTTP round trip — critical since each Apps Script call takes ~2s.
Deployment — Google Cloud Run (recommended):
Cloud Run is recommended because Apps Script → Cloud Run stays on Google's internal network (lowest latency for the UrlFetchApp hop, auto HTTPS, scales to zero).
Also works on any VPS via Docker or direct binary — see
tunnel-node/README.md.2. Apps Script (
assets/apps_script/CodeFull.gs)Extends Code.gs with tunnel forwarding. All original HTTP relay functionality preserved. When request has
tfield → tunnel mode:t: "connect"/"data"/"close"→ single op to/tunnelt: "batch"→ batch to/tunnel/batch3. Batch Multiplexer (
src/tunnel_client.rs)Central coordinator that collects data from all active sessions and sends one batch request per tick:
Connects run in parallel as individual requests (each spawned on a different script ID via round-robin) because they're slow and can't block each other. Data/close ops are batched.
4. Rust Client Changes
src/config.rs:Mode::Full, same validation asapps_scriptsrc/domain_fronter.rs:TunnelResponse,BatchOp,tunnel_request(),tunnel_batch_request()— reuse existing pool + SNI rotationsrc/proxy_server.rs:TunnelMuxinit inrun()(tokio runtime required), Full mode dispatchsrc/main.rs: startup logging, cert-check skip5. UI Changes (Android + Desktop)
Mode.FULLenum, VPN service handles Full mode (skips MITM credential check per upstream ورژن اندروید کرش میکنه #73 fix), mode dropdownMultiple Script Deployments
More deployments = more parallel connects + quota headroom:
Performance
~2s per round trip through Apps Script (irreducible Google infrastructure overhead).
Bugs found and fixed during testing
TunnelMux::start()called outside tokio runtime: on Android,ProxyServer::new()runs on the JNI thread (no runtime). MovingTunnelMuxinit intorun()fixes the SIGABRT crash.scan-sniauto-discovery can produce SNI names that don't match Google's certificate. Workaround: set explicitsni_hostsin config. This is a pre-existing issue not introduced by this PR.Rebased onto current main
Rebased onto
d66f957(v1.2.6). Resolved conflict inMhrvVpnService.kt— merged upstream's #73startForegroundcrash fix with Full mode's credential check bypass.Test plan
cargo test— 69 tests pass (includes upstream's new tests)cargo build— clean for both main crate and tunnel-node🤖 Generated with Claude Code