First run needs the CLI to install the MITM CA into the system trust
store (sudo/admin prompt), which the UI alone can't do reliably from a
double-click. Add a small launcher for each platform that runs the CLI
with --install-cert once, then starts the UI. Each release archive now
contains a run.* script alongside the binaries.
New bin 'mhrv-rs-ui' behind the 'ui' feature flag. CLI users pay
zero egui compile cost; UI users get a single static binary.
UI features:
- Config form (Apps Script ID, auth key, Google IP, front domain,
ports, log level, verify_ssl)
- Start/Stop buttons that spawn the proxy on a dedicated tokio thread
- Live stats (relay calls, failures, cache hit rate, bytes relayed,
blacklisted scripts) polled every ~700ms
- Test button (end-to-end relay probe)
- Install CA / Check CA buttons
- Recent log panel (last 200 lines)
- Dense, dark, utility-look: no emojis, no cards, no gradients
Architecture:
- Refactored crate into lib + two bins (mhrv-rs, mhrv-rs-ui).
src/lib.rs exposes all modules, main.rs uses them via 'use mhrv_rs::...'
- New src/data_dir.rs: platform-appropriate user data dir
(~/Library/Application Support/mhrv-rs on macOS,
~/.config/mhrv-rs on Linux, %APPDATA%\mhrv-rs on Windows).
CLI falls back to ./config.json for backward compat.
- CA moves to {data_dir}/ca/ca.crt (was ./ca/ca.crt).
- UI background thread owns the tokio runtime and proxy handle;
communicates with UI via std::mpsc commands + Arc<Mutex<UiState>>.
- macOS .app bundle: assets/macos/Info.plist template + build-app.sh
that assembles .app from the binary. Bundled into release zips.
- CI: Linux system libs (libxkbcommon, libwayland, libxcb*, libx11,
libgl, libgtk-3) installed on Ubuntu runners for eframe. aarch64
Linux UI is best-effort cross-compile. Windows MinGW, macOS native.
25 lib tests still pass. 5MB release UI binary on macOS.
User reported log spam on Windows with many 'relay failed: خطای SSL'
errors for IP-literal targets like 172.105.237.214:443. Root cause:
xray/VLESS, torrent, SSH, and other app-level clients use raw IPs in
CONNECT/SOCKS5 targets. Our previous logic would MITM these, see
'POST /' inside the xhttp wrapping, forward to Apps Script, which
would then fail SSL-verifying the app's self-signed backend.
New heuristic: if the CONNECT target is an IP literal, skip MITM
entirely and do plain TCP passthrough. Reasoning: browsers never
use raw IPs in CONNECT -- they always have a domain. Any client
using an IP literal is using a custom protocol that we have no
business MITMing.
Effect: xray/VLESS tunnels now work through mhrv-rs SOCKS5 (the
app's own TLS wrap passes through untouched). Browser HTTPS still
MITM'd + relayed as before (domain CONNECTs).
Also downgraded 'relay failed' logs from error to warn so they don't
spam the ERROR channel on misrouted traffic.
Ports the SOCKS5 + fallback-chain design from @masterking32's
MasterHTTP-WithSOCKS branch so xray / Telegram / app-level TCP
clients work through this proxy.
Changes:
- New SOCKS5 listener on listen_port+1 (configurable via socks5_port)
- RFC 1928 CONNECT handshake (v5, no-auth, ATYP IPv4/domain/IPv6)
- Shared smart dispatch with the HTTP-CONNECT path
- Unified dispatch_tunnel() used by both CONNECT entry points:
1. If host matches SNI-rewrite suffix or hosts override: go direct
to google_ip via the MITM+TLS tunnel (fast path for google.com,
youtube, etc.)
2. Peek the first byte (300ms timeout for server-first protocols):
- 0x16: TLS client hello -> MITM + relay via Apps Script (scheme=https)
- HTTP method signature: HTTP relay via Apps Script (scheme=http)
- Anything else or timeout: plain TCP passthrough to the target
- handle_mitm_request() now takes a scheme arg (http/https) so the
same code path handles both MITM'd HTTPS and port-80 plain HTTP
- New plain_tcp_passthrough helper: bidirectional TCP bridge used as
the final fallback (covers MTProto / raw TCP / server-first protos)
Config:
- Added optional socks5_port field; defaults to listen_port+1
README:
- Added browser vs xray/Telegram instructions under 'Step 6'
Live-tested: HTTP proxy, HTTP proxy -> HTTPS, SOCKS5 -> HTTP,
SOCKS5 -> HTTPS, Google search via SNI-tunnel (now returns full
JS page) all pass.
Context: user reported Google search showing no-JS fallback page
('JS is off apparently'). Root cause is Apps Script's fixed
'Google-Apps-Script; beanserver' User-Agent that UrlFetchApp.fetch
does not let you override. Google detects the bot UA and serves
the degraded HTML.
Fix: add google.com to SNI_REWRITE_SUFFIXES so google.com requests
bypass Apps Script entirely and go direct to Google's edge via the
MITM+TLS tunnel. Real browser UA is sent; full JS version is served.
Also documented this and other inherent limitations (WebSockets,
2FA 'unknown device', video chunk slowness, brotli stripping) in
the README under 'Known limitations' in English + Persian so users
aren't surprised. These are platform limits of Apps Script, not
bugs -- same issues exist in the original Python project.
Two bug fixes surfaced in user testing:
1. Invalid HTTP methods forwarded to Apps Script
- Browser/xray sent HTTP/2 PRI preface through our MITM despite ALPN
being set to http/1.1 only (some clients ignore ALPN).
- Our parser accepted 'PRI' as a method and forwarded to Apps Script,
which rejected it: 'Exception: parameter provided with invalid value: method'.
- Fix: validate method against the standard list (GET/POST/PUT/DELETE/
HEAD/OPTIONS/PATCH/TRACE/CONNECT) at parse time. Non-matching requests
close the connection cleanly instead of forwarding garbage.
2. YouTube video playback broken by over-broad SNI-rewrite list
- Previous list included googlevideo.com, ytimg.com, doubleclick.net,
etc. -- but these are served from SEPARATE CDN pools, NOT from
Google's 216.239.38.120 frontend. Rewriting sent traffic to the
wrong backend, which Google dropped.
- Shrunk to a conservative list that's actually served from the
main Google frontend: youtube.com, youtu.be, youtube-nocookie.com,
fonts.googleapis.com. Everything else falls through to MITM+relay
(slower but actually works).
- YouTube video chunks now route through Apps Script which is slow
and quota-limited. This is a known limitation inherent to the
approach; same issue exists in the original Python version.
SNI-rewrite tunnels (src/proxy_server.rs):
- CONNECT to youtube.com / googlevideo.com / doubleclick / etc. now bypasses
the Apps Script relay entirely and goes direct to the Google edge IP
with SNI=front_domain.
- Accepts browser TLS with our MITM cert, opens outbound TLS to
config.google_ip with SNI=config.front_domain, bridges decrypted bytes.
- Matches Python's _do_sni_rewrite_tunnel behavior. Faster than relay for
large streams (video).
- Also respects config.hosts override map (custom IP per suffix).
gzip decode fix (src/domain_fronter.rs):
- Apps Script outer response is gzipped. Previous stub always failed,
causing 'non-utf8 json' errors. Swapped in flate2::GzDecoder.
- Verified end-to-end: HTTP and HTTPS requests through apps_script
relay succeed and return real Google IPs.