Two Go-side bugs found while making @webnet/tsconnect work in Node.js (see webnet/webnet#33).
fix(safesocket/js): unique memconn name per IPN instance
safesocket_js.go hardcoded memName = "Tailscale-IPN" for the in-memory IPC address. When a second newIPN() call was made in the same WASM process (e.g. two test nodes sharing a WASM instance), the second safesocket.Listen("") would fail with addr unavailable and log.Fatal the entire WASM process.
Fixed with an atomic counter: each listener gets a distinct name (Tailscale-IPN-1, Tailscale-IPN-2, …). The connect() path is unchanged — in the tsconnect build all LocalAPI calls go through the in-process httptest handler and connect() is never called.
fix(tsconnect/wasm): normalise :port listen addr to 0.0.0.0:port
netstack.ListenTCP requires a full host:port address. Callers passing the standard Go net.Listen form ":0" (any-interface ephemeral port) received ParseAddrPort(":0"): no IP. The listen() handler in wasm_js.go now prepends "0.0.0.0" when the address starts with ":", matching the behaviour of net.Listen.
Test plan
Covered by the integration test suite in webnet/webnet#33: the two-node dial/listen test spins up two IPN instances in the same WASM process and verifies that one can accept a connection from the other.
## Summary
Two Go-side bugs found while making `@webnet/tsconnect` work in Node.js (see webnet/webnet#33).
### fix(safesocket/js): unique memconn name per IPN instance
`safesocket_js.go` hardcoded `memName = "Tailscale-IPN"` for the in-memory IPC address. When a second `newIPN()` call was made in the same WASM process (e.g. two test nodes sharing a WASM instance), the second `safesocket.Listen("")` would fail with `addr unavailable` and `log.Fatal` the entire WASM process.
Fixed with an atomic counter: each listener gets a distinct name (`Tailscale-IPN-1`, `Tailscale-IPN-2`, …). The `connect()` path is unchanged — in the tsconnect build all LocalAPI calls go through the in-process `httptest` handler and `connect()` is never called.
### fix(tsconnect/wasm): normalise `:port` listen addr to `0.0.0.0:port`
`netstack.ListenTCP` requires a full `host:port` address. Callers passing the standard Go `net.Listen` form `":0"` (any-interface ephemeral port) received `ParseAddrPort(":0"): no IP`. The `listen()` handler in `wasm_js.go` now prepends `"0.0.0.0"` when the address starts with `":"`, matching the behaviour of `net.Listen`.
## Test plan
- [x] Covered by the integration test suite in webnet/webnet#33: the two-node dial/listen test spins up two IPN instances in the same WASM process and verifies that one can accept a connection from the other.
🤖 Generated with [Claude Code](https://claude.ai/code)
Each call to newIPN() starts an independent Go backend that calls
safesocket.Listen() to serve the ipnserver IPC channel. Because
memName was a global constant, the second instance would fail with
"addr unavailable" and log.Fatal the whole WASM process.
Use an atomic counter to give each listener a distinct name
(Tailscale-IPN-1, Tailscale-IPN-2, …). The connect() path is
unchanged: in the wasm/tsconnect build all LocalAPI calls go through
the in-process httptest handler, so connect() is never called.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
netstack.ListenTCP requires a full host:port address; callers passing
the standard net.Listen form (":0" for any-interface ephemeral port)
would get ParseAddrPort error. Prepend "0.0.0.0" when the address
starts with ":" so the API matches Go's net.Listen behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lb and ln are only initialised during run(); calling shutdown() before
run() panics on nil. Guard both fields before dereferencing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
codinget
marked the pull request as ready for review 2026-06-15 00:17:16 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Two Go-side bugs found while making
@webnet/tsconnectwork in Node.js (see webnet/webnet#33).fix(safesocket/js): unique memconn name per IPN instance
safesocket_js.gohardcodedmemName = "Tailscale-IPN"for the in-memory IPC address. When a secondnewIPN()call was made in the same WASM process (e.g. two test nodes sharing a WASM instance), the secondsafesocket.Listen("")would fail withaddr unavailableandlog.Fatalthe entire WASM process.Fixed with an atomic counter: each listener gets a distinct name (
Tailscale-IPN-1,Tailscale-IPN-2, …). Theconnect()path is unchanged — in the tsconnect build all LocalAPI calls go through the in-processhttptesthandler andconnect()is never called.fix(tsconnect/wasm): normalise
:portlisten addr to0.0.0.0:portnetstack.ListenTCPrequires a fullhost:portaddress. Callers passing the standard Gonet.Listenform":0"(any-interface ephemeral port) receivedParseAddrPort(":0"): no IP. Thelisten()handler inwasm_js.gonow prepends"0.0.0.0"when the address starts with":", matching the behaviour ofnet.Listen.Test plan
🤖 Generated with Claude Code
codinget referenced this pull request from webnet/webnet2026-06-13 22:19:39 +02:00
netstack.ListenTCP requires a full host:port address; callers passing the standard net.Listen form (":0" for any-interface ephemeral port) would get ParseAddrPort error. Prepend "0.0.0.0" when the address starts with ":" so the API matches Go's net.Listen behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>cef1031b8eto4618ee1496