A session is bound to its TCP connection with no roaming or migration path. On the node, a reconnect always builds a brand-new TCP+TLS+Auth session and roster.put evicts and Stop()s the previous flow (node/internal/server/server.go:220, roster.go:22); nothing ever moves a live session to a new remote address. Both clients tear down and rebuild on any interface-identity change: iOS runs NWPathMonitor -> Handoff.crossed -> pause()/resume() -> dial() builds a fresh TLSWire/AuthWire (ios/APNExtension/APNProvider.swift:186, :504), and Android does registerDefaultNetworkCallback -> Handoff.crossed -> disconnect -> reconnect -> start with a brand-new wire (android/.../vpn/VpnCore.kt:363, :236).
The tunnel IP itself survives (the reconnecting client keeps it via a refcounted lease in node/internal/server/lots.go), so what gets paid on every wifi<->cell switch, sleep/wake, or airplane-mode toggle is the full three-phase bring-up — TCP, then a hand-rolled TLS 1.3 handshake, then Auth/Welcome, roughly 3 RTT — plus an async Hub lease refetch. That cost is the backdrop to the reconnect-storm and wifi-switch pain (related but distinct: #184 is a crash on fast toggle; #164 was the revoke storm). WireGuard sidesteps all of it with endpoint roaming: an authenticated packet from a new source address just updates the peer endpoint (receive.go SetEndpointFromPacket) and the same crypto session follows the peer with zero handshake.
Add TLS 1.3 session resumption (RFC 8446 PSK / session tickets, optionally 0-RTT early_data carrying the Auth frame) so a reconnect collapses toward 1-RTT/0-RTT and re-attaches to the still-leased IP without a cold handshake or a fresh Hub round-trip. One constraint: the clients pin a Chrome JA4, so the resumed ClientHello must match a genuine Chrome ticket-resumption JA4 — achievable, but verify it. The cleaner long-term answer is QUIC connection migration (see #185), which makes roaming handshake-free by design. Filing per a WireGuard comparison review.
A session is bound to its TCP connection with no roaming or migration path. On the node, a reconnect always builds a brand-new TCP+TLS+Auth session and
roster.putevicts andStop()s the previous flow (node/internal/server/server.go:220,roster.go:22); nothing ever moves a live session to a new remote address. Both clients tear down and rebuild on any interface-identity change: iOS runsNWPathMonitor->Handoff.crossed->pause()/resume()->dial()builds a freshTLSWire/AuthWire(ios/APNExtension/APNProvider.swift:186,:504), and Android doesregisterDefaultNetworkCallback->Handoff.crossed->disconnect->reconnect->startwith a brand-new wire (android/.../vpn/VpnCore.kt:363,:236).The tunnel IP itself survives (the reconnecting client keeps it via a refcounted lease in
node/internal/server/lots.go), so what gets paid on every wifi<->cell switch, sleep/wake, or airplane-mode toggle is the full three-phase bring-up — TCP, then a hand-rolled TLS 1.3 handshake, then Auth/Welcome, roughly 3 RTT — plus an async Hub lease refetch. That cost is the backdrop to the reconnect-storm and wifi-switch pain (related but distinct: #184 is a crash on fast toggle; #164 was the revoke storm). WireGuard sidesteps all of it with endpoint roaming: an authenticated packet from a new source address just updates the peer endpoint (receive.goSetEndpointFromPacket) and the same crypto session follows the peer with zero handshake.Add TLS 1.3 session resumption (RFC 8446 PSK / session tickets, optionally 0-RTT
early_datacarrying the Auth frame) so a reconnect collapses toward 1-RTT/0-RTT and re-attaches to the still-leased IP without a cold handshake or a fresh Hub round-trip. One constraint: the clients pin a Chrome JA4, so the resumed ClientHello must match a genuine Chrome ticket-resumption JA4 — achievable, but verify it. The cleaner long-term answer is QUIC connection migration (see #185), which makes roaming handshake-free by design. Filing per a WireGuard comparison review.