Files
therealaleph 48a0e469c0 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.
2026-04-29 19:32:37 +03:00

10 KiB

شکستگی سازگاری minor: نام‌گذاری mode = "google_only" به mode = "direct" تغییر کرد (PR #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 قدیمی استفاده می‌کنن. • fronting_groups: domain fronting چند-edge برای CDN غیر-Google (Vercel، Fastly، …) (PR #488 از @dazzling-no-more، با credit به @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. • edge-cache DNS در CodeFull.gs برای skip کردن round-trip tunnel-node (PR #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. • default tunnel_doh: true (flipped از false در v1.8.x) (#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 رفتار حفظ می‌شن. • اشتراک‌گذاری Hotspot iOS/laptop (PR #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 می‌شن (بازنویسی نمی‌شن).

Minor breaking: mode = "google_only" renamed to mode = "direct" (PR #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. • fronting_groups: multi-edge domain fronting for non-Google CDNs (PR #488 by @dazzling-no-more, credit to @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. • Edge-cache DNS in CodeFull.gs to skip the tunnel-node round-trip (PR #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. • Default tunnel_doh: true (flipped from false in v1.8.x) (#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. • Hotspot sharing for iOS / laptop (PR #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).