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.
This commit is contained in:
therealaleph
2026-05-01 15:38:45 +03:00
parent 7268baf098
commit 541b37ad7d
4 changed files with 157 additions and 8 deletions
+4
View File
@@ -0,0 +1,4 @@
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
• fix exit-node v1.9.4: مدارا با TLS ungraceful close (peer closed without close_notify) که val.town از Apps Script عبور می‌دهد ([#585](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/585) از @gregtheph): در v1.9.4، کاربری که val.town رو با درست‌ترین config setup کرد، در log می‌دید `WARN exit node failed for https://chatgpt.com/: io: peer closed connection without sending TLS close_notify — falling back to direct Apps Script` + سپس fallback به Apps Script که خود نمی‌تونه ChatGPT رو reach کنه، در نتیجه decoy/no-json error. علت: rustls سختگیر است درباره‌ی TLS shutdown — وقتی peer (val.town) underlying TCP رو می‌بنده بدون اول send کردن TLS close_notify alert، rustls `io::ErrorKind::UnexpectedEof` می‌فرسته. کد ما در `read_http_response` این error رو propagate می‌کرد به‌عنوان hard error. حالا UnexpectedEof به‌صورت graceful EOF (مشابه `n == 0`) درمان می‌شه — اگر body completed شده با Content-Length، response درست برمی‌گرده. اگر mid-body close بود، error real (truncation) همچنان propagate می‌شه. ۴ regression test جدید (شامل UnexpectedEof tolerance + envelope unwrap valtown). 173 lib tests + 33 tunnel-node tests pass.
---
• Fix v1.9.4 exit-node: tolerate ungraceful TLS close (peer closed without close_notify) on the val.town path ([#585](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/585) by @gregtheph): in v1.9.4, users with a correctly-configured val.town deployment saw `WARN exit node failed for https://chatgpt.com/: io: peer closed connection without sending TLS close_notify — falling back to direct Apps Script` in the log, followed by a fallback to direct Apps Script which can't reach ChatGPT either, resulting in the decoy/no-json error. 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 code in `read_http_response` was propagating this as a hard error rather than treating it as graceful EOF. Now `UnexpectedEof` is handled like `n == 0`: if the body has been fully received per Content-Length, the response returns successfully; if it's a real mid-body truncation, the error still propagates as `BadResponse`. Same handling added to the chunked reader and the no-framing reader. Four regression tests cover the new behavior (UnexpectedEof tolerance for Content-Length and no-framing branches + val.town envelope unwrap success and error paths). 173 lib tests + 33 tunnel-node tests passing.