feat(android): add youtube_via_relay toggle to Advanced settings#3
Open
yyoyoian-pixel wants to merge 96 commits intomainfrom
Open
feat(android): add youtube_via_relay toggle to Advanced settings#3yyoyoian-pixel wants to merge 96 commits intomainfrom
yyoyoian-pixel wants to merge 96 commits intomainfrom
Conversation
…ecycle-reliability fix(android): tighten VPN session lifecycle reliability
Five small but real Android-only fixes: 1. Connect/Disconnect button gated on VpnState.isRunning state-flow with 12s backstop, replacing the fixed 2s transitionCooldown timer. Closes the race where a tap-after-Stop hit "Address already in use" because the previous teardown's listener-socket release wasn't done. 2. Tun2proxy.stop() wrapped in 2s join() — if the native call hangs, bounded teardown still releases the listener port instead of holding the teardown thread. 3. fd-leak fixed between parcelFd.detachFd() and Thread.start(): an OOM-thrown Thread.start used to orphan the detached fd. Now adopted into a fresh ParcelFileDescriptor purely so we can close() it. 4. Misleading teardown doc-comment rewritten — the "step 2 closes the TUN fd to force EBADF on read" claim has been factually wrong since detachFd landed. 5. Recursive crash trap: Log.e in MhrvApp's uncaught handler now wrapped in try/catch so a logd failure during exception logging falls through to the previous handler with the real exception. No Rust changes; 98 lib + 22 tunnel-node tests still pass. Local Android build verified, APK installed on mhrv_test emulator, launches cleanly with v1.6.1 in title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In `relay_parallel_range`, when a chunk failed validation (`extract_exact_range_body` returned Err) OR the stitched body length didn't match the advertised total, the fallback path called `rewrite_206_to_200(&first)` — which converted the 256 KiB probe response into HTTP 200 + Content-Length=262144 and returned that as if it were the full file. Browsers saw a complete-looking 200 and treated the download as finished at 256 KB. Common triggers for the chunk-validation failure (per the user reports): - Apps Script's UrlFetchApp stripping `Content-Range` from chunk responses while preserving it on the probe - Origin returning 200-OK on follow-up Range requests (some servers flatten ranges after the first one) - Mismatched `total` field across chunks for paths behind a varying cache layer The correct fallback is a single GET without any Range header — Apps Script fetches the whole URL (up to its 50 MiB cap) and returns a normal 200 with the complete body. Slower than parallel for large files but produces a correct response, which is the minimum bar. Two independent reports (Ehsan in therealaleph#162, Recruit1992 confirming). 98 lib tests still pass; existing `validate_probe_range_rejects_*` and `extract_exact_range_body_*` tests already cover the validation side, the fallback path is observed integration-testing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildNotif() hardcoded `proxyPort + 1` for the SOCKS5 line, ignoring cfg.socks5Port entirely. With the default Android config (listenPort=8080, socks5Port=1081) the foreground notification read "Routing via SOCKS5 127.0.0.1:8081" but the real listener was on 1081 — so users configuring per-app SOCKS5 (Telegram, etc.) against the notification value silently failed. Use the same `cfg.socks5Port ?: (cfg.listenPort + 1)` elvis fallback the real listener uses, and surface both ports in the notification: HTTP 127.0.0.1:8080 · SOCKS5 127.0.0.1:1081 Reported by vpnineh and l3est (with netstat screenshots showing the exact mismatch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The batch-build loop blocked on a 30 ms timeout for the first message, then drained whatever else was in the channel via try_recv() and fired the batch. Under any non-bursty workload, the channel queue was always empty by the time the first op woke us up — so every "batch" had exactly one op, defeating the entire batching premise. Reporter (w0l4i) saw `batch: 1 ops → ..., rtt=6.3 s` repeating in logs even under high concurrency. Fix: after the first op lands, hold the buffer open for an 8 ms coalescing window. Concurrent ops (parallel fetches, HTTP/2 stream openings, etc.) now accumulate into the same batch. 8 ms is rounding error against the 2–7 s Apps Script RTT we're amortizing, and restores the multi-op-per-batch behavior the rest of the code already supports (MAX_BATCH_OPS=50, MAX_BATCH_PAYLOAD_BYTES=4 MiB). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: include twitter.com in X.com URL normalization (therealaleph#245)
- therealaleph#245 (@Parsa307): match twitter.com in X.com URL normalization - therealaleph#255 (@dazzling-no-more): copy-logs button + selectable log lines on Android - therealaleph#257 (@dazzling-no-more): bulk paste of multiple deployment IDs on Android - therealaleph#256 (@dazzling-no-more): plain HTTP proxy passthrough in google_only mode (used to return 502; now falls through to direct TCP / upstream_socks5, matching the existing CONNECT behavior) No protocol or wire-format changes; existing config and Apps Script deployments work unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…table VoIP, faster browsing - needs new tunnel deployment for udpgw (therealaleph#222) * feat: native udpgw protocol alongside existing UDP associate Why udpgw is needed even with UDP associate: UDP associate (udp_open/udp_data) creates one tunnel session per UDP destination and polls each independently. On high-latency or shaky networks this compounds — N simultaneous UDP flows need N separate polling loops, each paying its own batch round-trip overhead. Google Meet calls, which fire dozens of concurrent STUN + RTP flows, stall or fail entirely because the per-destination polling can't keep up. udpgw multiplexes ALL UDP over one persistent TCP-like session using conn_id framing. One batch op carries frames for many destinations. Persistent sockets per (conn_id, dest) with continuous reader tasks keep source ports stable — critical for protocols like Telegram VoIP and STUN that expect replies on the same port. Both paths coexist — they serve different traffic: - UDP associate (SOCKS5): apps that negotiate SOCKS5 UDP relay - udpgw (198.18.0.1:7300): TUN-captured UDP (DNS, QUIC, Meet, etc.) tun2proxy vendored as git submodule at v0.7.20 with one transparent commit adding udpgw_server to the Android JNI run() function. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: block QUIC (UDP 443) and DNS (UDP 53) from udpgw QUIC through udpgw is slower than TCP/HTTP2 through the batch pipeline — blocking it forces browsers to fall back to TCP, improving YouTube and general browsing speed. DNS is better handled by tun2proxy's virtual DNS / SOCKS5 UDP associate path which is more reliable for single request-response exchanges. VoIP (Telegram, Meet) still flows through udpgw normally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace submodule with [patch.crates-io] for tun2proxy udpgw Use the idiomatic Rust [patch.crates-io] mechanism instead of a git submodule. Points to yyoyoian-pixel/tun2proxy fork with the udpgw JNI parameter patch (upstream PR: tun2proxy/tun2proxy#247). Will be removed once upstream ships the change in tun2proxy >= 0.8. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: pin tun2proxy patch SHA in Cargo.lock Locks tun2proxy at dfc24ed1 so the patch resolution is recorded and any branch rewrite is visible in the lockfile diff. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use AbortHandle for ConnSocket readers to prevent FD leaks JoinHandle::drop detaches the task without aborting it. When udpgw_server_task is cancelled (session close), the post-loop cleanup never runs and per-(conn_id, dest) reader tasks become zombies holding Arc<UdpSocket> file descriptors. AbortHandle::drop aborts the task automatically, so cleanup is correct by construction regardless of how the parent task exits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Highlights: - Native udpgw protocol in Full mode (therealaleph#222) — Telegram voice/video calls and Google Meet now work in Full mode on Android. UDP flows through one persistent TCP tunnel (instead of session-per-destination) so STUN/RTP flow counts no longer stall. Requires redeploying the tunnel-node Docker image (ghcr.io/therealaleph/mhrv-tunnel-node:1.7.0). - Android home screen restructure (therealaleph#258, closes therealaleph#246) — Connect button now pinned under Mode field, App picker shows pre-selected apps at top. With long deployment-ID lists, Connect no longer scrolls off-screen. - release-drafter + prepare-release tooling (therealaleph#260) — incrementally drafts release notes from merged PR titles; manual workflow_dispatch prepares version bumps + changelog stubs. No protocol breaking changes; existing apps_script-mode and Full-mode deployments work unchanged. Full-mode users get udpgw automatically once the tunnel-node Docker image is updated. Thanks to @yyoyoian-pixel and @dazzling-no-more. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…te revocation (therealaleph#121) * feat(cert): add --remove-cert flag and Remove CA button for clean-slate revocation * fix(cert): testable euid-root branch + orphan enterprise_roots warning
mhrv-rs --remove-cert (CLI) and Remove CA button (UI) for verified clean-slate revocation. Clears OS trust store, NSS browser stores (Linux Firefox/Chrome), and the on-disk ca/ directory. config.json and the Apps Script deployment are untouched. By-name trust verification runs before browser-state mutation; OS removal failures return RemovalIncomplete with browser state intact so retries are idempotent. Sudo-aware on Unix (re-roots HOME to the real user). 29 new unit tests on the pure logic (Firefox user.js marker handling, getent passwd parsing, NSS stderr classification, NssReport state rules). Tested end-to-end on Windows by the contributor; macOS verified at merge time on real hardware (login keychain delete + NSS-missing fallback). Linux paths await user testing. Closes therealaleph#121. Thanks @dazzling-no-more. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Telegram release notifier used to post just the universal APK with a single-document caption. This change ships the per-platform binaries for macOS (amd64+arm64 CLI), Linux (amd64+arm64 CLI), Windows (amd64 UI), and Android (universal APK) as a single Telegram media group with one caption listing every filename + SHA-256. Workflow side (.github/workflows/release.yml): - The telegram job now downloads ALL artifacts (was: APK only). - New `Prepare files for Telegram media group` step extracts the raw binaries out of each per-platform .tar.gz / .zip (no archive wrappers in the channel) and renames them with version suffixes (mhrv-rs-linux-amd64-v1.7.2, mhrv-rs-windows-amd64-ui-v1.7.2.exe, etc.). Per-platform extraction is best-effort: a missing artifact emits a `::warning::` and skips that platform rather than failing the whole post. - The post step builds a `--files <path>` arg list from tg-files/, sorted for deterministic order across runs, and invokes the notifier without --with-changelog (the script auto-replies with changelog whenever --files is used). Script side (.github/scripts/telegram_release_notify.py): - New --files arg (repeatable). 2..=10 files → sendMediaGroup; 1 file → sendDocument with the same caption shape; 0 → error. Telegram's sendMediaGroup rejects single-item groups, so the 1-file fallback isn't optional. - New build_media_group_caption() composes title + per-file filename+SHA list + repo/release URLs. Fits ~860 chars for a 6-file release; fallback to filename-only-list if a future swell pushes past Telegram's 1024-char caption cap. - send_media_group() handles the multipart/form-data shape with each file referenced as `attach://fileN` from the media JSON. Caption is attached to file 0 only (Telegram clients render per-item captions inconsistently for media groups; first-item-only is the safe pattern). - Legacy --apk path kept for any caller that hasn't migrated; either --apk or --files must be present (validated at startup). - _content_type_for() picks application/vnd.android.package-archive for .apk and application/octet-stream for everything else, so Telegram clients label the APK with the Android icon and label desktop binaries by filename without a misleading icon. Behavioural change for users: - The Telegram channel now sees one grouped post per release with all primary platform binaries inline, instead of just the APK. macOS users wanting the gatekeeper-friendly .app.zip still grab it from the GitHub Releases page; the Telegram drop is for the "give me the binary, I'll run it" path. - The Persian/English changelog reply that used to be opt-in (via TELEGRAM_INCLUDE_CHANGELOG=true) is now automatic in the --files path because the per-file SHA list eats the caption budget that previously held the FA brief-note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…therealaleph#266) * feat(android): config import/export via clipboard, QR code, deep link, and share sheet - Clipboard paste: banner auto-detects mhrv:// or raw JSON in clipboard, one tap to import. Clipboard cleared after successful import. - Export dialog: QR code + compressed hash + copy button + Android share sheet (sends QR image + text together). - QR scanner: ZXing embedded scanner in portrait orientation. - Deep link: mhrv:// URIs auto-open the app and import the config. - Compact encoding: only non-default fields included, DEFLATE compressed before base64. Accepts both compressed and raw JSON on import. - ConfigStore.loadFromJson() deduplicated — shared by file load + import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: deep link requires confirmation, trust warning on import, mhrv-rs:// scheme Security fix: deep link (mhrv-rs://) no longer auto-imports config. Stashes decoded config for UI confirmation dialog — same flow as clipboard paste and QR scan. Import confirmation dialog now shows: - Trust warning: "Importing routes your traffic through the deployment IDs in this config. Only import from trusted sources." - Mode and deployment ID count with first 3 IDs previewed - Explicit Import / Cancel buttons Also: - Renamed scheme from mhrv:// to mhrv-rs:// (less collision risk) - Deduplicated import dialog into shared ImportConfirmDialog composable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- mhrv-rs:// deep links, QR scanner, clipboard banner, share sheet - DEFLATE-compressed base64 encoding (~200 chars vs ~800 raw) - Every import path requires explicit user confirmation; the dialog shows the new deployment IDs and a trust warning so an attacker posting a malicious mhrv-rs:// link in a public channel can't silently overwrite a user's auth_key + script_ids - ZXing for QR generation/scanning (no Google Play Services) Closes therealaleph#266. Thanks @yyoyoian-pixel — the rebase from auto-import to confirmation-gated import is exactly the right shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ia group" This reverts commit e9ce03e.
…ph#271) Uses tun2proxy_run_with_cli_args (the C API) via dlsym instead of modifying the JNI run() signature. The upstream tun2proxy maintainer recommended this path — the CLI API accepts --udpgw-server natively. - Cargo.toml: enable udpgw feature, remove [patch.crates-io] - MhrvVpnService.kt: build CLI args with --udpgw-server in full mode - Native.kt + android_jni.rs: dlsym wrapper for the C API - Tun2proxy.kt: reverted to upstream signature No fork, no patch, no submodule. Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move from yyoyoian-pixel/tun2proxy fork (with patched JNI signature) to canonical tun2proxy 0.7.21 from crates.io with feature flag "udpgw". Cargo.toml [patch.crates-io] section removed entirely. The Android side now resolves tun2proxy_run_with_cli_args at runtime via dlsym from libtun2proxy.so, which is the upstream maintainer's recommended path for callers that need full CLI flexibility. mhrv-rs builds the CLI string in MhrvVpnService and passes it through Native.runTun2proxy → src/android_jni.rs → dlsym → tun2proxy. Future tun2proxy upgrades are now a single Cargo version bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-therealaleph#275, youtube_via_relay=true routed every YouTube-related host through Apps Script — including ytimg.com (thumbnails) and any googlevideo.com chunk request the player issued. Two problems: 1. ytimg.com via Apps Script is wasted quota — image CDN, no Restricted Mode logic to bypass. 2. googlevideo.com wasn't even in SNI_REWRITE_SUFFIXES, so video chunks hit the relay regardless of the flag. A single chunk timeout aborted the whole video on Firefox; long videos risked the Apps Script 6-min execution cap mid-playback. Fix: split YouTube into "API/HTML hosts" (where Restricted Mode lives, gated by the flag) and "asset CDNs" (always direct). The new YOUTUBE_RELAY_HOSTS list is youtube.com, youtu.be, youtube-nocookie.com, youtubei.googleapis.com — those go through relay when the flag is on. ytimg.com, googlevideo.com (added), ggpht.com all stay on SNI rewrite. The matches_sni_rewrite logic was also restructured: the carve-out now runs FIRST before the SNI suffix match, so the broad googleapis.com entry can't override the narrower youtubei.googleapis.com decision. Reported with detailed analysis by @amirabbas117. Will ship in v1.7.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tunnel-docker job in v1.7.3 release failed with:
error: failed to unpack package `serde_json v1.0.149`
Caused by: failed to open `/usr/local/cargo/registry/src/.../serde_json-1.0.149/.cargo-ok`
Caused by: File exists (os error 17)
Root cause: BuildKit's default cache-mount sharing is "shared" — both
linux/amd64 and linux/arm64 build stages mount the SAME on-disk cache
dir. Cargo's registry source extraction is non-atomic; both arches
race on `tar -xzf serde_json-1.0.149.crate` into the same destination,
and the loser hits EEXIST mid-unpack.
Fix: scope each cache mount with `id=cargo-registry-${TARGETPLATFORM}`
(and matching for cargo-git + target). BuildKit then keeps separate
on-disk caches per architecture — no race. Per-arch warm-build speedup
is preserved (each cache fills with that arch's pre-built deps); the
only loss is one cache miss per arch on the first build after this
change, which we already paid in v1.7.3.
The target/ mount is also platform-scoped since target/ holds compiled
object files for a single ABI; sharing across arches would either miss
or, worse, link wrong-ABI objects together.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ative-cache + pre-warm) therealaleph#275: youtube_via_relay no longer routes video/image CDNs through Apps Script. The flag now correctly carves out only the API/HTML hosts where Restricted Mode is enforced; video chunks come direct from googlevideo.com (which was missing from the SNI rewrite list entirely — fixed). Long videos no longer hit Apps Script's 6-min execution cap, and single-chunk timeouts no longer abort playback. therealaleph#280: TunnelMux now caches "destination unreachable" responses from the tunnel-node (Network is unreachable / No route to host) for 30 seconds, short-circuiting subsequent CONNECTs to that destination with 502 (HTTP) or 0x04 (SOCKS5). Saves ~5 batches/second on IPv6-only host probes. Startup pre-warm pool grew 12→24. 143/143 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
w0l4i has been asking for client-side QUIC block since therealaleph#213. Now implemented as a small config flag. When `block_quic = true`, the SOCKS5 UDP relay drops any datagram destined for port 443 — that's HTTP/3-over-UDP. The client's QUIC stack retries a couple of times and then falls back to TCP/HTTPS through the regular CONNECT path (which goes through the relay normally). Why client-side rather than server-side udpgw block: the udpgw block in therealaleph#222 is bound to Full mode + Android tun2proxy. This covers everyone — apps_script users, desktop, Full mode, all the same path. Skipping at the SOCKS5 layer rather than the tunnel-node layer also avoids paying 200–500 ms tunnel-node round-trip per QUIC datagram drop, which compounds during browser retries. Silent drop is the contractually correct shape: SOCKS5 UDP wire has no `host unreachable` reply (RFC 1928 §6 only defines that for TCP CONNECT). Browsers' QUIC stacks have a "no response → fall back" timeout, so silent drop matches what the protocol expects. Default false (opt-in) — udpgw mitigates QUIC partly via persistent sockets, and a tiny minority of sites only support HTTP/3. Will ship in v1.7.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resume the practice (dropped after v1.1.0) of committing prebuilt binaries to the repo's releases/ folder. Iranian users behind state network filtering frequently can't reach the GitHub Releases page (/releases/tag/...) but CAN reach the static source tree via Code → Download ZIP — that pulls the in-repo releases/ folder along with the source. Telegram channel feedback explicitly requested this be resumed. The new commit-releases job: 1. Runs after release+build+android succeed. 2. Wipes existing binary artifacts from releases/ (.apk, .tar.gz, .zip) but preserves README.md and .gitattributes. 3. Copies all desktop archives (which already have stable platform-suffixed names like mhrv-rs-linux-amd64.tar.gz). 4. Copies all per-ABI Android APKs (so users on slow connections can grab the ~37 MB arm64-v8a APK instead of the ~110 MB universal). 5. sed-updates the "Current version" line and APK filename refs in releases/README.md (both English and Persian copies). 6. Commits as github-actions[bot] and pushes to main. The GitHub Release page itself keeps the canonical versioned artifacts as before — this in-repo folder is the fallback for users who can't reach that URL. Tag protection rules don't apply to refs/heads/main so the push isn't gated. release-drafter.yml triggers on push-to-main but only updates the next-release draft, no cycle risk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… folder - Adds `block_quic = true` config flag for client-side QUIC drop. SOCKS5 UDP relay refuses UDP/443 datagrams; browsers fall back to TCP/HTTPS through the relay. Opt-in. Thanks @w0l4i - Workflow now auto-refreshes the in-repo releases/ folder on each release tag, so Iranian users behind GitHub-Releases-page filtering can download via Code → Download ZIP. Practice was started before v1.1.0 then dropped; resumed at user request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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).
…p to 500ms Apps like Telegram maintain persistent XMPP connections (:5222) and Google Push uses :5228 — both rely on long-lived sessions with periodic heartbeats. At the previous 5s long-poll deadline, the tunnel-node returned empty responses frequently enough that Telegram interpreted it as connection instability and rotated sessions. Each reconnect costs a full TLS handshake (~4s through Apps Script), causing visible video/voice interruptions and buffering. Raising the long-poll deadline to 15s keeps these persistent connections alive: the tunnel-node holds the response open until server data actually arrives (push notification, chat message, media chunk) rather than returning empty every 5s. Tested on censored networks in Iran where users reported smoother Telegram video playback and fewer session resets. The straggler settle is now adaptive (40ms steps, 500ms max): after the first session in a batch gets data, keep checking every 40ms whether neighboring sessions also have data. Break early when all sessions are ready — no fixed 500ms wait when data is already there. On high-latency relays where each Apps Script call costs ~1.5s overhead, packing more session responses into one batch saves quota and reduces total round-trips. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the fixed 8ms batch coalesce window with an adaptive scheme: after each new op arrives, wait another 40ms (step) for more ops. The timer resets on every arrival, up to 1000ms (max) from the first op. Both values are configurable via config JSON (coalesce_step_ms, coalesce_max_ms) and two new sliders in the Android Advanced section. Why this helps: Apps Script adds ~1.5s overhead per HTTP call. The previous 8ms window barely caught any concurrent ops — most batches carried just 1 op. With 40ms/1000ms, batches average 2-3 ops, reducing total Apps Script calls for the same workload. Tested on device in Iran: Before: P75=6.2s, 61% fast (<3s), ~1 op/batch After: P75=3.0s, 74-85% fast, ~2-3 ops/batch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…herealaleph#446) Tunnel-node stability fix from @yyoyoian-pixel based on field testing in Iran. - LONGPOLL_DEADLINE 5s → 15s: persistent connections (Telegram XMPP :5222, Google Push :5228) stay alive instead of forcing re-handshakes every 5s - Replace fixed 30ms straggler settle with adaptive 40ms-step / 500ms-max — packs more session responses into each batch, breaks early when all ready Local verification: tunnel-node 30/30 tests pass, main crate 160/160 tests pass, both build clean.
…aleph#448) Adaptive batch coalescing from @yyoyoian-pixel based on field testing in Iran. Replace fixed 8ms batch coalesce with adaptive 40ms-step / 1000ms-max scheme. Apps Script adds ~1.5s overhead per HTTP call — packing more ops into each batch means fewer total calls. Field testing showed P75 RTT 6.2s → 3.0s, fast (<3s) batches 61% → 74-85%. Both values configurable via config.json (coalesce_step_ms, coalesce_max_ms) and Android UI Advanced sliders (10-500ms / 100-2000ms). Note: desktop UI's to_config() needs follow-up to round-trip the new fields. Filing immediately as a separate commit so v1.8.4 can ship both PRs together.
Two PRs from @yyoyoian-pixel field-tested in Iran: - therealaleph#448: adaptive batch coalescing replaces fixed 8ms window. P75 RTT 6.2s → 3.0s in Iran network testing. Configurable via coalesce_step_ms / coalesce_max_ms. - therealaleph#446: tunnel-node long-poll 5s → 15s for Telegram/Google Push persistent connection stability. Adaptive 40ms-step / 500ms-max straggler settle replaces fixed 30ms. Includes desktop UI build fix: round-trip the new coalesce_* fields through to_config(). Desktop UI sliders queued for v1.8.x batch.
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).
…gents Mirror of ~/.claude/skills/mhrv-rs-maintainer/ — SKILL.md plus eight reference files plus assets. Cloud-scheduled agents clone the repo fresh on each fire and have no access to the maintainer's local home directory; embedding the skill in docs/maintainer/ lets them read the same canonical context as the local maintainer and produce replies indistinguishable from a local DOPR session. The local copy at ~/.claude/skills/mhrv-rs-maintainer/ remains the source of truth; this directory mirrors it.
…d DOPR agents" This reverts commit 9f821cc7580c8fe012346c451c86891b8b185485.
… Script body ceiling @bankbunk reported (therealaleph#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.
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).
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>
…alaleph#483) Closes therealaleph#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.
…realaleph#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.
…ealaleph#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.
…p + hotspot sharing Three substantive PRs landed for this release plus an Iran-safe DoH default: - therealaleph#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. - therealaleph#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). - therealaleph#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 therealaleph#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.
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).
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 therealaleph#502.
…igurable 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 (therealaleph#391, therealaleph#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 (therealaleph#430, masterking32 PR therealaleph#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 therealaleph#391/therealaleph#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 therealaleph#417 / therealaleph#426 / therealaleph#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.
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).
…herealaleph#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 (therealaleph#380, therealaleph#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.
Pure docs + GAS/Worker addition shipped via PR therealaleph#533 (therealaleph#380 / therealaleph#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.
f0c5700 to
d365392
Compare
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>
d365392 to
576dfa6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
youtubeViaRelayfield toMhrvConfigwith JSON serialization (youtube_via_relay), deserialization, and config-sharing encodeParity fix: the desktop UI already has a
youtube_via_relaycheckbox (src/bin/ui.rs) but the Android UI was missing it — users had to hand-edit config.json to enable it on mobile.🤖 Generated with Claude Code