Repository navigation
Terminated tunnels end - #700
Merged
Merged
Conversation
A TLS-terminated tunnel's HTTP server never listens, so the runtime applies a server's connection timeouts in part (Node) or not at all (Bun): a tunnel could hold its slot for as long as its client or its upstream left it so, and an upload answered early could leave it paused with its client's FIN unread. boundTunnel closes a tunnel that owes no response 5 s after a response or 10 s after its ClientHello, drops the rest of a request once its response is out, and closes a tunnel whose request has not moved for 300 to 600 s. A whole request that waits for its response is left alone. The 503 at the cap is logged and recorded as a violation, and updateConfig changes maxTunnels and handshakeTimeoutMs.
…nd keeps a margin The idle timer was armed when a response closed, with up to a megabyte still queued for the client, and what was left when it fired twice was thrown away: a slow reader lost the tail of a large response. No one layer counts what is queued, so three are summed. A timer that finds anything there, when it is armed or when it fires, starts over. The idle end comes 1 s later than Keep-Alive: timeout says, as in 0.0.79, for a client that takes it at its word. A tunnel with something under way is looked at five times in the time allowed, so it is closed 300 to 360 s after anything last moved. That now holds for a response its client never reads as well. When its backend closes first, the mux closes the client after the flush.
It gave up after a fixed 30 s. The kernel takes more a megabyte or two at a time, so below about 50 KB/s a response that ends its connection could lose its last bytes. It now waits the 300 s a tunnel may stall. No progress is counted: a socket's writableLength falls in one step, when the whole batch has gone, so a slow reader cannot be told from one that has stopped. The README's figure for the slowest reader that is not cut: about 5 KB/s.
dylan-conway
approved these changes
Oct 13, 2026
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.
Since #665 a TLS-terminated tunnel's HTTP server never listens: it is handed the connection. The runtime then applies a server's connection timeouts in part (Node:
keepAliveTimeoutalone) or not at all (Bun). So a tunnel can hold its slot, three descriptors and up to 4 MiB for as long as its client or its upstream leaves it so. AtmaxTunnels(256) every further CONNECT gets 503. In no release.main, Bunmain, Nodecurl -X HEADAnd an upload answered early can hold its slot for good. Past 4 MiB buffered the tunnel is paused. Once the upstream has answered (413, 401) or failed (reset, port closed, bad certificate), nothing reads on, so not even the client's FIN is seen. Whether that shows depends on the runtime build: some drop the rest of such a request themselves. On one that does not, with a cap of 4, eight uploads of 16 MiB answered 413: four of them, and every later CONNECT, get 503. Here all pass.
What changes
boundTunnel(), called once per tunnel:Keep-Alive: timeout=5says, as in 0.0.79, or 10 s after the ClientHello. Destroyed if it is still there, and idle, as long again.Also:
mainand in 0.0.79 too.closeAfterFlushwaits 300 s for the flush, where it waited 30 s. The kernel takes more a megabyte or two at a time, so below about 50 KB/s aConnection: closeresponse could lose its last bytes. No progress is counted there: a socket'swritableLengthfalls in one step, when the whole batch has gone (measured on three runtimes).maxTunnelsandhandshakeTimeoutMsare read at each CONNECT, soupdateConfigchanges them.host:port(it readnull443).Testing
closeAfterFlush's time, run as children under Node and Bun. CI's Bun drops the rest of an early-answered request by itself, so those tests switch that off, and thenmainfails them on every runtime. 87 mutations: 86 fail a test, and one changes no answer.socatrelays, on four runtime builds, against 0.0.79 andmain: the table above, and:maincurl --limit-rate50k to 400k, 24 MiB, both waysConnection: close, 15k to 45k, 14 MiB, both waysNot covered
Connection: closeresponse holds its slot for 300 s, where it held it for 30 s.srt proxyhave no timers. Opaque tunnels still destroy the client when the upstream closes.Merge order
Meets #698 in one list of options (
sandbox-manager.ts). Whichever merges second needsmainmerged in.