Skip to content

fix: recover direct tunnels without recreating them - #60

Closed
dr-hoseyn wants to merge 8 commits into
AminMGMT:mainfrom
dr-hoseyn:codex/fix-direct-tunnel-recovery
Closed

dr-hoseyn wants to merge 8 commits into
AminMGMT:mainfrom
dr-hoseyn:codex/fix-direct-tunnel-recovery

Conversation

@dr-hoseyn

@dr-hoseyn dr-hoseyn commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Direct tunnels could leave a failed forwarded listener down indefinitely, report stale sockets as healthy, or retain paced connections and admission slots after a session ended. Each TCP/UDP mapping now retries its own listener with a cancellable delay, preserving healthy ports and established mux sessions during a temporary bind conflict. Authenticated live sessions drive watchdog state, and blocked mux writes have a deadline.

Handshake cancellation closes the transport promptly. Forwarders close and join their workers, and closing a bandwidth-limited connection cancels its pending token wait. Losing one of several sessions preserves connected state. The TCP bridge preserves delayed responses after upload EOF through counted/paced wrappers. If the HTTP service beneath a direct WS/WSS listener exits, Accept wakes and pending upgrades retire so the origin can recover.

Validation: real TCP and UDP bind conflicts reproduce the original permanent outages. Recovery preserves an already-open connection on another port, and hour-long retry delays cancel promptly; these checks pass 20 repetitions under the race detector. Existing direct/cmd race suites and vet pass at 3c5da21. Exact-source checks and complete integrated tests, runtime race suites and vet pass.

No new test files or configuration/wire-format changes. Fork-only validation fixtures and workflows are excluded.

AminMGMT pushed a commit that referenced this pull request Oct 9, 2026
…d openUDPPaths wrapper

These three branches were the only ones of the 32 that could not be merged directly: each conflicted in exactly one file against a sibling that had already landed, and the branches live on a fork this session cannot push to.

Resolutions, each verified rather than guessed:

- #87 (reverse QUIC DNS bounded by the dial timeout) conflicted with #71 in internal/utils/network/quic.go, in the import block alone. Both sides' imports are kept: strings, sync and sync/atomic.

- #84 (TCP proxy setup bounded, retry backoff cancellable) conflicted with #66 in internal/utils/network/tcp_dialer.go. main's copy of that file was byte-identical to #66's, so #66 was its only other author, and #84's version is a superset of it: it keeps #66's cancellable retry timer and AfterFunc guard and adds the per-attempt deadline. #84's version is taken whole.

- #60 (direct tunnel recovery) conflicted with #85 in internal/tunnel/limits/limits.go. main's copy is byte-identical to #85's, which already carries #60's ctx/cancel/Close plus the pacingDeadline work, so it supersedes #60's change to that file and is kept. #60's other ten files apply unchanged.

Separately, #76 kept openUDPPaths as a context.Background() shim after moving its only caller to openUDPPathsContext. Nothing calls it, so staticcheck fails the audit job with U1000 — red on main since #76 landed, and clean on main before it. The shim is removed and its explanation moved onto the function that does the work.

Verified on the result: gofmt clean, go build ./... and go vet ./... pass, staticcheck clean, and go test ./... plus go test -race ./... match main's baseline (internal/socks fails only because this container has no IPv6, which fails on plain main too).

Co-authored-by: dr-hoseyn <95236112+dr-hoseyn@users.noreply.github.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wBoD8a2hetHgBWnfsRAuY

AminMGMT commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Closing: this landed on main through #93, in 34577eb, with you as co-author.

It conflicted with #85 in internal/tunnel/limits/limits.go. main's copy of that file is byte-identical to #85's, which already carries this branch's change to it — the per-connection ctx/cancel, Close cancelling the pending token wait, and pacing errors propagated — plus the pacingDeadline work on top. So main's version was kept for that one file; your other ten files (direct edge/origin/session recovery, sessions.go, the bridge, the health check and the tests) apply unchanged. The branch is on a fork, so the resolution went through #93.

Thank you.


Generated by Claude Code

@AminMGMT AminMGMT closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants