tailscale/tsnet2
Andrew Dunham 7dd5d94c0c tsnet2: implement out-of-process daemon and client shim
WARNING: this is an unreviewed prototype; please do not assume this
works or is correct

Bring the tsnet2 skeleton from the previous commit to a passing
TestTsnet2EndToEnd integration test (~8s on a recent Linux box).

Architecture follows PLAN.tsnet2.md:

- cmd/tsnet2d/daemon/ stands up wgengine + magicsock + netstack +
  ipnlocal.LocalBackend + localapi.NewHandler exactly the way
  tsnet/tsnet.go does, against the daemon's state dir.
- The daemon listens on a Unix socket. Each conn opens with a 1-byte
  channel-kind handshake (control / localapi / datapath / accept).
- Control channel: newline-delimited JSON RPC for Start, Up, Close,
  TailscaleIPs, CertDomains, RegisterListener, UnregisterListener.
- LocalAPI channel: the daemon wraps the conn in a one-shot
  net.Listener and serves http.Server.Serve on it with the localapi
  handler — preserves Hijacker/Flusher semantics for streaming
  endpoints. Server.LocalClient() returns *local.Client with Dial
  pointed at this channel so WhoIs / Status / WatchIPNBus etc. all
  work for free.
- Datapath channel: outbound dial flows. Client writes a one-line
  JSON header, daemon UserDials, then bytes are spliced and tee'd
  into the traffic logger.
- Accept channel (parked-worker model): each tsnet2 listener pre-
  parks K accept-channel conns; the daemon's GetTCPHandlerForFlow
  hands inbound netstack flows to the next parked slot, writes a
  metadata header, and streams cleartext bytes. Chose this over the
  daemon-dials-back-to-app pattern in PLAN.tsnet2.md because it
  avoids requiring the app to bind a second socket.
- tsnet2/traffic emits the JSONL schema from the plan (open/data/
  close) with WhoIs enrichment on inbound opens and 16 KiB payload
  chunking on data records.

Deferred (per plan v2): ListenPacket/UDP, ListenFunnel, ListenService,
Loopback, fallback TCP handlers (registered as a no-op for API
compat), peercred auth on the Unix socket (TODO), daemon-restart
re-registration of listeners.

Server struct keeps its mutable state behind a plain pointer field
guarded by a package-level mutex so the value-type Server stays
copy-friendly — the smoke test reflects over *Server and would trip
the vet lock-copy check if we embedded sync.Mutex/sync.Once
directly.
2026-05-25 15:49:27 -04:00
..
internal/clientsock tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
proto tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
traffic tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
conn.go tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
doc.go tsnet2: skeleton package and failing integration test 2026-05-25 15:48:31 -04:00
integration_test.go tsnet2: skeleton package and failing integration test 2026-05-25 15:48:31 -04:00
listener.go tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
localclient.go tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00
tsnet2_test.go tsnet2: skeleton package and failing integration test 2026-05-25 15:48:31 -04:00
tsnet2.go tsnet2: implement out-of-process daemon and client shim 2026-05-25 15:49:27 -04:00