fix(ext/node): bind to IPv6 wildcard for default Server.listen() to enable dual-stack#33617
Conversation
…nable dual-stack Enables tests/node_compat/runner/suite/test/parallel/test-http2-ip-address-host.js Co-authored-by: Divy Srivastava <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
…isten() Removes the Windows-specific IPv4-only fallback so the default listen() binds the IPv6 wildcard on all platforms (matching Node), and updates two unit tests that asserted IPv4-only remoteAddress/family formats from the old behavior. Co-authored-by: Divy Srivastava <[email protected]>
fibibot
left a comment
There was a problem hiding this comment.
The fix restores Node's default IPv6-first binding behavior that was workaround-disabled in _createServerHandle and _setupListenHandle. Both blocks now try bind6(DEFAULT_IPV6_ADDR) first and fall back to IPv4 only on failure, matching lib/internal/net.js upstream. The implementation symmetry between the two call sites is good.
But this is a behavior change for stable APIs that I want a maintainer to weigh in on before landing:
net.createServer().listen()(no host arg) currently binds0.0.0.0. After this PR it binds::and accepts IPv4 connections via dual-stack as::ffff:127.0.0.1-style mapped addresses.- Same for
http.createServer().listen(),http2.createSecureServer().listen(), etc. - User code that does
if (socket.remoteAddress === "127.0.0.1") { ... }(orsocket.remoteFamily === "IPv4") will silently start failing — see the test changes intests/unit_node/http_test.tsandtests/unit_node/net_test.tsthat the author had to update. - The original IPv4-only behavior was a deliberate workaround per the existing
// TODO(@bartlomieju): differs from Node ... Forcing IPv4 as a workaround for Deno not aligning with Node on implicit binding on Windowscomment, with issue #10762 as the rationale. The PR doesn't link a comment confirming the underlying Windows issue is resolved (or thatbind6reliably falls back when Windows can't dual-stack).
cc @bartlomieju — the workaround was originally yours and the Windows reliability question is the load-bearing one. Worth confirming current Windows behavior before this lands.
Holding off on autonomous APPROVE per fibibot's policy on:
- Breaking changes to stable Node-compat APIs (
socket.remoteAddress,socket.remoteFamily). - CI not yet complete (58/n passing, no failures yet but most checks queued).
One smaller observation about CI noise inline.
| err = (handle as TCP).bind6(DEFAULT_IPV6_ADDR, port ?? 0, flags ?? 0); | ||
| if (err) { | ||
| handle.close(); | ||
| return _createServerHandle(DEFAULT_IPV4_ADDR, port, 4, null, flags); |
There was a problem hiding this comment.
Style/safety nit on the IPv6→IPv4 fallback: when bind6 fails, the code calls handle.close() then recurses into _createServerHandle(DEFAULT_IPV4_ADDR, ...) which creates a new TCP handle. The recursion is fine and matches Node, but the recursive call can fail too — and if both binds fail, the user just gets the IPv4 error code with no indication that an IPv6 attempt happened first. Worth confirming this matches Node's behavior (Node exposes only the second attempt's error per lib/internal/net.js's lookupAndListen, so this is probably fine, just verifying).
More importantly: the PR's bind6 → fallback to bind flow assumes the IPv6 wildcard reliably accepts IPv4 connections via dual-stack on Windows. The original workaround comment (Forcing IPv4 as a workaround for Deno not aligning with Node on implicit binding on Windows) was specifically about Windows — has the underlying issue (#10762) been verified resolved on Windows debug + release? CI's test node_compat debug windows-* shards aren't in yet on this run.
There was a problem hiding this comment.
You were right to flag this - CI on Windows confirmed dual-stack does not reliably accept IPv4 connections in our environment (the tests/specs/node/http_server_connect_event and node_compat::sequential::test-net-localport.js Windows shards both reproduced connect ECONNREFUSED 127.0.0.1 against a server bound to ::).
I've restored the Windows-specific IPv4 fallback in _createServerHandle / _setupListenHandle (so non-Windows now matches Node, Windows keeps the existing IPv4-only behavior) and skipped parallel/test-http2-ip-address-host.js on Windows via tests/node_compat/config.jsonc. The two unit_node assertion changes stay because they're more permissive (accept either 127.0.0.1 or ::ffff:127.0.0.1) and work on both code paths.
CI confirmed Windows dual-stack does not reliably accept IPv4 connections (server bound to :: refuses 127.0.0.1 clients with ECONNREFUSED). Restore the Windows-specific IPv4 fallback and skip parallel/test-http2-ip-address-host.js on Windows. Co-authored-by: Divy Srivastava <[email protected]>
fibibot
left a comment
There was a problem hiding this comment.
LGTM on the delta. The Windows-IPv4-only carve-out is the minimal fix for the windows-x86_64 CI failure I flagged before. Verified:
isWindowsalready imported atext/node/polyfills/net.ts:105, no new import needed.- Both
_createServerHandleand_setupListenHandleget the same Windows guard (mirror logic — important forServer.listen()paths going through either function). test-http2-ip-address-host.jsis correctly Windows-disabled with a one-line reason; thewindows: falseshape matches existing config entries.- Linux/macOS path is unchanged — still IPv6 dual-stack with IPv4 fallback, matching Node.
CI: 134/134 green, all test node_compat debug shards including windows-x86_64 pass.
One thing worth flagging for follow-up (not blocking): on Windows, IPV6_V6ONLY defaults to TRUE, which is why an IPv6-bound socket there doesn't accept IPv4 connections. Node solves this by clearing IPV6_V6ONLY on its dual-stack sockets — a future PR setting that socket option in the Deno bind path would let Windows match Node's "single-stack-bound-with-IPv4-fallback" default rather than diverging to IPv4-only. The new comment ("in our environment") is honest about this being a workaround, but it's worth a tracking issue if there isn't one already.
cc @bartlomieju on the Windows divergence — the behavior change is small but it's a default-listen-address difference from Node on a stable API.
littledivy
left a comment
There was a problem hiding this comment.
LGTM, windows divergence fine until #10762 is fixed
Summary
Enables
test-http2-ip-address-hostin node_compat suite.Test plan
cargo test --test node_compat -- test-http2-ip-address-host