The old version landed each release cycle with a different "step 5 says to open Security settings but the code now opens top-level Settings" kind of drift. Swept the whole page and rebuilt it around what the app actually does on v1.0.2: * adds a table of contents at the top — the guide is now scan-first * requirements moved into a table so the phone/SDK/quota constraints are all visible at once * Apps Script deploy step uses a table for the New-deployment form fields (less prose to read) * step 4 (SNI tester) explains each possible row outcome in a table, with the concrete "tap Auto-detect" action for the common failure * step 5 (MITM CA) now matches the v1.0.2 flow: top-level Settings app + search "CA certificate", not a Security-settings deep-link. Search is more portable across Pixel/Samsung/Xiaomi than naming the menu path * new "UI quick reference" table mapping each control to what it does — helps users who skipped the setup prose * Known limitations tightened: Cloudflare Turnstile loop explained with the (IP, UA, JA3) binding table; IPv6 leak, UDP/QUIC, per-script quota, and the Android-7+ user-CA opt-out all kept * Troubleshooting is now a single table with symptom → cause → fix columns, including the INSTALL_FAILED_UPDATE_INCOMPATIBLE one-time note for the v1.0.1 → v1.0.2 upgrade path * new "Collecting a useful log" section: one copy-pasteable adb logcat command that captures the tags that matter (MhrvVpnService, mhrv_rs, mhrv-crash, tun2proxy) * removed the stub Persian section at the bottom — it said "file an issue if you want a translation" which is noise; re-adding only if someone actually asks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
Android app
Full guide for the mhrv-rs Android app: install, first-run setup, troubleshooting, known limits.
- Overview
- Requirements
- 1. Install the APK
- 2. Deploy the Apps Script
- 3. Enter your config in the app
- 4. Run the SNI tester
- 5. Install the MITM certificate
- 6. Start the tunnel
- UI quick reference
- Known limitations
- Troubleshooting
- Uninstall
Overview
The Android app is the exact same mhrv-rs Rust crate that powers the desktop build, wrapped in a Compose UI and fed a TUN file descriptor via VpnService + tun2proxy. Every app on the device is routed through the proxy — no per-app setup.
Any app on the device
│
▼
VpnService TUN ──► tun2proxy (in-process)
│
▼
Local SOCKS5 listener ──► mhrv-rs dispatcher
│
┌───────────────────────┤
▼ ▼
sni-rewrite tunnel Apps Script relay
(Google-owned hosts (everything else,
direct to google_ip) via your /exec URL)
Setup time: ~10 minutes if your Apps Script deployment already exists, ~15 min if you're deploying fresh.
Requirements
| Android version | 7.0 (API 24) or later |
| Device architecture | Any. The APK is universal: arm64-v8a, armeabi-v7a, x86_64, x86 |
| Google account | Yes — you'll deploy the Apps Script under it. A throwaway Gmail works |
| Screen lock | PIN, pattern, password, or biometric + fallback. Required by Android for user-CA install. Can be removed after install; the cert stays trusted |
| Data usage | ~5 MB for the APK, then ~2 MB overhead per GB of browsing (base64 + JSON wrapping) |
Scope note. mhrv-rs relays through Apps Script. That's what makes it cheap and DPI-resilient, but it's also what imposes the known limitations below. If you're evaluating against a real VPN (WireGuard/Tailscale/OpenVPN), skim that section first.
1. Install the APK
- On your phone, open the browser and go to https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases/latest.
- Download
mhrv-rs-android-universal-v*.apk. - Tap the download to open the installer.
- When Android asks "Allow this source to install apps?":
- Tap Settings
- Toggle Allow from this source
- Tap ← Back → Install
- Tap Open once install finishes.
If Android refuses with "App not installed": an old build signed with a different key is still present.
Settings → Apps → mhrv-rs → Uninstall, then try again. (From v1.0.2 onward this is a one-time thing — updates are signed with a stable key.)
2. Deploy the Apps Script
Skip this step if you already have a working /exec URL.
Do this on a laptop — it's a browser-heavy flow that's painful on a phone.
-
Go to https://script.google.com → New project.
-
Copy the full contents of
assets/apps_script/Code.gsfrom this repo. -
In the script editor, select the default
function myFunction() {}and paste over it. -
Find the line near the top:
const AUTH_KEY = "CHANGE_ME_TO_A_STRONG_SECRET";Replace the placeholder with a strong random secret (20+ chars, letters + digits). Save this value — you'll paste it into the app too.
-
File → Save (⌘S / Ctrl+S). Name the project something like
mhrv-relay. -
Deploy → New deployment.
-
Click the gear icon → Web app. Fill in:
Field Value Description mhrv-relay v1(or whatever)Execute as Me Who has access Anyone -
Click Deploy. First time only: Google asks for permissions.
- Click Authorize access → pick your account
- On "Google hasn't verified this app" → Advanced → Go to <project name> (unsafe) → Allow
-
Copy the Web app URL. It looks like
https://script.google.com/macros/s/AKfyc.../exec.
What the script does
It receives POST { method, url, headers, body_base64 } from our proxy, calls UrlFetchApp.fetch(url, ...) inside Google's datacenter, and returns { status, headers, body_base64 }. DPI bypass comes from us connecting to script.google.com using a different TLS SNI than the HTTP Host header — the ISP sees www.google.com, Google's edge routes by the Host header inside the encrypted stream.
3. Enter your config in the app
Back on the phone:
| Field | What to enter |
|---|---|
| Deployment URL(s) or script ID(s) | The /exec URL you copied. You can paste multiple — one per line — and the proxy will round-robin between them (useful when you hit the 20k/day per-script quota) |
| auth_key | The exact string you put in AUTH_KEY inside Code.gs |
| google_ip | Leave the default. The next step will auto-populate it |
| front_domain | Leave at www.google.com |
Tap anywhere outside the text fields to dismiss the keyboard.
4. Run the SNI tester
Before starting the tunnel, verify the outbound leg works. Expand SNI pool + tester and tap Test all.
| Result | Meaning | Action |
|---|---|---|
✅ Green check + NNN ms |
google_ip is reachable + accepts the SNI |
Proceed |
❌ connect timeout on every row |
Configured google_ip is unreachable |
Tap Auto-detect google_ip under the Network card, then Test all again |
❌ connect timeout on some rows |
Those specific SNIs are DPI-filtered on your network | Leave them unchecked; rotation pool uses only ticked boxes |
❌ dns: ... |
Device can't resolve www.google.com at all |
Fix Wi-Fi / airplane mode |
If you tap Auto-detect and it still fails on every row, your network is blocking Google's edge entirely — mhrv-rs can't help there.
5. Install the MITM certificate
The proxy terminates TLS locally (re-encrypts before routing through Apps Script), so your phone needs to trust a cert we minted on first run.
-
In the app, tap Install MITM certificate.
-
The confirmation dialog shows the certificate fingerprint. Tap Install.
-
The app:
- saves a PEM copy to
Downloads/mhrv-ca.crt - opens the Android Settings app
- saves a PEM copy to
-
If you don't have a screen lock — Android will prompt you to set one now. You have to. User CAs require it. You can remove it after install; the cert stays trusted.
-
In Settings, tap the search bar at the top and type
CA certificate. Open the result labelled "CA certificate" (or "Install CA certificate" on some OEMs).Don't pick "VPN & app user certificate" or "Wi-Fi certificate" — wrong category, won't work.
Searching is more reliable than navigating menus: Pixel/Samsung/Xiaomi all bury CA install under different paths, but all of them index it under "CA certificate" in search.
-
Android warns "Your network may be monitored by an unknown third party". That's us. Tap Install anyway.
-
Pick Downloads → tap mhrv-ca.crt. Give it a friendly name (or accept the default). Tap OK.
-
Switch back to the mhrv-rs app. A snackbar confirms Certificate installed ✓ — the app verifies by fingerprint against
AndroidCAStore.If it says "not yet installed", repeat step 5.
Why can't the app install the cert directly?
Android 11 removed the inline KeyChain.createInstallIntent flow. That intent used to open a category picker directly inside the app. On current Android it opens a dead-end dialog with just a Close button — Google wants CA installs to be deliberate. We do the grunt work (save file, open Settings, verify afterwards), but the manual navigation step is unavoidable.
6. Start the tunnel
- Tap Start.
- Android shows the VPN-permission dialog: "mhrv-rs wants to set up a VPN connection...". Tap OK.
- A key icon appears in the status bar. That's your VPN indicator.
- Open Chrome. Try
https://www.cloudflare.com,https://yahoo.com,https://discord.comas stress tests — all should render normally.
Expand Live logs to watch the traffic flow:
| Log line | What it means |
|---|---|
SOCKS5 CONNECT -> <host>:443 |
Browser opened a TCP flow; TUN captured it |
dispatch <host>:443 -> MITM + Apps Script relay |
Routing decision |
MITM TLS -> <host>:443 (sni=<host>) |
Our leaf cert was accepted by the browser |
relay GET https://<host>/... |
Forwarded to Apps Script |
preflight 204 <url> |
CORS preflight we answered ourselves (normal, don't worry about these) |
UI quick reference
| Control | Location | Notes |
|---|---|---|
| Deployment URL(s) or script ID(s) | Apps Script relay section | One per line; round-robin dispatch |
| auth_key | Apps Script relay section | Must match AUTH_KEY in Code.gs |
| google_ip / front_domain | Network section | Auto-detect button fills google_ip via DNS |
| Auto-detect google_ip | Under the Network row | Re-resolves www.google.com + repairs front_domain if corrupted to an IP |
| SNI pool + tester | Collapsible | Checkboxes for rotation; per-row Test + Test all |
| Advanced | Collapsible | verify_ssl, log_level, parallel_relay, upstream_socks5 |
| Start / Stop | Bottom row | 2-second debounce between taps |
| Install MITM certificate | Below Start/Stop | Save PEM → open Settings → search "CA certificate" |
| Live logs | Collapsible (below the Install button) | 500ms poll of the proxy's log ring buffer |
| v1.0.x (version badge) | Top bar, right | Tap to check GitHub for a newer release |
Known limitations
Read this before reporting a bug — most "it doesn't work" reports fall into one of these.
Cloudflare Turnstile ("Verify you are human") loops
On Cloudflare-protected sites that challenge every request, you'll solve the Turnstile, reach the page, then get challenged again on the next click. This is inherent to the Apps Script relay model:
| Factor | Normal browser | Apps Script relay |
|---|---|---|
| Egress IP | Stable (your ISP) | Rotates across Google's datacenter pool per request |
| User-Agent | Chrome's | Fixed Google-Apps-Script (locked by Google; we can't override) |
| TLS JA3/JA4 | Chrome's | Google-datacenter's |
Cloudflare's cf_clearance cookie is bound to the (IP, UA, JA3) tuple the challenge was solved against. Different IP next request → re-challenge.
Sites that only gate the first page load (most of CF's Bot Fight Mode customers) work fine after one solve. Sites that challenge every request (crypto exchanges, adult, some forums) fundamentally can't hold a session through this architecture — use a different tunnel for those.
UDP / QUIC (HTTP/3) doesn't go through
The SOCKS5 listener only handles CONNECT, not UDP ASSOCIATE. Chrome tries HTTP/3 first and falls back to HTTP/2 over TCP, which works fine. Effect: slightly slower first connect, everything else normal.
IPv6 leaks
The TUN only routes IPv4 (addRoute 0.0.0.0/0). IPv6 goes out your normal interface, including WebRTC. If you're using mhrv-rs for privacy rather than DPI bypass, disable IPv6 on your Wi-Fi network entirely.
Apps Script daily quota
Each /exec has a daily execution limit (20k/day for consumer Google accounts, higher for Workspace). Heavy streaming or infinite-scroll sites burn through it. Mitigation: deploy 2–3 scripts, paste all their /exec URLs into the app, one per line — the proxy round-robins.
Most non-browser apps ignore user CAs
By default, Android apps opt out of trusting user-installed CAs (Android 7+ Network Security Config default). Banking apps, Netflix, Spotify, most messengers — they'll fail with cert errors through mhrv-rs. The TUN routes their traffic to us; they just refuse our leaf. Only apps that explicitly opt in (browsers, curl, some developer tools) will work. This is a general MITM-proxy limitation.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
504 Relay timeout in Chrome |
Apps Script deployment not responding | Re-check the /exec URL (must end in /exec, not /dev). Watch Live logs for Relay timeout vs connect: errors |
NET::ERR_CERT_AUTHORITY_INVALID |
MITM CA not installed / not found | Redo step 5. Make sure you picked "CA certificate" in Settings, not VPN or Wi-Fi |
NET::ERR_CERT_COMMON_NAME_INVALID on Cloudflare sites |
Pre-v1.0 bug | Upgrade to v1.0.0 or later |
| JS parts of a site don't load | Pre-v1.0 OPTIONS rejection | Upgrade to v1.0.0+. If still present: Live logs → grep for Relay failed, report |
| All SNIs time out in the tester | google_ip is stale (Google rotated the A record) |
Tap Auto-detect google_ip |
| SNI tester red on some rows only | Those SNIs are DPI-filtered on your network | Uncheck the failing ones in the rotation pool |
| App closes when tapping Stop | Was a v1.0.0/1.0.1 race bug | Upgrade to v1.0.2. If still present on v1.0.2+: adb logcat -s MhrvVpnService mhrv-crash mhrv_rs and report |
INSTALL_FAILED_UPDATE_INCOMPATIBLE when upgrading |
Old APK signed with a different key (pre-v1.0.2) | Uninstall first, then install the new APK. Only a one-time thing — v1.0.2 onward has a stable signature |
| Chrome white-pages with no error | Often a rendering bug on the emulator with software GPU | Test on real hardware. Check Live logs to verify the relay is actually making requests |
| Cloudflare Turnstile loop | Known limitation | No fix inside this architecture |
| Banking/streaming apps show cert errors | Known limitation | No fix — app chose not to trust user CAs |
Collecting a useful log
If you need to report a bug:
adb logcat -c # clear
# reproduce the issue in the app
adb logcat -d | grep -E "MhrvVpnService|mhrv_rs|mhrv-crash|tun2proxy" > mhrv.log
Attach mhrv.log to your issue. Also include:
- Android version (Settings → About phone → Android version)
- OEM (Pixel / Samsung / Xiaomi / …)
- App version (tap the version badge in the top bar)
- What you did, what you expected, what happened
Uninstall
Settings → Apps → mhrv-rs → Uninstall.- Optional: remove the MITM CA —
Settings → Security → Encryption & credentials → User credentials → mhrv-rs MITM CA → Remove. (On OEMs where that path is buried, search Settings foruser credentials.) - The VPN profile is auto-revoked on uninstall — nothing to clean up there.