Skip to content

v1.12.0 — the proxy tunnel is a byte stream, and a knob that was never connected

Latest

Choose a tag to compare

@latentharbor latentharbor released this 07 Aug 01:52

The bug

openTunnel finishes a CONNECT (or SOCKS5) handshake holding a buffered reader — the peer may have
sent tunnel payload in the same chunk as the reply — and handed that onward as
new ReadableStream({ pull }). Correct, and a plain stream rather than a byte stream.

The TLS record layer asks for a BYOB reader and silently falls back to a default one when it cannot
have it. So every proxied connection lost BYOB reads, and with them tls.pullBytes, whose only
job is to size them.

Measured on the edge, 1 MB through the record layer, n=15 in one isolate:

pullBytes: 16 KiB pullBytes: 1 MiB
direct min 25, p50 31 min 73, p50 90
proxied min 95, p50 111 min 101, p50 121

2.9× on a direct socket and 6% through a proxy — the knob was not being read.

The fix

src/proxy/tunnel.js makes the tunnel a byte stream and, once the handshake's leftovers are
drained, hands the caller's own view straight to the socket. Both dialects share it. A transport
whose readable is not a byte stream keeps the previous buffered path.

The default moved: 64 KiB → 16 KiB

The old value came from a sweep captioned "against a real proxied socket" whose knob was never
reaching the code on that path — four samples of one configuration. Re-swept with it connected,
ms for 1 MB at the record layer, p50:

8 KiB 16 KiB 32 KiB 64 KiB 256 KiB
direct 21 20 18 22 40
proxy A 51 61 61 89 152
proxy B 68 74 102

A BYOB read resolves the instant any byte exists and never waits to fill, so the view is a ceiling
rather than a target — average fill over 4 MB is 37 KB direct and 8 KB through a proxy, and
everything above that is allocation that is never used.

End to end through the Client and a proxy, n=13: 63 against 78 ms for 1 MB and 145 against
164 ms
for 4 MB on the median.

No API changed

tls.pullBytes still overrides the default. If you had tuned it for a proxied connection, it was
not being read before this release.

Also

  • The cost tables are re-measured against a rebuilt, now-committed sizeorigin/, with the method
    stated: a warm page is (reuse=4 - reuse=1)/3. The 4 MB row reproduced; the mid sizes came in
    20–30% lower for reasons that are not this release's changes, and the README says so.
  • The pricing table had an arithmetic bug independent of any of this — it billed every request and
    every CPU millisecond, ignoring the included allowance its own prose describes, overstating the
    10M/month column by up to 74%.
  • maxBodyBytes: Infinity is now priced on a real request rather than a fixture: it is worth
    20–27 ms on a 4 MB body, roughly $540/month at a billion such requests.