Skip to content

fix: populate localAddress on the connect handler's Socket#6647

Open
xortive wants to merge 1 commit intocloudflare:mainfrom
xortive:fix/connect-handler-remote-address
Open

fix: populate localAddress on the connect handler's Socket#6647
xortive wants to merge 1 commit intocloudflare:mainfrom
xortive:fix/connect-handler-remote-address

Conversation

@xortive
Copy link
Copy Markdown
Contributor

@xortive xortive commented Apr 23, 2026

Summary

  • When a worker exports a connect() handler, socket.opened.localAddress currently always resolves to undefined because ServiceWorkerGlobalScope::connect discards the CONNECT authority when constructing the ingress Socket.
  • Plumb a new localAddress parameter through Socket / setupSocket and forward the host parameter into it. JS callers now observe the exact authority string that was passed to fetcher.connect("host:port"), on the correct field.
  • Add a service-binding integration test that asserts info.localAddress matches the authority supplied by the client.

Semantics: why localAddress, not remoteAddress

On the ingress (handler) side of a CONNECT tunnel, the authority the caller targeted (e.g. "example.com:1234") represents the address on this side of the socket — the endpoint the peer asked to connect to. That's the local address from the handler's perspective, not the remote address. The outbound connect() side continues to populate remoteAddress as before; localAddress remains empty on outbound since there is no useful value to expose.

SocketInfo.localAddress previously carried a comment saying it "will always remain empty" — updated to reflect the new behavior.

Changes

  • src/workerd/api/sockets.h:
    • Added localAddress to SocketInfo's backing member + Socket constructor parameter list.
    • Added localAddress parameter to the setupSocket free-function declaration.
    • Updated the SocketInfo.localAddress comment.
  • src/workerd/api/sockets.c++:
    • Threaded localAddress through setupSocket, Socket::startTls, and both Socket::handleProxyStatus overloads.
  • src/workerd/api/global-scope.c++:
    • Forward kj::mv(host) into the new localAddress argument of setupSocket.
  • src/workerd/api/hyperdrive.c++:
    • Pass kj::none /* localAddress */ (outbound, unchanged behavior).
  • Tests:
    • New .wd-test + 2 JS files under src/workerd/api/tests/ exercising the service-binding path.
    • Wired into BUILD.bazel next to the existing connect-handler-test entry.

Verification

Red-green

  • Red: With the global-scope.c++ forwarding reverted, the new test fails with Actual: "BAD:undefined" — confirming the test catches the bug.
  • Green: With the fix, both the new test and the existing connect-handler-test pass.

Broader suite

bazel test //src/workerd/api/... --nocache_test_results: 906/912 pass. The 6 failures (worker-loader-test:pythonBasics and dns-nodejs-test) are pre-existing on clean main (verified by stashing the change and rerunning before any code modifications) — they fail due to environmental TLS cert chain / DNS issues on the developer machine and are unrelated to this change.

Non-goals

  • Not populating the domain / SNI parameter — incoming CONNECT with TLS is still rejected upstream in worker-entrypoint.c++.
  • No change to the shape of SocketInfo's public interface: both remoteAddress and localAddress fields already exist on the JS surface; localAddress was simply never populated before.
  • Outbound connect() behavior is preserved: remoteAddress continues to carry the target address; localAddress remains empty on outbound.

@xortive xortive requested review from a team as code owners April 23, 2026 17:26
const payload =
info.remoteAddress === EXPECTED_AUTHORITY
? 'OK'
: `BAD:${info.remoteAddress}`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly think this should be localAddress here.
That fits socket semantics more imo.

@kentonv wdyt?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, on the server side, "remoteAddress" should be the client's address, not the server's address.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember the socket object here is not the same socket object that the client holds. It's a socket representing the other end of the connection. So local vs. remote are swapped.

// info.remoteAddress matches the authority string the caller supplied to
// fetcher.connect(...). Writes 'OK' on match, 'BAD:<actual>' on mismatch so
// the client's assertion failure points directly at the observed value.
export class RemoteAddressEndpoint extends WorkerEntrypoint {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the existing tests at src/workerd/api/tests/connect-handler-test.js for this instead of adding a new test that duplicates a lot of the code from it? I think that should be cleaner and easier to maintain in the long run

ServiceWorkerGlobalScope::connect was dropping the CONNECT authority when
constructing the ingress Socket, causing both socket.opened.remoteAddress
and socket.opened.localAddress to resolve to undefined on the connect
handler side. Plumb a localAddress parameter through Socket/setupSocket
and forward the host parameter into it so JS callers see the exact
authority string that was passed to fetcher.connect(...).

From the handler's perspective the CONNECT authority is the local address
on this side of the tunnel (the address the peer asked to connect to),
not the remote address, so populate localAddress rather than
remoteAddress. Outbound sockets continue to populate remoteAddress as
before; localAddress remains empty for outbound since we have no useful
value to expose.

Add a service-binding test that asserts info.localAddress matches the
authority supplied by the client.
@xortive xortive force-pushed the fix/connect-handler-remote-address branch from ca473a0 to a182141 Compare April 24, 2026 19:33
@xortive xortive changed the title fix: populate remoteAddress on the connect handler's Socket fix: populate localAddress on the connect handler's Socket Apr 24, 2026
@danlapid
Copy link
Copy Markdown
Collaborator

LGTM

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.

4 participants