Releases: therealaleph/MasterHttpRelayVPN-RUST
v1.9.33
• در حالت Full Tunnel، نشست های کاملا idle حالا keepalive خالی را با backoff شدیدتر می فرستند و وقتی همه deploymentها legacy تشخیص داده شوند، بعد از idle شدن دیگر poll خالی پشت سر هم نمی زنند. این کار مصرف request روی Apps Script و tunnel-node را برای اتصال های بی کار کمتر می کند. (PR #1322 از @yyoyoian-pixel)
• برای fleetهای mixed که هنوز حداقل یک deployment با long-poll سالم دارند، کلاینت همچنان poll خالی را ادامه می دهد تا round-robin بتواند به همان peer سالم برسد و داده remote→client گیر نکند.
• In Full Tunnel mode, fully idle sessions now use a stronger empty-keepalive backoff, and when every deployment is detected as legacy they stop sending repeated empty polls after going idle. This reduces Apps Script and tunnel-node request load for quiet connections. (PR #1322 by @yyoyoian-pixel)
• For mixed fleets where at least one deployment still has healthy long-poll support, the client keeps emitting empty polls so round-robin can still reach that healthy peer and remote→client data does not stall.
What's Changed
- fix(pipeline): stop idle keepalive polls, reduce idle requests by @yyoyoian-pixel in #1322
Full Changelog: v1.9.32...v1.9.33
v1.9.32
• در حالت Full Tunnel، پروتکل batch حالا از فشرده سازی zstd پشتیبانی می کند. وقتی کلاینت جدید، CodeFull.gs جدید، و tunnel-node جدید همزمان استفاده شوند، درخواست ها و پاسخ های batch می توانند فشرده شوند و مصرف پهنای باند مخصوصا در دانلودهای سنگین کمتر شود.
• فعال شدن فشرده سازی کاملا سازگار با نسخه های قدیمی است: اگر یکی از سه بخش هنوز آپدیت نشده باشد، سیستم به پاسخ های معمولی بدون فشرده سازی برمی گردد و نباید قطع شود.
• برای استفاده از این قابلیت در Full mode، علاوه بر آپدیت برنامه، باید CodeFull.gs را دوباره به عنوان نسخه جدید deploy کنید و tunnel-node / Docker image روی VPS یا Cloud Run را هم redeploy کنید.
• لاگ های batch فشرده شده دوباره امن تر شدند: بدنه پاسخ یا محتوای zops در سطح info چاپ نمی شود، و TUNNEL_AUTH_KEY قبل از باز کردن payload فشرده بررسی می شود.
• با تشکر از @yyoyoian-pixel برای پیاده سازی و تست تجربی این قابلیت در PR #1314.
• Full Tunnel batch protocol now supports zstd compression. When the new client, new CodeFull.gs, and new tunnel-node are deployed together, batch requests and responses can be compressed, reducing bandwidth use especially on download-heavy traffic.
• Compression negotiation is backward compatible: if any one of the three pieces is still old, the system falls back to normal uncompressed responses instead of failing.
• To use this in Full mode, update the app, redeploy CodeFull.gs as a new Apps Script version, and redeploy tunnel-node / the Docker image on your VPS or Cloud Run.
• Compressed batch logging is kept safer: response bodies and zops contents are not printed at info, and TUNNEL_AUTH_KEY is checked before opening compressed payloads.
• Thanks to @yyoyoian-pixel for the implementation and empirical testing in PR #1314.
What's Changed
- feat: zstd compression — requires tunnel-node + CodeFull.gs update - 30% saving download - Full Tunnel by @yyoyoian-pixel in #1314
- release: prepare v1.9.32 by @therealaleph in #1321
Full Changelog: v1.9.31...v1.9.32
v1.9.31
• رگرسیون pipeline در Full mode که بعد از v1.9.28 میتوانست روی sessionهای idle درخواستهای خالی زیادی بسازد و quota چند deployment را سریع مصرف کند، اصلاح شد. مسیر keepalive دوباره backoff مرحلهای دارد و timer refill در حالت idle کمتر poll خالی میفرستد.
• جریان داده در Full mode پایدارتر شد: پاسخهای خالی قدیمی که قبل از شروع جریان داده queue شده بودند دیگر streak داده را قطع نمیکنند، بنابراین افت زودهنگام عمق pipeline و گیر کردن ویدیوها، مخصوصاً Instagram، کمتر میشود.
• default گزینه block_stun از این نسخه false است تا STUN/TURN به صورت پیشفرض اجازه داشته باشد؛ اگر میخواهید آن ترافیک را مسدود کنید، block_stun: true را صریحاً در config بگذارید. با تشکر از @yyoyoian-pixel برای PR #1309.
• Fix a Full mode pipeline regression introduced after v1.9.28 where idle sessions could generate too many empty polls and burn quota across multi-deployment setups. Keepalive polling now has staged backoff again, and idle refill timers schedule fewer empty polls.
• Make Full mode data flow steadier: stale empty-poll replies queued before data starts no longer break active data streaks, reducing premature pipeline-depth drops and video stalls, especially on Instagram.
• Change the block_stun default to false, so STUN/TURN traffic is allowed by default; set block_stun: true explicitly if you want to block that traffic. Thanks @yyoyoian-pixel for PR #1309.
What's Changed
- fix(pipeline): idle poll flood, data streak breaks, tuning by @yyoyoian-pixel in #1309
- release: prepare v1.9.31 by @therealaleph in #1310
Full Changelog: v1.9.30...v1.9.31
v1.9.30
• مسیر CodeFull.gs برای Full mode کمی سبکتر شد: چند hot path در Apps Script با ساختن رشتهها و آرایههای کمتر اجرا میشوند تا فشار CPU/GC در batchهای پرترافیک کمتر شود. با تشکر از @brightening-eyes برای PR #1254.
• Make the Full mode CodeFull.gs path a little lighter: several Apps Script hot paths now allocate fewer strings/arrays, reducing CPU/GC pressure during high-traffic batches. Thanks @brightening-eyes for PR #1254.
What's Changed
- some performance improvements for CodeFull.gs by @brightening-eyes in #1254
- release: prepare v1.9.30 by @therealaleph in #1287
Full Changelog: v1.9.29...v1.9.30
v1.9.29
• رفع خطای JSON در Code.gs نسخه 1.9.28 (PR #1265، #1245، #1253، #1261). مسیر ساده Code.gs در بعضی درخواستها پاسخ مقصد را خام برمیگرداند و کلاینت Rust آن را بهجای envelope داخلی relay به عنوان JSON parse میکرد؛ نتیجه خطاهایی مثل json: key must be a string یا no json in بود. حالا Code.gs مثل CodeFull.gs پاسخها را داخل envelope {s,h,b} نگه میدارد و req.r فقط برای کنترل follow-redirect استفاده میشود. برای گرفتن این fix، فایل جدید assets/apps_script/Code.gs را در Apps Script جایگزین کنید و یک New version deploy بسازید.
• Fix the v1.9.28 Code.gs JSON parse regression (PR #1265, #1245, #1253, #1261). The simple Code.gs path could return the destination body verbatim for some requests, so the Rust client tried to parse arbitrary site HTML/JSON as the relay envelope and surfaced errors such as json: key must be a string or no json in. Code.gs now matches CodeFull.gs: normal relay responses stay wrapped as {s,h,b}, and req.r only controls redirect following. To get this fix, replace your Apps Script with the new assets/apps_script/Code.gs and deploy a New version.
What's Changed
- fix(apps_script): keep Code.gs relay responses wrapped by @therealaleph in #1265
- release: prepare v1.9.29 by @therealaleph in #1266
Full Changelog: v1.9.28...v1.9.29
v1.9.28
• پایپلاین شدن pollهای Full mode و سریعتر شدن تونل (PR #1115 از @yyoyoian-pixel). کلاینت Full-mode حالا چند poll همزمان و adaptive نگه میدارد تا وقتی یک پاسخ Apps Script یا tunnel-node دیر میرسد، بقیه مسیر معطل نماند. برای جلوگیری از بههمریختن uploadها، هر write شمارهٔ ترتیبی (wseq) میگیرد و tunnel-node writeهای خارج از ترتیب را buffer میکند تا دقیقاً به همان ترتیب به upstream برسند.
• بهبود WebRTC با بلاک کردن STUN/TURN UDP (PR #1115). پورتهای UDP مربوط به STUN/TURN (3478، 5349، 19302) بهصورت پیشفرض در SOCKS5 block میشوند تا Google Meet، Discord، WhatsApp و برنامههای مشابه بهجای ۱۰ تا ۳۰ ثانیه تلاش ناموفق ICE روی UDP، سریعتر سراغ TCP TURN روی پورت ۴۴۳ بروند. Android و desktop هر دو تنظیم block_stun را نگه میدارند؛ در UI دسکتاپ هم کنار گزینهٔ Block QUIC قابل تغییر است.
• ابزارهای تشخیص و تست برای pipeline (PR #1115). Android یک overlay تشخیصی برای وضعیت pipeline اضافه کرده و اسکریپت scripts/bench-pipeline.sh برای benchmark مسیر جدید آمده است. قبل از merge یک fix کوچک maintainer هم اضافه شد تا تستهای tunnel-node با فیلدهای جدید SessionInner کامپایل شوند و drain بزرگ TCP بعد از رسیدن به سقف ۱۶MiB همان poll، tail را برای poll بعدی نگه دارد.
• Improve WebRTC fallback by blocking STUN/TURN UDP (PR #1115). SOCKS5 now blocks STUN/TURN UDP ports (3478, 5349, 19302) by default so Google Meet, Discord, WhatsApp, and similar apps move to TCP TURN on port 443 instead of waiting through 10-30 seconds of failed UDP ICE attempts. Android and desktop both preserve the block_stun setting; the desktop UI exposes it next to Block QUIC.
• Add pipeline diagnostics and benchmark tooling (PR #1115). Android gains a pipeline debug overlay, and scripts/bench-pipeline.sh gives maintainers a repeatable benchmark path. Before merge, a small maintainer fix also made the tunnel-node tests compile with the new SessionInner fields and kept capped TCP drains from consuming the over-16MiB tail in the same poll.
What's Changed
- feat(tunnel): pipelined polls with adaptive depth, wseq ordering, STUN blocking by @yyoyoian-pixel in #1115
Full Changelog: v1.9.27...v1.9.28
v1.9.27
• رفع deploy نشدن exit_node.ts روی Deno جدید (#1197، #1120). نسخههای جدید deno serve / Deno Deploy انتظار دارند entrypoint به شکل export default { fetch: ... } باشد. exit_node.ts قبلاً یک تابع async را مستقیم default-export میکرد و برای بعضی کاربران با خطاهای deploy-warm up failed / deploy-route failed شکست میخورد. حالا export shape با Deno جدید سازگار است و wrapper.ts همچنان برای VPS / Deno / Bun / Node کار میکند. اگر exit node را جدا deploy کردهاید، فایل جدید assets/exit_node/exit_node.ts را کپی کنید، PSK را تنظیم کنید، و دوباره deploy کنید.
• رفع مسیر Deno exit-node و خطای Content Encoding Error (PR #1209 از @CaptainMirage، #1222). Code.gs حالا وقتی mhrv-rs برای exit node پاسخ raw میخواهد، جواب {s,h,b} خود exit node را دوباره داخل envelope دوم نمیپیچد. سمت Rust هم header قدیمی content-encoding از پاسخ exit-node حذف میشود، چون runtimeهای fetch مثل Deno body را auto-decompress میکنند ولی ممکن است header فشردهسازی قدیمی را نگه دارند؛ همین mismatch باعث خطای مرورگر میشد. برای استفاده از این fix باید Code.gs جدید را هم در Apps Script با New version deploy کنید.
• سختتر شدن parser در برابر پاسخهای relay خراب (PR #1229 از @CaptainMirage). اگر fallback JSON extraction با متنی روبهرو شود که جای { و } در آن معکوس یا نامعتبر است، حالا بهجای panic/برش نامعتبر، خطای no valid json object برمیگرداند. این مخصوصاً برای پاسخهای HTML/decoy/truncated از Apps Script مسیر خطا را قابل فهمتر و امنتر میکند.
• Fix the Deno exit-node path and browser Content Encoding Error (PR #1209 by @CaptainMirage, #1222). Code.gs now returns the exit node's raw {s,h,b} response when mhrv-rs requests it instead of wrapping it in a second envelope. The Rust side also strips stale content-encoding from exit-node responses because fetch runtimes such as Deno auto-decompress the body but may preserve the original compression header; that mismatch was what made browsers report content encoding failures. Deploy the new Code.gs as a new Apps Script version to get this fix.
• Harden relay fallback parsing against malformed responses (PR #1229 by @CaptainMirage). If fallback JSON extraction sees text where the { / } positions are inverted or invalid, it now returns no valid json object instead of slicing with invalid bounds. This makes HTML/decoy/truncated Apps Script responses fail more cleanly.
What's Changed
- fix(exit-node): fix double-wrapped response envelope and panic in error paths by @CaptainMirage in #1209
- fix(domain_fronter): prevent panic when brace positions are inverted in fallback JSON extraction by @CaptainMirage in #1229
New Contributors
- @CaptainMirage made their first contribution in #1209
Full Changelog: v1.9.26...v1.9.27
v1.9.26
• بهینهسازی Full mode Apps Script: batch کردن Edge-DNS cache lookupها (PR #958 by @dazzling-no-more). مسیر CodeFull.gs قبلاً برای هر DNS query داخل batch یک CacheService.get جداگانه میزد. روی batchهای شلوغ، همین چند round-trip داخلی به CacheService میتونست latency بسازه، مخصوصاً وقتی چند lookup مشابه پشت سر هم میاومدن. حالا DNS candidateها در دو pass پردازش میشن: اول همه keyها جمع میشن، بعد یک cache.getAll(keys) برای کل batch انجام میشه، و جوابهای DoH موفق هم داخل همان batch دوباره استفاده میشن تا query تکراری دوباره به resolver نخوره.
• مسیر امن fallback حفظ شده. اگر CacheService خطا بده، DoH هنوز از داخل شبکه Google انجام میشه ولی cache.put غیرفعال میمونه؛ اگر parse/DoH شکست بخوره، همان op مثل قبل به tunnel-node forward میشه. qnameهای خیلی بلند هم بهجای skip شدن، با SHA-256 زیر namespace جداگانه cache میشن تا محدودیت ۲۵۰ کاراکتری key در CacheService شکسته نشه. هیچ config migration یا تغییر سمت client لازم نیست؛ فقط CodeFull.gs جدید را deploy کنید.
• The safe fallback behavior is preserved. If CacheService fails, DoH still runs from inside Google's network but cache.put is skipped; if parse/DoH fails, the op falls back to the existing tunnel-node forward path. Very long qnames now use a SHA-256 cache key under a separate namespace instead of skipping cache entirely, keeping keys below CacheService's 250-character limit. No client config migration is required; deploy the new CodeFull.gs.
What's Changed
- Update config.fronting-groups.example.json by @Shjpr9 in #1191
- perf(apps_script): batch CacheService getAll + edge-DNS hot-path wins by @dazzling-no-more in #958
Full Changelog: v1.9.25...v1.9.26
v1.9.25
• نصب MITM CA در LibreWolf (#1145, PR #1159 by @dazzling-no-more). کاربران LibreWolf با خطای MOZILLA_PKIX_ERROR_MITM_DETECTED روی سایتهای HSTS-protected (bing.com، youtube.com، …) مواجه میشدن. علت: cert_installer.rs فقط Firefox profile rootها رو scan میکرد. LibreWolf یک Firefox fork است که همون NSS DB layout رو share میکنه ولی profile tree خودش رو زیر app dir خودش نگه میداره — هیچکدوم از certutil -A per-profile install یا user.js enterprise-roots auto-trust fallback به LibreWolf نمیرسیدن. راهحل: firefox_profile_dirs() → mozilla_family_profile_dirs() که هم Firefox هم LibreWolf paths رو per-OS برمیگردونه. هیچ تغییری برای کاربران Firefox. ۲۳۱ → ۲۳۹ lib test (+۸ regression برای LibreWolf path discovery). همان class از bug که قبلاً در #955 و #959 (Firefox-fork) closed شده بود.
• رفع باگ Full mode «Google و اکثر سایتها خراب، تلگرام سالم» — udpgw magic IP از داخل virtual-DNS range tun2proxy منتقل شد (#251 by @dazzling-no-more).
در Full mode روی Android، تلگرام کار میکرد ولی Google search و اکثر سایتها silently fail میشدن — apps_script mode روی همون device سالم بود و VPS هم idle.
علت: آدرس magic مربوط به udpgw (یعنی 198.18.0.1:7300) داخل 198.18.0.0/15 بود، یعنی دقیقاً همون rangeای که tun2proxy --dns virtual ازش IPهای ساختگی رو برای hostname lookupها اختصاص میده. هر دفعه که virtual DNS اتفاقاً 198.18.0.1 رو به یک hostname مثل www.google.com allocate میکرد، traffic اون host بهعنوان udpgw connection مصادره میشد و drop میشد. تلگرام immune بود چون native clientش از IPهای عددی hardcoded استفاده میکنه؛ همچنین apps_script mode هم immune بود چون اصلاً --udpgw-server ست نمیکنه.
راهحل: ثابت UDPGW_MAGIC_IP به 192.0.2.1 (RFC 5737 TEST-NET-1) منتقل شد. دو فایل تغییر کرده: یکی tunnel-node/src/udpgw.rs (constant + tests) و دیگری android/.../MhrvVpnService.kt (که حالا از یک companion const به اسم UDPGW_MAGIC_DEST استفاده میکنه).
سازگاری با نسخههای قدیمی: نسخهٔ جدید tunnel-node همچنان 198.18.0.1:7300 قدیمی رو هم accept میکنه برای یک deprecation cycle (حذف در v1.10.0) — یعنی اگه VPS رو زودتر آپدیت کنی، Android قدیمی هنوز کار میکنه. ولی اگه Android رو زودتر آپدیت کنی، tunnel-node قدیمی UDP relay رو در Full mode break میکنه. توصیه: اول tunnel-node رو آپدیت کن، بعد APK رو.
Root cause: cert_installer.rs only scanned Firefox profile roots (~/.mozilla/firefox, the snap variant, %APPDATA%\Mozilla\Firefox\Profiles, ~/Library/Application Support/Firefox/Profiles). LibreWolf is a Firefox fork that shares Firefox's NSS DB layout and respects the same security.enterprise_roots.enabled pref, but stores its profile tree under its own app dir — neither the per-profile certutil -A install nor the user.js enterprise-roots auto-trust fallback ever touched LibreWolf. Same failure mode as already-closed #955 / #959 (Firefox-fork users).
Fix: extend Mozilla-family profile discovery to cover LibreWolf on every supported platform. firefox_profile_dirs() → mozilla_family_profile_dirs() (returns union of Firefox + LibreWolf paths per-OS). Per-OS coverage:
- Linux:
~/.mozilla/firefox, snap variant,~/.librewolf,$XDG_CONFIG_HOME/librewolf. - macOS:
~/Library/Application Support/Firefox/Profiles,~/Library/Application Support/LibreWolf/Profiles. - Windows:
%APPDATA%\Mozilla\Firefox\Profiles,%APPDATA%\LibreWolf\Profiles.
No behavioural change for Firefox installs. 231 → 239 lib tests (+8 regression for LibreWolf path discovery on each OS).
• Fix Full mode "Google + most websites broken while Telegram works" — udpgw magic IP moved out of tun2proxy virtual-DNS range (#251 by @dazzling-no-more). Users on Android Full mode reported that Telegram worked fine but Google search and most other websites failed to load — while apps_script mode on the same device + same google_ip worked perfectly and the VPS was sitting idle.
Root cause: the udpgw magic destination address (198.18.0.1:7300) lived inside 198.18.0.0/15 — the exact same range that tun2proxy's --dns virtual allocator uses to synthesise fake IPs for hostname lookups. Whenever virtual DNS happened to assign 198.18.0.1 to a real hostname (e.g. www.google.com), that hostname's connections were intercepted by tun2proxy itself as a udpgw request before they ever reached the SOCKS5 proxy. Result: a random subset of DNS-resolved hosts silently broke per session, depending on which hostname won the 198.18.0.1 allocation. Telegram was unaffected because its native client uses hardcoded numeric IPs (no DNS allocation needed). apps_script mode was unaffected because it doesn't pass --udpgw-server to tun2proxy at all.
Fix: relocate UDPGW_MAGIC_IP from 198.18.0.1 to 192.0.2.1 (RFC 5737 TEST-NET-1). TEST-NET-1 is reserved for documentation, never routed on the public internet, and — critically — outside any virtual-DNS allocation pool. Structurally equivalent to the old address as a "guaranteed-not-real-destination", just no longer colliding with tun2proxy's reserved range.
Coordinated two-side change:
tunnel-node/src/udpgw.rs:UDPGW_MAGIC_IP = [192, 0, 2, 1], doc comment now cites RFC 5737 + explicitly explains why it must stay out of198.18.0.0/15. Test additions:is_udpgw_dest_workscovers both the new IP and the legacy IP (back-compat assertion); newmagic_ip_outside_virtual_dns_rangeenforces the invariant at the198.18.0.0/15range level, so any future move to198.19.x.ywould also fail the test rather than re-introducing the same class of bug.android/.../MhrvVpnService.kt:--udpgw-server $UDPGW_MAGIC_DESTwhereUDPGW_MAGIC_DEST = "192.0.2.1:7300"is a new companion-object constant, with a docstring pointing back at the Rust constant — gives the next editor a single, labelled place to update if the convention ever changes again.
Back-compatibility — partial, one-way:
The udpgw magic IP is a wire-protocol convention between the Android client and the mhrv-tunnel Docker container. v1.9.25 tunnel-nodes accept both the new 192.0.2.1:7300 and the legacy 198.18.0.1:7300 for one deprecation cycle (slated for removal in v1.10.0). That softens — but does not fully resolve — the asymmetric-upgrade matrix:
| Android | Tunnel-node | Full-mode UDP relay |
|---|---|---|
| v1.9.25 | v1.9.25 | ✅ fully fixed |
| ≤v1.9.24 | v1.9.25 | --udpgw-server 198.18.0.1:7300 — meaning the underlying #251 virtual-DNS-pool collision is still live on the device. Telegram works; the random Google-search-style breakage persists until the APK is updated. |
| v1.9.25 | ≤v1.9.24 | ❌ breaks silently — new client sends 192.0.2.1, old node treats it as a real TCP destination and the connect fails |
| ≤v1.9.24 | ≤v1.9.24 | unchanged from before (still has the original #251 bug) |
Recommended upgrade order: update both halves to v1.9.25. The fix is on the client side (which magic IP it asks tun2proxy to reserve) — the tunnel-node back-compat shim only prevents a hard handshake break during the window where the node is upgraded first; it does not fix the original bug. If you can only update one half right now: do the APK first (or both together), since updating just the tunnel-node leaves clients still hitting the virtual-DNS collision. apps_script-only users are unaffected (the udpgw path isn't used in apps_script mode).
Diagnostic note for stuck users: if Telegram works on Full mode but Google search / random websites silently fail on v1.9.24 or earlier, this is your bug. As a workaround pending upgrade, add Google domains to passthrough_hosts to route them through tunnel-node like Telegram does:
{
"passthrough_hosts": [".google.com", ".gstatic.com", ".googleusercontent.com", ".googleapis.com", ".youtube.com", ".ytimg.com"]
}Slower per-request (Apps Script overhead) but bypasses the virtual-DNS clash entirely. Remove once both halves are on v1.9.25.
What's Changed
- fix(udpgw): move magic IP out of tun2proxy virtual-DNS range by @dazzling-no-more in #1143
- fix(cert): install MITM CA into LibreWolf NSS stores by @dazzling-no-more in #1159
Full Changelog: v1.9.24...v1.9.25
v1.9.24
• Fix Full mode timeout cascade — `batch header read honors request_timeout_secs` (#1088, PR #1108 by @dazzling-no-more). در Full mode، یک Apps Script edge کند، تمام تونل sessionهای hot-and-flowing رو cascade-kill میکرد. کاربرها روی v1.9.21+ مرتب 10s "batch timeout" میدیدن و download progress تلگرام/browser رو از دست میدادن. Root cause: `read_http_response` در `domain_fronter.rs` یک hardcoded 10s header-read timeout داشت که داخل `tunnel_batch_request_to` اجرا میشد — مستقل از و کوتاهتر از outer `tokio::time::timeout(batch_timeout, ...)` در `fire_batch`. Apps Script cold starts معمولاً 8-12s طول میکشن (PR #1040's A/B 4/30 H1 batches رو ثبت کرد که دقیقاً 10s timeout میشدن)، پس inner cliff بهعنوان false-positive batch timeout قبل از اینکه `request_timeout_secs` (default 30s) trigger بشه fire میشد. Fix: (1) `tunnel_batch_request_to` حالا `batch_timeout` رو به header read pass میکنه via new `read_http_response_with_header_timeout` helper. (2) Header read یک absolute deadline استفاده میکنه (`timeout_at`) به جای per-read `timeout()` — slow drip-feed peer دیگه نمیتونه silently extend بزنه. (3) Bonus: `TunnelMux::reply_timeout` با `batch_timeout` co-vary میکنه (`batch_timeout + 5s slack`). ۲۰۹ → ۲۳۱ lib test (+22 regression).
• Docker: cargo-chef برای build بدون BuildKit (#620, PR #1117 by @dazzling-no-more). `tunnel-node/Dockerfile` از BuildKit-only `RUN --mount=type=cache` استفاده میکرد که روی Cloud Run's `gcloud run deploy --source .` path شکست میخورد (underlying `gcr.io/cloud-builders/docker` builder BuildKit رو enable نمیکنه). cargo-chef pattern: `recipe.json` planner stage + `cargo chef cook` deps stage + final build with `src/` on top. Docker's regular layer cache حالا dependency reuse رو handle میکنه — warm rebuilds تنها `src/` رو compile میکنن. Base bump `rust:1.85-slim` → `rust:1.90-slim` (cargo-chef نیاز به rustc 1.86+ داره).
• Fix Full mode timeout cascade — batch header read honors request_timeout_secs (#1088, PR #1108 by @dazzling-no-more). Under Full mode, a single slow Apps Script edge cascade-killed every in-flight tunnel session sharing its batch. Users on v1.9.21+ saw frequent 10s "batch timeout" errors and lost download progress on Telegram / browser sessions.
Root cause: read_http_response in domain_fronter.rs had a hardcoded 10s header-read timeout that ran inside tunnel_batch_request_to — independent of and shorter than the outer tokio::time::timeout(batch_timeout, …) in fire_batch. Apps Script cold starts routinely land in the 8-12s range (PR #1040's A/B recorded 4/30 H1 batches timing out at exactly 10s after the H2→H1 switch), so the inner cliff fired as a false-positive batch timeout well before request_timeout_secs (default 30s) could.
Fix (in domain_fronter.rs + tunnel_client.rs):
tunnel_batch_request_topassesbatch_timeoutto the header read via newread_http_response_with_header_timeouthelper.Config::request_timeout_secsis now the only knob controlling how long we wait for an Apps Script edge to start responding. Other callers (relay path, exit-node) keep the historical 10s value.- Header read uses a single absolute deadline (
timeout_at) instead of per-readtimeout(). Total elapsed across all header reads is bounded regardless of read cadence — a slow drip-feed peer can no longer silently extend. TunnelMux::reply_timeoutco-varies withbatch_timeout(computed at construction asfronter.batch_timeout() + 5s slackinstead of fixed 35s const). Operators raisingrequest_timeout_secsno longer have sessions abandonreply_rxjust beforefire_batch's HTTP round-trip would complete.
209 → 231 lib tests (+22 regression covering the deadline/co-variance behavior).
• Docker: cargo-chef so tunnel-node builds without BuildKit (#620, PR #1117 by @dazzling-no-more). tunnel-node/Dockerfile used BuildKit-only RUN --mount=type=cache directives, breaking on Cloud Run's gcloud run deploy --source . path (the underlying gcr.io/cloud-builders/docker builder doesn't enable BuildKit, and --set-build-env-vars DOCKER_BUILDKIT=1 doesn't flip it on either).
Reworked to use cargo-chef: a dedicated planner stage emits recipe.json for dependency metadata, a cargo chef cook stage builds just the deps in their own Docker layer, the final build stage adds src/ on top. Docker's regular layer cache handles dependency reuse — warm rebuilds where only src/ changes still skip the slow crate compile.
Base bump rust:1.85-slim → rust:1.90-slim (cargo-chef's transitive deps require rustc 1.86+; tunnel-node's Cargo.toml has no rust-version pin so the bump is internal-only).
Action for Cloud Run users blocked on #620: pull v1.9.24 of the tunnel-node Docker image (ghcr.io/therealaleph/mhrv-tunnel-node:v1.9.24 or :latest) — your gcloud run deploy --source . should now succeed without BuildKit.
Followup: issue #1131 (BuffOvrFlw) reports h1 open timed out after 8s — that's the H1_OPEN_TIMEOUT_SECS = 8 from PR #1029 firing on open() (TCP+TLS handshake), separate from the header-read timeout this release fixes. Worth a follow-up PR to make H1_OPEN_TIMEOUT_SECS parameterized via request_timeout_secs too.
What's Changed
- fix(docker): cargo-chef so tunnel-node builds without BuildKit (#620) by @dazzling-no-more in #1117
- fix(tunnel): batch header read honors request_timeout_secs (#1088) by @dazzling-no-more in #1108
Full Changelog: v1.9.23...v1.9.24