Skip to content

Commit 48a0e46

Browse files
committed
feat: v1.9.0 — multi-edge fronting + edge DNS cache + DoH default flip + hotspot sharing
Three substantive PRs landed for this release plus an Iran-safe DoH default: - #488 by @dazzling-no-more (with credit to @patterniha): fronting_groups config field generalizes the Google-edge SNI-rewrite trick to any multi-tenant CDN edge (Vercel, Fastly, etc.). Renames `mode = "google_only"` → `mode = "direct"` with a deprecated alias keeping existing configs working. This is the v1.9.0 headline — new top-level config field + public mode-string rename are minor-bump territory. xmux moves to v1.10.0. - #494 by @dazzling-no-more: edge-cache DNS at Apps Script (CodeFull.gs) using CacheService. udp_open / port=53 ops served from cache or DoH fallback chain (Cloudflare → Google → Quad9). Cache hits drop typical first-hop DNS latency from 600-1200ms to 200-400ms. Default-on, opt-out via ENABLE_EDGE_DNS_CACHE; every failure mode falls through to existing tunnel-node forward path (zero regression). - #483 by @yyoyoian-pixel: default listen_host from 127.0.0.1 to 0.0.0.0 so an Android phone running the tunnel + an iPhone/laptop on the same hotspot can use it as proxy. Old configs with explicit 127.0.0.1 are honored (not overwritten). Plus: default `tunnel_doh: true` (flipped from false in v1.8.x) per #468 — Iran ISPs filter direct connections to dns.google, chrome.cloudflare-dns.com, and other pinned DoH hosts. The bypass-on default silently broke DNS for the dominant Iranian userbase. The safe default keeps DoH inside the tunnel; non-Iran users can opt back into the bypass for the latency win. Backwards-compatible — any config with explicit tunnel_doh keeps its setting. 169 mhrv-rs lib tests + 33 tunnel-node tests + 11 edge-DNS JS tests all passing. Clean release + UI builds.
1 parent 79cca10 commit 48a0e46

6 files changed

Lines changed: 39 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mhrv-rs"
3-
version = "1.8.5"
3+
version = "1.9.0"
44
edition = "2021"
55
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
66
license = "MIT"

