Commit Graph

305 Commits

Author SHA1 Message Date
yyoyoian-pixel e13bca822f fix: block DoH by default + fix Android tunnel_doh config mismatch (#763)
Problem:
PR #468 changed `tunnel_doh` default to `true` (tunnel DoH through
Apps Script) to avoid ISP-blocked DoH on censored networks. But this
added ~1.5s of Apps Script round-trip per DNS lookup — every page
load got noticeably slower because Chrome's DoH connections had to
traverse the full tunnel path before the page could even start
connecting.

The Android side had a separate bug: `tunnelDoh` defaulted to
`false` but only emitted `tunnel_doh` to JSON when `true`. Since
the Rust default is `true`, omitting the field meant Rust always
tunneled DoH regardless of the Android UI setting — bypass_doh was
silently broken on Android.

Fix:
- Add `block_doh` config option: immediately reject (RST) connections
  to known DoH endpoints. Browsers fall back to system DNS, which
  tun2proxy handles via virtual DNS (instant, zero tunnel cost).
  Eliminates the DoH round-trip without exposing DoH connections to
  the ISP (unlike bypass_doh which sends DoH direct).
- Default `block_doh: true` on Android — tested on Chrome/Brave,
  falls back to virtual DNS correctly.
- Fix Android `tunnelDoh` default to `true` (matches Rust).
- Always emit `tunnel_doh` and `block_doh` explicitly in Android
  JSON serialization — no more default-mismatch bugs.
- Add Block DoH and Bypass DoH toggles in Android Advanced UI.
  Block DoH takes priority; Bypass DoH is disabled when Block is on.

Tested on Pixel 6 Pro: zero chrome.cloudflare-dns.com tunnel
sessions with block_doh=true. All DNS resolves instantly via
tun2proxy virtual DNS.

Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 14:10:24 +03:00
github-actions[bot] b45b45f098 chore(releases): refresh prebuilt binaries for v1.9.10
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-04 16:29:33 +00:00
therealaleph c12ffd4dd4 chore: redact val.town from code and docs, rename exit-node script
The val.town founder asked us not to promote using their service. This
commit removes every val.town reference from the codebase and rewrites
the exit-node guides to be platform-agnostic.

Changes:
- Renamed assets/exit_node/valtown.ts → assets/exit_node/exit_node.ts.
  TypeScript itself is unchanged — same web-standard Request/Response/
  fetch API that runs on any serverless runtime.
