From 915dca44fe7691dc729e906af4e92f6a26e3c366 Mon Sep 17 00:00:00 2001 From: Codinget Date: Sat, 13 Jun 2026 20:02:24 +0000 Subject: [PATCH 1/3] fix(safesocket/js): use unique memconn name per IPN instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- safesocket/safesocket_js.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/safesocket/safesocket_js.go b/safesocket/safesocket_js.go index 746fea511..0807885a8 100644 --- a/safesocket/safesocket_js.go +++ b/safesocket/safesocket_js.go @@ -5,15 +5,22 @@ package safesocket import ( "context" + "fmt" "net" + "sync/atomic" "github.com/akutz/memconn" ) const memName = "Tailscale-IPN" +// memSeq ensures each IPN instance in the same WASM process gets a distinct +// memconn address, so concurrent instances do not conflict on the registry. +var memSeq atomic.Int64 + func listen(path string) (net.Listener, error) { - return memconn.Listen("memu", memName) + name := fmt.Sprintf("%s-%d", memName, memSeq.Add(1)) + return memconn.Listen("memu", name) } func connect(ctx context.Context, _ string) (net.Conn, error) { -- 2.52.0 From 4618ee14961c1cb034b9d3ae6084f80106587f1c Mon Sep 17 00:00:00 2001 From: Codinget Date: Sat, 13 Jun 2026 20:11:32 +0000 Subject: [PATCH 2/3] 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 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 --- cmd/tsconnect/wasm/wasm_js.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index 054bcbea2..1c1028b22 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -873,6 +873,11 @@ func (i *jsIPN) listen(network, addr string) js.Value { if n == "tcp" { n = "tcp4" } + // netstack.ListenTCP requires a full host:port; normalise the + // standard net.Listen form ":port" that omits the host. + if strings.HasPrefix(addr, ":") { + addr = "0.0.0.0" + addr + } ln, err := i.ns.ListenTCP(n, addr) if err != nil { return nil, err -- 2.52.0 From e7270026f7e45c59a3e45ec8c65864d5abbc925d Mon Sep 17 00:00:00 2001 From: Codinget Date: Sun, 14 Jun 2026 21:54:55 +0000 Subject: [PATCH 3/3] fix(tsconnect/wasm): nil-check lb and ln in shutdown() before use 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 --- cmd/tsconnect/wasm/wasm_js.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index 1c1028b22..9dcea4d20 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -637,8 +637,12 @@ func (i *jsIPN) logout() { func (i *jsIPN) shutdown() js.Value { return makePromise(func() (any, error) { i.shutdownOnce.Do(func() { - i.lb.Shutdown() - i.ln.Close() + if i.lb != nil { + i.lb.Shutdown() + } + if i.ln != nil { + i.ln.Close() + } close(i.shutdownCh) }) return nil, nil -- 2.52.0