docs/changelog/v1.9.0.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
2+
• **شکستگی سازگاری minor: نام‌گذاری `mode = "google_only"` به `mode = "direct"` تغییر کرد** (PR [#488](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/488) از @dazzling-no-more): نام قدیمی توصیف وضعیت رو بعد از اضافه شدن fronting_groups (که فراتر از Google می‌رسه) درست نمی‌داد. در Rust + Android + UI dropdown همه به `direct` تغییر کرده‌اند، ولی **`google_only` به‌عنوان alias deprecated در parser قابل قبول مونده** — config‌ها و saved settings قدیمی نمی‌شکنن. در Save بعدی، on-disk file خودکار به `direct` migrate می‌شه. در docs (README EN/FA, SF_README EN/FA, tunnel-node FA) note "تا قبل v1.9 نام `google_only` بود — هنوز کار می‌کنه" گذاشته شده برای کاربرانی که از راهنماهای قدیمی یا پست‌های Telegram قدیمی استفاده می‌کنن.
3+
• fronting_groups: domain fronting چند-edge برای CDN غیر-Google (Vercel، Fastly، …) (PR [#488](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/488) از @dazzling-no-more، با credit به [@patterniha/MITM-DomainFronting](https://github.com/patterniha/MITM-DomainFronting) برای technique اصلی): فیلد جدید config `fronting_groups: [{name, ip, sni, domains}]`. هر group شامل (edge IP، front SNI، domain‌های member). وقتی CONNECT به یکی از domain‌های member می‌رسه، proxy MITM می‌کنه + upstream با `ip` به‌عنوان TCP destination + `sni` به‌عنوان TLS SNI re-encrypt می‌کنه — همان trick که برای `google_ip` + `front_domain` می‌کنیم، حالا قابل تنظیم برای هر CDN multi-tenant. بر روی Google fronting (built-in) برتری داره؛ زیر `passthrough_hosts` و DoH bypass قرار داره. در `mode = full` غیر فعال (که end-to-end TLS رو حفظ می‌کنه + MITM نمی‌کنه). config مثال: `config.fronting-groups.example.json`. doc کامل: `docs/fronting-groups.md` شامل recipe انتخاب `(ip, sni)`، routing precedence، و warning صریح ⚠️ درباره cross-tenant Host-header leak failure mode (هرگز domain‌هایی که واقعاً روی edge نیستند رو list نکنید). reviews folded: SNI اعتبار رستورد روی config-load gate، `Vec<Arc<>>` به‌جای clone-on-match، byte-level dot-anchored matcher، startup warnings برای inert combos.
4+
• edge-cache DNS در CodeFull.gs برای skip کردن round-trip tunnel-node (PR [#494](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/494) از @dazzling-no-more): `udp_open` ops با port=53 در `_doTunnelBatch` intercept می‌شن + از `CacheService` (cache hit) یا DoH (cache miss) سرو می‌شن. cache hit‌ها latency typical first-hop DNS رو از ~۶۰۰-۱۲۰۰ms به ~۲۰۰-۴۰۰ms پایین می‌آرن. تغییر pure server-side در CodeFull.gs (فقط Full mode — apps_script mode UDP path نداره). بدون تغییر Rust/client. DoH fallback chain: Cloudflare → Google → Quad9 روی RFC 8484 GET. cache key per-qtype برای جلوگیری از A/AAAA collision. TTL clamping در `[30s, 6h]`. NXDOMAIN/SERVFAIL با ۴۵s negative cache. NODATA-with-SOA بر اساس RFC 2308 §5 SOA TTL رو honor می‌کنه. default-on، opt-out با `ENABLE_EDGE_DNS_CACHE`. هر failure mode به path forward موجود tunnel-node fallback می‌کنه (zero regression). انتخاب CacheService بر روی Sheets به دلیل سرعت (~۱۰ms) + privacy (volatile، روی Drive log persist نمی‌کنه — برای کاربران در صحنه‌های censorship مهمه). ۱۱ تست pure-JS pass.
5+
• default `tunnel_doh: true` (flipped از `false` در v1.8.x) ([#468](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/468)): default قبلی (DoH bypass فعال) برای کاربران ایرانی بدون نشان دادن چیزی شکست می‌خورد چون Iran ISP direct connection به `dns.google`، `chrome.cloudflare-dns.com` و سایر pinned DoH hosts رو filter می‌کنن — همان hosts که bypass در حال route مستقیم می‌فرستاد. در نتیجه، DNS lookup‌ها fail می‌گرفتن + browsing شکست می‌خورد. حالا default safe است (DoH داخل tunnel نگه داشته می‌شه، در یک شبکه فیلتر شده کار می‌کنه). کاربری روی شبکه‌هایی که direct DoH کار می‌کنه (non-Iran)، می‌تونه `tunnel_doh: false` در config بگذاره برای latency win. تغییر backwards-compatible برای configs موجود — همه‌ی configs دارای فیلد explicit `tunnel_doh` رفتار حفظ می‌شن.
6+
• اشتراک‌گذاری Hotspot iOS/laptop (PR [#483](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/483) از @yyoyoian-pixel): default `listen_host` از `127.0.0.1` به `0.0.0.0` تغییر کرده. این workflow معمول رو enable می‌کنه — یک phone Android که tunnel run می‌کنه، iPhone یا laptop روی همان hotspot WiFi می‌تونه از proxy استفاده کنه. configs قدیمی با explicit `listen_host: "127.0.0.1"` honor می‌شن (بازنویسی نمی‌شن).
7+
---
8+
**Minor breaking: `mode = "google_only"` renamed to `mode = "direct"`** (PR [#488](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/488) by @dazzling-no-more): the old name no longer described the mode now that `fronting_groups` reaches more than Google. Rust + Android + UI dropdown all updated, but **`google_only` is preserved as a deprecated alias on parse** — existing configs and saved settings don't break. On the next Save, the on-disk file migrates automatically to `direct`. Docs (README EN+FA, SF_README EN+FA, tunnel-node FA) carry a "was named `google_only` before v1.9 — old name still works" note so users following older guides / Telegram posts find their way.
9+
• `fronting_groups`: multi-edge domain fronting for non-Google CDNs (PR [#488](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/488) by @dazzling-no-more, credit to [@patterniha/MITM-DomainFronting](https://github.com/patterniha/MITM-DomainFronting) for the original technique): new config field `fronting_groups: [{name, ip, sni, domains}]`. Each group is `(edge IP, front SNI, member domains)`: when a CONNECT to one of the member domains arrives, the proxy MITMs at the local CA, then re-encrypts upstream against `ip` with `sni` as the TLS SNI — same trick we already do for `google_ip` + `front_domain`, now configurable for any multi-tenant CDN edge (Vercel, Fastly, etc.). Wins over the built-in Google SNI-rewrite suffix list; loses to `passthrough_hosts` and DoH bypass. Skipped in `mode = full` (which preserves end-to-end TLS and can't MITM). Working example at `config.fronting-groups.example.json`. Full doc at `docs/fronting-groups.md` including the recipe for picking `(ip, sni)`, routing precedence, and an explicit ⚠️ warning about the cross-tenant Host-header leak failure mode (don't list domains that aren't actually on the edge). Review fixes folded: SNI validated via rustls at config-load gate; `Vec<Arc<>>` refcount on per-CONNECT match; byte-level dot-anchored matcher (no per-match `format!()`); startup warnings for inert combos.
10+
• Edge-cache DNS in `CodeFull.gs` to skip the tunnel-node round-trip (PR [#494](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/494) by @dazzling-no-more): intercepts `udp_open` / port=53 ops in `_doTunnelBatch` and serves them from `CacheService` (cache hit) or DoH (cache miss). Cache hits drop typical first-hop DNS latency from ~600-1200ms to ~200-400ms. Pure server-side change in `CodeFull.gs` (Full mode only — apps_script mode has no UDP path); zero Rust/client changes. DoH fallback chain: Cloudflare → Google → Quad9 over RFC 8484 GET. Per-qtype cache key keeps A and AAAA from colliding. Min RR TTL clamped to `[30s, 6h]`; NXDOMAIN/SERVFAIL get a 45s negative cache; NODATA-with-SOA honors the SOA TTL per RFC 2308 §5. Default-on, opt-out via `ENABLE_EDGE_DNS_CACHE`. Every failure mode (parse error, resolver outage, key-too-long, `cache.put` rejection) falls through to the existing tunnel-node forward path — zero regression on any failure. CacheService chosen over Sheets (#443's pattern) because Sheets reads/writes are 100-500ms per op (often slower than the DoH lookup we'd be caching), have a daily-quota hazard, and persist a Drive-listed log of every domain users resolve — a real privacy regression for users in censorship contexts. CacheService is ~10ms, volatile, free, no on-disk artifact. 11 pure-JS tests covering parsers, txid non-mutation, TTL clamp, NXDOMAIN-with-SOA TTL extraction, malformed/truncated input rejection, splice correctness for mixed batches.
11+
• Default `tunnel_doh: true` (flipped from `false` in v1.8.x) ([#468](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/468)): the previous default (DoH bypass active) silently broke for Iranian users because Iran ISPs filter direct connections to `dns.google`, `chrome.cloudflare-dns.com`, and other pinned DoH hosts — exactly the hosts the bypass was routing direct. DNS resolution failed and browsing broke. The safer default keeps DoH inside the tunnel; users on networks where direct DoH works can opt back into the bypass with `tunnel_doh: false`. Backwards-compatible for existing configs — anyone who explicitly set `tunnel_doh` keeps their behavior. Iranian users on pre-v1.8.6 versions hitting this regression should upgrade.
12+
• Hotspot sharing for iOS / laptop (PR [#483](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/pull/483) by @yyoyoian-pixel): default `listen_host` changed from `127.0.0.1` to `0.0.0.0`. Enables the common workflow where an Android phone runs the tunnel and an iPhone/iPad/laptop on the same hotspot uses it as a proxy (HTTP `192.168.43.1:8080` or SOCKS5 `:1081`). For full device-wide coverage on iOS, Shadowrocket or Potatso create a local VPN that routes all traffic through the SOCKS5 on the Android phone. Old configs with explicit `"listen_host": "127.0.0.1"` are honored (not overwritten).

src/bin/ui.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ fn load_form() -> (FormState, Option<String>) {
398398
passthrough_hosts: Vec::new(),
399399
block_quic: false,
400400
disable_padding: false,
401-
tunnel_doh: false,
401+
tunnel_doh: true,
402402
bypass_doh_hosts: Vec::new(),
403403
fronting_groups: Vec::new(),
404404
}

src/config.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,25 @@ pub struct Config {
232232
/// round-trip cost paid on every name lookup. In Full mode this
233233
/// was the dominant DNS slowdown source.
234234
///
235-
/// Set `tunnel_doh: true` to keep DoH inside the tunnel. With the
235+
/// Set `tunnel_doh: false` to enable the bypass and let DoH go
236+
/// direct (saves the ~2 s Apps Script round-trip per name on
237+
/// networks where the DoH endpoints are reachable). With the
236238
/// bypass off, browsers that find their pinned DoH host
237239
/// unreachable already fall back to OS DNS on their own, so
238240
/// failure modes are graceful in either direction.
239241
///
242+
/// **Default flipped to `true` in v1.9.0** (issue #468). The
243+
/// previous default (`false` = bypass active) silently broke for
244+
/// Iranian users because Iran ISPs filter direct connections to
245+
/// `dns.google`, `chrome.cloudflare-dns.com`, etc. — exactly the
246+
/// "pinned DoH" hosts that the bypass was sending through. The
247+
/// safe default keeps DoH inside the tunnel; users on networks
248+
/// where direct DoH works can opt back into the bypass.
249+
///
240250
/// Port-gated to TCP/443 only. A private DoH on a non-standard port
241251
/// (e.g. `doh.internal.example:8443`) won't take the bypass path —
242252
/// list it in `passthrough_hosts` instead, which has no port gate.
243-
#[serde(default)]
253+
#[serde(default = "default_tunnel_doh")]
244254
pub tunnel_doh: bool,
245255

246256
/// Extra hostnames to treat as DoH endpoints in addition to the
@@ -324,6 +334,14 @@ fn default_max_ips_to_scan() -> usize { 100 }
324334
fn default_scan_batch_size() -> usize {500}
325335
fn default_google_ip_validation() -> bool {true}
326336

337+
/// Default for `tunnel_doh`: `true` (DoH stays inside the tunnel).
338+
/// Flipped from `false` in v1.9.0 per #468 — Iran ISPs filter direct
339+
/// connections to pinned DoH hosts (`dns.google`, `chrome.cloudflare-dns.com`,
340+
/// …) and the prior bypass-on default silently broke DNS for the
341+
/// dominant userbase. Users on networks where direct DoH works can
342+
/// opt back in with `tunnel_doh: false`.
343+
fn default_tunnel_doh() -> bool { true }
344+
327345
fn default_google_ip() -> String {
328346
"216.239.38.120".into()
329347
}

src/proxy_server.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,10 @@ pub struct RewriteCtx {
241241
/// the trade-off. Issue #213.
242242
pub block_quic: bool,
243243
/// If true, route DoH CONNECTs around the Apps Script tunnel via
244-
/// plain TCP. Default true via `Config::tunnel_doh = false`. See
245-
/// `DEFAULT_DOH_HOSTS` and `matches_doh_host` for matching, and
246-
/// config.rs `tunnel_doh` for the trade-off.
244+
/// plain TCP. Default false via `Config::tunnel_doh = true` (flipped
245+
/// in v1.9.0, issue #468). See `DEFAULT_DOH_HOSTS` and
246+
/// `matches_doh_host` for matching, and config.rs `tunnel_doh` for
247+
/// the trade-off.
247248
pub bypass_doh: bool,
248249
/// User-supplied DoH hostnames added to the built-in default list.
249250
/// Same matching semantics as `passthrough_hosts`.

0 commit comments

Comments
 (0)