- Rewrote assets/exit_node/README.md and README.fa.md to recommend
  Deno Deploy as the primary host for users who want a free serverless
  TS endpoint, with fly.io and your-own-VPS as alternatives. CF Workers
  is explicitly called out as not-helpful (CF outbound is still on
  CF's flagged IP space).
- Updated all val.town mentions in source comments (src/config.rs,
  src/domain_fronter.rs, src/bin/ui.rs) to neutral wording.
- Updated config.exit-node.example.json `_comment` strings and the
  example URL.
- Updated main README.md FAQ entries (Persian + English) and
  docs/guide.md / docs/guide.fa.md.
- Old changelog files (v1.9.4 / v1.9.5 / v1.9.9) had val.town mentions
  retroactively replaced too — same redaction principle.
- Bumped to v1.9.10 with a changelog noting the rename + Telegram
  channel brief format from earlier today.

Users who already have an exit node deployed (on whichever host they
picked) don't need to change anything — the wire protocol is identical
and the renamed script is byte-identical to the old one.

Tests: 179 lib + 35 tunnel-node green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.9.10
2026-05-04 19:11:56 +03:00
therealaleph 6c692441be ci(telegram): brief English bullets in announcement + cross-link, drop Persian-full
Telegram channel posts up through v1.9.9 inlined the full Persian half of `docs/changelog/v{version}.md` (often >2000 chars), with sub-bullets, contributor mentions, and architectural prose. In a chat-client viewport the result was an unreadable wall of mixed RTL Persian + LTR `<code>` / `<b>` spans + nested bullets that scrolled past most readers.

Switched to brief-extracted English instead:
- Added `brief_changelog(text)` — keeps only top-level `• ` bullets (drops sub-bullets), strips "by @user with full root cause + fix" / "from @user" prefatory phrases, replaces `[#nnn](url)` with `#nnn` for inline issue refs, cuts each bullet at the first natural sentence boundary (`:` after pos 30, `. `, ` — `), hard-caps at 200 chars per bullet, and trims any dangling unbalanced `(` or `[` left by the truncation.
- Both posts (files-channel announcement + main-channel cross-link) now use `english_brief = brief_changelog(english_notes)` instead of the full Persian.
- Title and footer chrome of both posts switched to English ("released" / "Files (Android, Windows, ...)" / "Channel:" / "or:").

The full Persian + full English text stays in `docs/changelog/v*.md` for archival; only the channel post becomes brief.

Verified locally on v1.9.7 / v1.9.8 / v1.9.9 — produces 246–458 char briefs with clean bullet structure, no dangling parens, no contributor noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:39:38 +03:00
github-actions[bot] 38c359f0f6 chore(releases): refresh prebuilt binaries for v1.9.9
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-04 01:02:42 +00:00
therealaleph 49b6fbfae7 fix: v1.9.9 — Android second disconnect crash + tunnel-node drain correctness
Android (#700 from @ilok67):
- Reordered MhrvVpnService.teardown() to call Native.stopProxy() FIRST. The previous order (tun2proxy.stop → tun.close → join → stopProxy) crashed SIGSEGV ~2s after Disconnect: tun2proxy's worker thread was blocked in native code on a SOCKS5 socket read; after the 2s+4s timeouts expired with the worker still alive, Native.stopProxy freed the runtime including that socket, and the worker hit use-after-free in the next read. The old comment claimed "runtime shutdown will knock the rest of the world over" — wrong, Native.stopProxy can't forcibly terminate a separate native thread, it just frees memory the other thread is still using. New order closes the socket first, the worker's blocking read returns with EOF, the worker exits cleanly through its error path, and the join is then near-instant.

tunnel-node (PR #695 from @dazzling-no-more, merged):
- Cleanup now tracks eof'd sids from drain_now's return value, not the raw atomic — was silently dropping the tail on >16 MiB buffers when EOF arrived between polls.
- Phase-1 `data` op no longer holds the sessions map across upstream write/flush — was head-of-line-blocking every other batch op.
- Mixed TCP+UDP batch wait switched from tokio::join! to tokio::select! — was paying the UDP LONGPOLL_DEADLINE (15 s) on TCP-ready bursts.
- Watcher tasks now wrapped in AbortOnDrop newtype — was leaking Arc<Inner> permits when select!'s loser arm dropped its future.
- 2 new regression tests, 35/35 pass.

Example configs:
- config.exit-node.example.json: added aistudio.google.com + ai.google.dev to default hosts (#701 — AI Studio sanctions Iran IPs).
- config.fronting-groups.example.json: PR #696 from @Shjpr9 added Reddit/Fastly/Pinterest/CNN/BuzzFeed family domains on the Fastly 151.101.x.x edge.

Tests: 179 lib + 35 tunnel-node green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.9.9
2026-05-04 03:44:20 +03:00
Shayan Jafarpour d8d03be11e Update config.fronting-groups.example.json (#696)
Added more Fastly fronting groups
2026-05-04 03:35:03 +03:00
dazzling-no-more 38d9d9fcd6 fix(tunnel-node): batch drain correctness and lock contention (#695)
* fix(tunnel-node): batch drain correctness and lock contention

* fix(tunnel-node): single-op lock contention and batch base64 consistency
2026-05-04 03:33:49 +03:00
github-actions[bot] 3cb56c36c7 chore(releases): refresh prebuilt binaries for v1.9.8
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-03 13:16:19 +00:00
therealaleph 677ec26bee fix: v1.9.8 — Android disconnect crash + UI test-button gate for non-apps_script modes
Android (#666 from @ilok67 with full root cause):
- MainActivity.onStop was sending ACTION_STOP via startService() AND immediately calling stopService() on the same service. ACTION_STOP runs teardown() on a background thread that stopSelf()s at the end; the redundant stopService() triggered onDestroy() in parallel, racing the lifecycle and crashing on every Disconnect tap. Removed the stopService() — ACTION_STOP alone is sufficient for both the live-service and the zombie-after-process-death cases. The tornDown AtomicBoolean already guards against double-teardown of native state but couldn't protect against OS-level stopSelf vs stopService race.

UI (#665 from @cmptrnb):
- Test Relay button was showing red "test result: fail" status when used in full or direct mode. The underlying test_cmd::run deliberately refuses in those modes because probing Apps Script directly while the data plane goes via tunnel-node would give a misleading result, but the refuse path was getting translated to generic "test failed". UI now checks mode before running and shows a mode-specific explainer for full/direct (point users at https://whatismyipaddress.com in the browser via the proxy as the right way to verify).

Includes already-merged PR #674 from @yyoyoian-pixel: drop client coalesce_step + tunnel-node straggler settle_step from 40 ms → 10 ms, raise tunnel-node settle max from 500 ms → 1000 ms. Asymmetric tuning: fast-fire when nothing else is queued, but adaptive coalesce on bursts. Backwards compatible — existing configs with explicit `coalesce_step_ms: 40` keep old behavior.

Tests: 179 lib + 33 tunnel-node green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.9.8
2026-05-03 15:57:53 +03:00
yyoyoian-pixel 994dd0b23c tune: lower coalesce/settle step from 40 → 10 ms, raise tunnel-node settle max to 1 s (#674)
The batch coalesce step controls how long the client (and the
tunnel-node's straggler settle) waits between checking for more ops
to pack into the same batch.  At 40 ms the wait was conservative —
good for packing uploads but needlessly slow on the download path
where the tunnel-node round-trip, not coalescing, is the bottleneck.

Lowering the step to 10 ms means we fire batches almost immediately
when there's nothing else queued, cutting ~30 ms of dead air on
every download-dominated round-trip.  When both sides DO have data
in flight (uploads, bursty page loads), the adaptive reset still
works: each arriving op resets the 10 ms step timer, so a rapid
burst naturally coalesces up to the 1 s hard cap without wasting
quota on many small batches.

In short: don't wait when there's nothing to wait for; batch
aggressively when there is.

Client side:
  - DEFAULT_COALESCE_STEP_MS  40 → 10 ms
  - DEFAULT_COALESCE_MAX_MS   unchanged at 1000 ms

Tunnel-node side:
  - STRAGGLER_SETTLE_STEP     40 → 10 ms  (matches client step)
  - STRAGGLER_SETTLE_MAX     500 → 1000 ms (more room to pack
    straggler responses when upstream targets reply at different
    speeds — saves Apps Script quota on the return leg)

Users who prefer the old behaviour can set "coalesce_step_ms": 40
in config.json.

Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 15:45:52 +03:00
therealaleph d53399dd7b docs(readme): collapse the Persian guides block into a 2-item numbered list
Replaces the YouTube thumbnail + caption + separate text-guide line with a single centered, RTL-directed paragraph containing two Persian-numerated items: ۱ for the video (YouTube) and ۲ for Kian Irani's text guide. Cleaner, less vertical space, and the (YouTube) suffix on item ۱ tells the reader where the link points without needing the big thumbnail to imply it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:47:35 +03:00
therealaleph 5ea77aedb9 docs(readme): rename video caption to "راهنمای تصویری راه اندازی به زبان فارسی"
Symmetric with the text-guide caption right below it ("راهنمای جامع متنی…") — تصویری/متنی parallel makes the video-vs-text distinction obvious at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:45:50 +03:00
therealaleph 81a0e44f12 docs(readme): single-line layout for Kian Irani's guide credit
Two links on one line: "راهنمای جامع متنی راه اندازی به زبان فارسی" → his guide, "Kian Irani" → his GitHub. Plain "با تشکر از" between them. Cleaner than the previous two-line + sub-text version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:45:00 +03:00
therealaleph 880d304bf3 docs(readme): link to @KIAN-IRANi's comprehensive Persian setup guide under the video
Adds a centered link below the video pointing to https://kian-irani.github.io/mhrv-setup-full-tunell/, with credit to @KIAN-IRANi (https://github.com/KIAN-IRANi). Both links open in a new tab. Persian-speaking users now have three escalating depths of guide right at the top of the README: the 5-min Quick Start, the YouTube walk-through, and Kian Irani's full long-form guide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:43:37 +03:00
therealaleph e91189c4f5 docs(readme): open the Persian-guide video in a new tab
Adds target="_blank" + rel="noopener noreferrer" so the click doesn't navigate the README away from the repo. The `rel` value is the conventional safety pair: `noopener` blocks the new tab from accessing `window.opener`, `noreferrer` strips the Referer header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:41:46 +03:00
therealaleph e85e42bd98 docs(readme): embed Persian setup-guide YouTube video below the language-switcher links
YouTube iframe embeds get sanitized by GitHub's README renderer, so we use the standard "thumbnail-image-linking-to-youtube.com" pattern instead — visually identical (Play overlay + auto-loaded by YouTube's CDN), survives the sanitizer, and one click opens the video on YouTube.

Caption: "راهنمای راه اندازی به فارسی:".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:41:12 +03:00
therealaleph 50bb3d0eaf ci(telegram): include Persian changelog in announcement + main-channel cross-link
Up through v1.9.7 the Telegram posts said only "📦 mhrv-rs vX.Y.Z منتشر شد" + a hashtag and a link to the files channel — subscribers had to click through to GitHub to see what actually changed. Now the announcement post in the files channel and the cross-link post in the main channel both inline the Persian half of `docs/changelog/v{version}.md`.

How it works:
- New `load_changelog(repo_root, version)` reads `docs/changelog/v{version}.md`, strips the leading `<!-- ... -->` editor comment, splits on the lone `---` line that separates Persian from English. Returns (None, None) if the file doesn't exist (lets out-of-band re-publishes for old tags whose changelog file was never landed work without crashing).
- New `md_to_tg_html(md, max_len)` does a minimal markdown → Telegram-flavoured-HTML conversion: `**bold**`, `[text](url)`, `` `code` `` are translated; nested patterns (e.g. `[`code`](url)`, `**[`code`](url)**`) work via a placeholder/unwind pass that loops until stable. Truncates at the 4096-char sendMessage limit, snapping to a newline boundary so a span isn't cut in half, with a "see full notes on GitHub" tail.
- Falls back gracefully if the changelog file is missing — uses the old skeleton message.

Verified locally on docs/changelog/v1.9.7.md: 3039 chars after conversion, well under the 4096 limit, all bold / code / link spans render correctly including nested ones (`[`assets/apps_script/Code.gs`](url)` becomes `<a href="url"><code>assets/apps_script/Code.gs</code></a>`).

This change takes effect on the next release (v1.9.8+); v1.9.7 is already published with the old skeleton.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:23:01 +03:00
github-actions[bot] b75a816334 chore(releases): refresh prebuilt binaries for v1.9.7
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 16:33:35 +00:00
therealaleph 680502759a feat: v1.9.7 — friendly LAN-share toggle + supersede unshipped v1.9.6
The desktop UI now has a single "Share with other devices on my Wi-Fi / network" checkbox in place of the cryptic `listen_host: 0.0.0.0` text field. When enabled:
- Bind auto-flips to 0.0.0.0
- LAN IP is detected via the standard UDP-connect trick (no actual traffic) and shown alongside the proxy ports for handing to the guest device
- Tooltip explains macOS Firewall prompt behavior
- A pre-existing custom bind IP in config.json is preserved with a "Custom bind: ..." badge so the next Save can't clobber it

New `src/lan_utils.rs` module with detect_lan_ip / is_share_on_lan / is_loopback_only helpers (3 unit tests).

Also rolls in the v1.9.6 changes (release was cancelled before binaries shipped):
- Code.gs / CodeFull.gs: removed duplicate doGet, switched HtmlService -> ContentService, stripped X-Forwarded-* family in SKIP_HEADERS, added SAFE_REPLAY_METHODS fallback when fetchAll throws as a whole.
- Rust client: parse_relay_json now unwraps goog.script.init iframe wrappers (defense-in-depth for legacy deployments or redirect-induced GET-on-doGet).
- README rewritten as a short bilingual landing page; advanced reference moved to docs/guide.md + docs/guide.fa.md. Persian guide's `[x]` task list replaced with a table because GitHub's RTL renderer mangles checkbox positions inside `<div dir="rtl">`.

Tests: 6 new regression tests (3 goog.script.init unwrap + 3 lan_utils). 179 lib + 33 tunnel-node tests all passing. Bind on 0.0.0.0 smoke-tested via lsof.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.9.7
2026-05-01 19:15:52 +03:00
therealaleph cbb08468bc fix: v1.9.6 — Code.gs/CodeFull.gs hardening, goog.script.init unwrap, README rewrite
Server-side (Apps Script) fixes — users replace their Code.gs with assets/apps_script/Code.gs (or CodeFull.gs for full mode) and Manage deployments → ✏️ → New version → Deploy:
- Removed duplicate doGet in Code.gs (HtmlService one was overriding ContentService one due to JS hoisting → every GET to /exec returned a goog.script.init iframe instead of the placeholder HTML)
- CodeFull.gs doGet switched from HtmlService to ContentService (same reason)
- SKIP_HEADERS now strips X-Forwarded-* / Forwarded / Via family — second line of defense to v1.2.9's client-side stripping (#104), in case a misconfigured upstream proxy adds these
- _doBatch fallback when UrlFetchApp.fetchAll() throws as a whole — per-item fetch on safe methods so one bad URL no longer poisons the entire batch (port from masterking32@3094288)

Client-side (Rust) defense-in-depth:
- parse_relay_json now unwraps goog.script.init("...userHtml...") if any deployment returns the iframe-wrapped form (legacy Code.gs, or a redirect that GETs doGet). New extract_apps_script_user_html + decode_js_string_escapes helpers. Tested against a real deployment's doGet response.

Docs:
- README rewritten as short bilingual landing page (English + Persian RTL) targeting normal users; advanced reference moved to docs/guide.md + docs/guide.fa.md.

Tests: 3 new regression tests. 176 lib + 33 tunnel-node tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:00:32 +03:00
github-actions[bot] d336bd39e5 chore(releases): refresh prebuilt binaries for v1.9.5
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 12:56:08 +00:00
therealaleph 541b37ad7d fix: v1.9.5 — exit-node tolerates TLS close without close_notify (#585)
Issue #585 from @gregtheph: v1.9.4's exit-node feature failed for every
ChatGPT/Claude/Grok request with `io: peer closed connection without
sending TLS close_notify` and fell back to direct Apps Script (which
can't reach those sites either, producing the no-json error chain).

Root cause: rustls is strict about TLS shutdown — when the peer (val.town's
host) closes the underlying TCP without first sending a TLS close_notify
alert, rustls surfaces this as `io::ErrorKind::UnexpectedEof`. Our
read_http_response propagated this as a hard error, even when the body
was already complete per Content-Length.

Fix: treat UnexpectedEof the same as `n == 0` (graceful EOF). If
Content-Length is satisfied, return the response; if mid-body truncation,
still error as BadResponse. Same handling added to the chunked reader
and the no-framing reader.

4 new regression tests:
- read_http_response_tolerates_unexpected_eof_with_content_length
- read_http_response_tolerates_unexpected_eof_no_framing
- parse_exit_node_response_unwraps_valtown_envelope
- parse_exit_node_response_surfaces_explicit_error

173 lib tests + 33 tunnel-node tests + both release builds passing.
v1.9.5
2026-05-01 15:38:47 +03:00
github-actions[bot] 7268baf098 chore(releases): refresh prebuilt binaries for v1.9.4
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-05-01 09:10:33 +00:00
therealaleph 4aac9a793f feat: v1.9.4 — exit node for ChatGPT/Claude/Grok + drop duplicate Telegram post
Two changes addressing user-reported issues today:

1. Exit-node feature ported from upstream masterking32@464a6e1d, with
   hardening. Cloudflare-protected sites (chatgpt.com, claude.ai,
   grok.com, x.com, openai.com) flag Google datacenter IPs as bots and
   return Turnstile / CAPTCHA / 502 challenges. Apps Script's UrlFetchApp
   exits from those IPs, so v1.9.3 surfaces these as "Relay error: json:
   key must be a string..." with no apps_script-mode workaround.

   Now a small TypeScript HTTP endpoint (assets/exit_node/valtown.ts)
   deployed on val.town / Deno Deploy sits between Apps Script and the
   destination. Chain: client → Apps Script (Google IP) → val.town
   (non-Google IP) → destination. Destination sees val.town's IP, no
   CF challenge.

   Config:
     "exit_node": {
       "enabled": true,
       "relay_url": "https://...web.val.run",
       "psk": "<openssl rand -hex 32>",
       "mode": "selective",
       "hosts": ["chatgpt.com", "claude.ai", "x.com", "grok.com", "openai.com"]
     }

   Hardening over upstream: PSK fail-closed if still placeholder (fresh
   deploy can't be open relay), loop guard (refuses fetch of own host),
   explicit 503 on misconfigured. Fallback to direct Apps Script on exit
   node failure (CF-affected sites fail, others keep working). Setup
   docs in English + Persian at assets/exit_node/README*.md. Example
   config at config.exit-node.example.json.

2. Removed the legacy `telegram` job from release.yml. With
   TELEGRAM_NOTIFY_ENABLED repo var set to true, every release was
   producing two duplicate APK posts on the main Telegram channel: the
   old bundled-APK-on-main job AND the newer per-file files-channel
   posts (telegram-publish-files.yml). Only the per-file flow is wanted.
   Legacy job and its helper telegram_release_notify.py are gone.
   Recoverable from git log if anyone needs the bundled pattern back.

169 mhrv-rs lib tests + 33 tunnel-node tests + UI build clean.
v1.9.4
2026-05-01 11:52:32 +03:00
github-actions[bot] d65759d8b8 chore(releases): refresh prebuilt binaries for v1.9.3
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 17:17:30 +00:00
therealaleph 55c1256fe1 feat(android): v1.9.3 — youtube_via_relay toggle in Android UI + CI retry fix
- PR #535 by @yyoyoian-pixel: Switch toggle for youtube_via_relay in
  Android Advanced settings, matching the desktop UI checkbox. Closes
  the parity gap that forced Android users to hand-edit config.json.
  Closes #520.

- ci(telegram-publish): --clobber on gh release download so retries
  survive partial downloads (caused the v1.9.2 telegram publish to
  fail; manually re-triggered).
v1.9.3
2026-04-30 19:58:23 +03:00
Shin (Former Aleph) 1929f8e14b feat(android): add youtube_via_relay toggle to Advanced settings (#535)
Parity fix: the desktop UI already has a youtube_via_relay checkbox in src/bin/ui.rs, but the Android UI was missing it — users had to hand-edit config.json on Android. By @yyoyoian-pixel.

Adds youtubeViaRelay field to MhrvConfig with JSON serialization, deserialization, and config-sharing encode. New Switch toggle in Advanced settings section matching the desktop UI checkbox. EN + FA string resources for label and helper text.

Closes #520 (vampire137 Android youtube_via_relay request).

Local verification: cargo test --lib 169/169 passing, both release builds clean. Pure Kotlin/Android change; no Rust impact.
2026-04-30 19:57:13 +03:00
therealaleph c98ae73c92 ci(telegram-publish): use --clobber to survive partial-download retries
The v1.9.2 telegram publish failed because attempt 1 hit a transient
HTTP 500 from GitHub's release-asset CDN mid-download, leaving partial
files in assets/. Attempts 2 and 3 then errored on "already exists"
because gh release download refuses to overwrite without --clobber.

With --clobber, retries can complete cleanly even when an earlier
attempt left files behind. No change to the success path.
2026-04-30 19:55:46 +03:00
yyoyoian-pixel 576dfa69e4 feat(android): add youtube_via_relay toggle to Advanced settings
Expose the `youtube_via_relay` config flag in the Android UI, matching
the desktop checkbox. Adds the field to MhrvConfig with serialization
round-trip (toJson / loadFromJson / encode), a Switch in the Advanced
section, and EN + FA string resources.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 16:28:53 +02:00
github-actions[bot] d162a47e3c chore(releases): refresh prebuilt binaries for v1.9.2
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 14:21:32 +00:00
therealaleph 8d0004c5f0 chore: v1.9.2 — Apps Script + Cloudflare Worker alternative backend
Pure docs + GAS/Worker addition shipped via PR #533 (#380 / #393 audit
task). No Rust client changes. Bumping to v1.9.2 so users get the new
deploy assets in the next release tarball + Telegram channel binaries
include the updated docs.
v1.9.2
2026-04-30 17:02:28 +03:00
Shin (Former Aleph) 1f7a1b33c5 feat(cfw): add Apps Script + Cloudflare Worker alternative backend (#533)
Adds opt-in alternative backend for mode: "apps_script". Deploy Code.cfw.gs (new GAS variant) + worker.js (Cloudflare Worker), and Apps Script becomes a thin auth+forward layer that pushes the outbound fetch to CF's edge. By @dazzling-no-more.

Closes the audit task on the v1.9.x roadmap (#380, #393).

Pure docs + GAS/Worker addition; mhrv-rs Rust client unchanged. Same JSON envelope on the wire, same mode/script_id/auth_key — only difference is what the deployed Apps Script does after authentication.

Hardened over upstream denuitt1/mhr-cfw: per-request AUTH_KEY check, fail-closed on placeholder secret, x-relay-hop loop guard + self-host fetch block, SKIP_HEADERS parity with Code.gs, batch handler with Promise.all + soft cap MAX_BATCH_SIZE = 40 paired with WORKER_BATCH_CHUNK on GAS side.

Honest limitations called out in docs:
- Not compatible with mode: "full" (raw-TCP/UDP tunnel ops not ported)
- YouTube long-form gets worse (30s CF Worker wall vs Apps Script 6min — SABR cliff arrives sooner)
- Cloudflare anti-bot unaffected (Worker IP often stricter than Google IP)
- No day-one UrlFetchApp quota relief (batch path unreachable from current single-shape client)

English + Persian docs (assets/cloudflare/README.md + README.fa.md) covering setup, three-matching-AUTH_KEY security model, trade-off table, full-mode incompatibility section.

Local verification: cargo test --lib 169/169 passing, both release builds clean.
2026-04-30 17:00:43 +03:00
dazzling-no-more 9013eb9ef7 feat(cfw): add Apps Script + Cloudflare Worker alternative backend 2026-04-30 16:41:19 +04:00
github-actions[bot] 777a28a16b chore(releases): refresh prebuilt binaries for v1.9.1
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-30 05:52:36 +00:00
therealaleph cd6ff8d381 feat: v1.9.1 — operator quality-of-life: tunable auto-blacklist, configurable batch timeout, MHRV_AUTH_KEY hint, run.bat CLI fallback
Four small fixes that address recurring user-issue patterns:

- src/config.rs / src/domain_fronter.rs: auto_blacklist_strikes,
  auto_blacklist_window_secs, auto_blacklist_cooldown_secs config fields
  (#391, #444). Previously 3 strikes / 30s window / 120s cooldown were
  hard-coded. Single-deployment users on flaky networks hit this too
  aggressively; multi-deployment users want tighter fail-fast. Defaults
  preserve historical behavior. Power-user file edit only — no UI
  control yet. Clamps to [1, 86400] for durations.

- src/config.rs / src/domain_fronter.rs / src/tunnel_client.rs:
  request_timeout_secs config field (#430, masterking32 PR #25).
  Replaces hard-coded BATCH_TIMEOUT 30s. DomainFronter::batch_timeout()
  exposes the value, fire_batch reads it. Clamped to [5s, 300s].

- tunnel-node/src/main.rs: detect MHRV_AUTH_KEY env var being set
  while TUNNEL_AUTH_KEY is unset, and emit a specific warning pointing
  at the right env var name. Catches the recurring #391/#444 docker
  run typo that made users chase phantom AUTH_KEY-mismatch decoys.

- assets/launchers/run.bat: when both UI renderers (glow + wgpu)
  fail on older Windows / RDP / VM-without-GPU, fall back to launching
  mhrv-rs.exe (CLI) instead of just printing "open an issue".
  Addresses #417 / #426 / #487. CLI has the same proxy functionality
  on 127.0.0.1:8085 (HTTP) / :8086 (SOCKS5).

169 mhrv-rs lib tests + 33 tunnel-node tests still passing. UI build
clean. ConfigWire round-trips the new fields with skip-default-on-write
so unchanged configs stay clean.
v1.9.1
2026-04-30 08:33:53 +03:00
therealaleph 58fb141cf7 docs(fronting-groups): add redd.it to the fastly group's domains list
Reddit serves images from redd.it (their CDN-style image host) which is
also on Fastly. Without this entry, the example config matches reddit.com
but image loads still fall back to direct, which is unreliable from Iran
ISPs. Suggested by @Shjpr9 in #502.
2026-04-30 06:57:34 +03:00
github-actions[bot] 7083d6eca4 chore(releases): refresh prebuilt binaries for v1.9.0
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-29 16:50:21 +00:00
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.
v1.9.0
2026-04-29 19:32:37 +03:00
Shin (Former Aleph) 79cca105de feat: multi-edge fronting_groups + rename google_only to direct (#488)
Generalizes the Google-edge SNI-rewrite trick to any multi-tenant CDN edge (Vercel, Fastly, …). By @dazzling-no-more, with credit to @patterniha for the original technique (MITM-DomainFronting).

New `fronting_groups: [{name, ip, sni, domains}]` config field — matched hosts get MITM-decrypted at the local CA and re-encrypted upstream against `ip` with `sni` as the TLS SNI. Works alongside the built-in Google fronting and `passthrough_hosts`.

Rename: `mode = "google_only"` → `mode = "direct"`. Old name kept as deprecated alias on parse — no existing config / saved settings break. UI dropdown updated, on-disk file migrates on next Save.

Review fixes folded in: SNI validated via rustls at config-load gate, Vec<Arc<>> refcount instead of clone-on-match, byte-level dot-anchored matcher (no per-match format!()), startup warnings for inert combos.

Working example at config.fronting-groups.example.json. Full doc at docs/fronting-groups.md including precedence rules + the cross-tenant Host-header leak warning.

Test plan: cargo build --release clean, cargo test --lib 169/169 passing (+8 new: dispatch matching, config validation, alias back-compat).

Per author's recommendation, this lands as the v1.9.0 headline — new top-level config field + public mode-string rename are minor-bump territory. xmux moves to v1.10.0.
2026-04-29 19:27:17 +03:00
Shin (Former Aleph) aad900ee06 feat(codefull.gs): edge-cache DNS to skip tunnel-node round-trip (#494)
Edge DNS caching at the Apps Script layer using CacheService. 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.

- 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 45s negative cache; NODATA-with-SOA honors SOA TTL per RFC 2308 §5
- Splice helper preserves op-index ordering across mixed TCP+DNS batches
- Default-on, opt-out via `ENABLE_EDGE_DNS_CACHE`; every failure mode falls through to existing tunnel-node forward path (zero regression)
- Privacy-aware: CacheService is volatile + has no on-disk artifact (vs Sheets which would persist a Drive-listed log of every domain users resolve)

11 pure-JS tests covering parsers, txid non-mutation, TTL high-bit clamp, NXDOMAIN-with-SOA TTL extraction, malformed/truncated input rejection, splice correctness for mixed batches. All 160 Rust lib tests still passing.
2026-04-29 19:25:21 +03:00
Shin (Former Aleph) d9593060fb feat: listen on all interfaces, hotspot sharing for iOS/laptop (#483)
Closes #481. Default `listen_host` from 127.0.0.1 to 0.0.0.0 so Android-phone-as-tunnel + iPhone/laptop on same hotspot can use it as proxy. Old configs with explicit `127.0.0.1` honored (not overwritten). By @yyoyoian-pixel.

Local verification: cargo test --lib 160/160 passing, both release builds clean.
2026-04-29 19:24:07 +03:00
dazzling-no-more 72ed6d893a feat(codefull.gs): edge-cache DNS to skip tunnel-node round-trip 2026-04-29 19:10:19 +04:00
dazzling-no-more f32d343260 docs(fronting-groups): add netlify (CloudFront) example 2026-04-29 17:28:49 +04:00
dazzling-no-more 8ed8e85687 feat: multi-edge fronting_groups + rename google_only to direct 2026-04-29 17:12:15 +04:00
yyoyoian-pixel ca171f702a feat: listen on all interfaces by default, add hotspot sharing docs
Change default `listen_host` from `127.0.0.1` to `0.0.0.0` so the
proxy is reachable from other devices on the same network. This
enables hotspot sharing: run the app on Android with hotspot enabled,
and an iPhone/iPad/laptop on the same WiFi can use the proxy by
pointing at the Android's hotspot IP (192.168.43.1:8080 for HTTP,
:1081 for SOCKS5).

On iOS, apps like Shadowrocket or Potatso can create a local VPN
that routes all device traffic through the SOCKS5 proxy — giving
full tunnel coverage without installing the app on the iOS device.

Added a "Sharing via hotspot" section to README with setup steps
for iOS, macOS, and Windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 14:17:31 +02:00
github-actions[bot] 23911ae93a chore(releases): refresh prebuilt binaries for v1.8.5
Auto-committed by release workflow so users behind GitHub-Releases-page filtering can download via the in-repo releases/ folder. The GitHub Release page itself still has the canonical versioned artifacts; this folder is the fallback path for users who can only reach the static source tree (Code → Download ZIP).
2026-04-29 03:45:13 +00:00
therealaleph 75401acf0c fix: v1.8.5 — tunnel-node caps TCP drain at 16 MiB to stay under Apps Script body ceiling
@bankbunk reported (#460) that on a 1 Gbps VPS, raw MP4 streams in Full
mode died with `batch JSON parse error: EOF while parsing a string at
line 1 column 52428685` minutes into playback. Root cause: drain_now
took the entire per-session read buffer in one shot. On high-bandwidth
VPS the reader task fills the buffer with tens of MiB between polls;
the resulting batch response (raw + base64 1.33× + JSON envelope)
exceeded Apps Script's ~50 MiB hard cap; Apps Script truncated mid-base64;
the client's serde_json parse hit EOF and the stream tore.

Fix: drain_now now returns at most TCP_DRAIN_MAX_BYTES (16 MiB) per call
and leaves the tail in the buffer for the next poll. EOF is held back
until the buffer is fully drained so partial drains don't tear the
session prematurely. Three regression tests cover the cap, the under-cap
pass-through, and the EOF-holdback case (33 tunnel-node tests passing).

@bankbunk's wondershaper rate-limit workaround (40 Mbps cap on the VPS
interface) is no longer necessary — high-bandwidth VPS users can run at
line rate again.
v1.8.5
2026-04-29 06:28:17 +03:00
therealaleph 8273325e4c docs(maintainer): add maintainer knowledge base for contributors and automated agents 2026-04-29 04:53:52 +03:00
therealaleph 61196ea6e6 Revert "docs(maintainer): add skill knowledge base for cloud-scheduled DOPR agents"
This reverts commit 9f821cc7580c8fe012346c451c86891b8b185485.
2026-04-29 04:45:25 +03:00