refactor(tsconnect/wasm): drop the unreachable LocalAPI socket

jsIPN.localAPI serves localapi.Handler in-process through an
httptest.ResponseRecorder, so nothing ever dialled the safesocket
listener that run() opened. The other in-tree callers of
safesocket.ConnectContext do not apply either: driveimpl's
FileSystemForRemote is replaced by jsFileSystemForRemote in this build,
and logpolicy's fallback is behind version.IsWindowsGUI.

Remove the listener, and with it ipnserver, whose only remaining use was
serving that listener. ipnserver.New builds a struct and SetLocalBackend
stores a pointer, so dropping both leaves LocalBackend untouched. Two
behaviours go with it: srv.Run's deferred lb.Shutdown, which made
shutdown call lb.Shutdown twice, and its localapi.Shutdown bus
subscription, which nothing in this build emits.

safesocket's generated per-listener name existed so several IPNs could
share one runtime. Each runtime has its own Go heap and its own memconn
registry, so the fixed name never conflicted between runtimes, and there
is now at most one IPN in each. Restoring the fixed name returns
safesocket_js.go to its upstream contents.

Co-Authored-By: claude-opus-5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:59:47 +00:00
co-authored by Claude
parent 24ee15e524
commit 00d16d6ae2
2 changed files with 1 additions and 32 deletions
-24
View File
@@ -44,7 +44,6 @@ import (
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/localapi" "tailscale.com/ipn/localapi"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
@@ -53,7 +52,6 @@ import (
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/safesocket"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd" "tailscale.com/tsd"
"tailscale.com/types/logid" "tailscale.com/types/logid"
@@ -205,7 +203,6 @@ func newIPN(jsConfig js.Value, shutdownCh chan struct{}) (map[string]any, error)
// initDriveForRemote must be called before NewLocalBackend (SubSystem is set-once). // initDriveForRemote must be called before NewLocalBackend (SubSystem is set-once).
driveFS := initDriveForRemote(sys) driveFS := initDriveForRemote(sys)
srv := ipnserver.New(logf, logid, sys.Bus.Get(), sys.NetMon.Get())
lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral) lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral)
if err != nil { if err != nil {
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
@@ -214,11 +211,9 @@ func newIPN(jsConfig js.Value, shutdownCh chan struct{}) (map[string]any, error)
return nil, fmt.Errorf("starting netstack: %w", err) return nil, fmt.Errorf("starting netstack: %w", err)
} }
wireTaildropFileOps(lb, jsConfig.Get("fileOps")) wireTaildropFileOps(lb, jsConfig.Get("fileOps"))
srv.SetLocalBackend(lb)
jsIPN := &jsIPN{ jsIPN := &jsIPN{
dialer: dialer, dialer: dialer,
srv: srv,
lb: lb, lb: lb,
ns: ns, ns: ns,
controlURL: controlURL, controlURL: controlURL,
@@ -411,7 +406,6 @@ func newIPN(jsConfig js.Value, shutdownCh chan struct{}) (map[string]any, error)
type jsIPN struct { type jsIPN struct {
dialer *tsdial.Dialer dialer *tsdial.Dialer
srv *ipnserver.Server
lb *ipnlocal.LocalBackend lb *ipnlocal.LocalBackend
ns *netstack.Impl ns *netstack.Impl
controlURL string controlURL string
@@ -422,9 +416,6 @@ type jsIPN struct {
funnelMu sync.Mutex funnelMu sync.Mutex
funnelPorts map[uint16]*funnelListenerEntry funnelPorts map[uint16]*funnelListenerEntry
// ln is the safesocket listener created by run(); stored here so shutdown
// can close it and unblock srv.Run.
ln net.Listener
shutdownCh chan struct{} // closed by shutdown() to unblock main() shutdownCh chan struct{} // closed by shutdown() to unblock main()
shutdownOnce sync.Once shutdownOnce sync.Once
} }
@@ -617,18 +608,6 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
} }
}() }()
ln, err := safesocket.Listen("")
if err != nil {
log.Fatalf("safesocket.Listen: %v", err)
}
i.ln = ln
go func() {
err := i.srv.Run(context.Background(), ln)
if err != nil && !errors.Is(err, net.ErrClosed) {
log.Fatalf("ipnserver.Run exited: %v", err)
}
}()
} }
func (i *jsIPN) login() { func (i *jsIPN) login() {
@@ -652,9 +631,6 @@ func (i *jsIPN) shutdown() js.Value {
if i.lb != nil { if i.lb != nil {
i.lb.Shutdown() i.lb.Shutdown()
} }
if i.ln != nil {
i.ln.Close()
}
close(i.shutdownCh) close(i.shutdownCh)
}) })
return nil, nil return nil, nil
+1 -8
View File
@@ -5,22 +5,15 @@ package safesocket
import ( import (
"context" "context"
"fmt"
"net" "net"
"sync/atomic"
"github.com/akutz/memconn" "github.com/akutz/memconn"
) )
const memName = "Tailscale-IPN" 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) { func listen(path string) (net.Listener, error) {
name := fmt.Sprintf("%s-%d", memName, memSeq.Add(1)) return memconn.Listen("memu", memName)
return memconn.Listen("memu", name)
} }
func connect(ctx context.Context, _ string) (net.Conn, error) { func connect(ctx context.Context, _ string) (net.Conn, error) {