Repository navigation
fix(control): send the advertised endpoints on every map request - #662
Conversation
Control takes the endpoints of each endpoint update (a map request with OmitPeers, without Stream and ReadOnly; tailcfg MapRequest.OmitPeers) as the node's endpoints. Only SetEndpoints sent them. The Hostinfo update after each (re)connect, SetDerpHomeRegion, SetRoutableIPs and SetHostname went without, so control dropped the node's endpoints, and they came back only when the endpoint set changed. After a map-poll reconnect, which brings a derp map and so a SetDerpHomeRegion, a node on a stable network could therefore have no endpoints at control. A peer that starts from a fresh netmap, with no cached candidate for the node, then has nothing to ping, and Go sends a CallMeMaybe only after a ping (endpoint.go:1446-1462), so it cannot start a direct path to the node. Keep the endpoints from the last SetEndpoints with the carried NetInfo and attach them to every request, as Go attaches c.endpoints to each one (direct.go:1048-1104). The carried state outlives the poll, so a SetEndpoints whose own request is lost is sent again after a reconnect. The post-connect Hostinfo update carries the state from when the poll started, and it could still be unsent or in flight when a side command sent newer state. Control could then apply it last and clear the endpoints again. A side command now records its state, then waits for that update before sending its own, as Go sends all updates one at a time from updateRoutine (auto.go:61-109). Recording first means a watchdog restart during the wait cannot lose the command's state. Go references are at tailscale/tailscale 3bb8a906. Signed-off-by: Kasper Nielsen <kasper0406@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
Thanks, this looks right to us and we'll merge it as is. We checked it against the Go client. Go's We aren't changing anything here:
We'll take this up separately: routes and hostname set at runtime still aren't carried, the same way endpoints weren't. tailscale-rs/ts_control/src/tokio/client.rs Line 1039 in c4e03e1 config.advertised_routes() and config.hostname (tailscale-rs/ts_control/src/tokio/client.rs Line 921 in c4e03e1 tailscale-rs/ts_control/src/tokio/client.rs Line 980 in c4e03e1 CarriedNetInfo (
) should probably get a broader name.
|
|
Merged as 4168e0a. Thanks for the careful write-up and the tests, @kasper0406. The follow-ups you listed (carrying runtime routes and hostname) will go in a separate issue. |
…ngine 0.58.4 (#662) Engine 0.58.4 (GeiserX/tailscale-rs#662) sends the advertised endpoints on every map request. Before it, the Hostinfo update after a map-poll reconnect, and DERP-home, route and hostname updates, went out without endpoints, so control dropped them until the endpoint set changed and peers could not open a direct path. Pin 0631a5fc (0.58.2) -> d92b8598 (0.58.4). Capver stays 125. Only engine crates move in Cargo.lock. Signed-off-by: Sergio Fernández <9169332+GeiserX@users.noreply.github.com>
Problem. Control takes the endpoints of every endpoint update as the node's endpoints. An endpoint update is a map request with
OmitPeers, withoutStreamand withoutReadOnly(tailcfgMapRequest.OmitPeers). Ints_control/src/tokio/client.rs, onlySetEndpointsset them.SetDerpHomeRegion, which follows every reconnect because the reconnect brings a derp map;SetRoutableIPsandSetHostname.CallMeMaybeonly after a ping (endpoint.go:1446-1462), so that peer cannot start a direct path.Seen on an exit node. After a map-poll reconnect, a Go client's netmap listed the node with HomeDERP but no
Endpoints. A peer whose Tailscale had restarted did not get back to a direct path until the node was restarted. Its first packet after the restart was a direct ping to the re-advertised endpoint. That restart also reset other state, so this is supporting context, not proof of the whole outage.Fix:
CarriedNetInfokeeps the endpoints from the lastSetEndpoints, andapplyattaches them to every request. Go attachesc.endpointsto each request too (direct.go:1048-1104). Because the carried state outlives the poll, endpoints whoseSetEndpointsrequest was lost are sent again on the next reconnect.updateRoutine(auto.go:61-109).Tests:
every_request_carries_the_last_advertised_endpointsfails ifapplydrops the endpoints orset_endpointsdoes not store them.startup_update_is_applied_before_a_side_updatefails without the wait.side_command_state_survives_a_watchdog_restart_while_waiting_for_startup(real watchdog, paused clock) fails if the state is recorded after the wait.SetDerpHomeRegionrequest after aSetEndpointsserializes noEndpoints.cargo test -p geiserx_ts_control --all-features, clippy-D warningsand fmt pass.Not in this PR:
SetRoutableIPsandSetHostnamevalues are not carried, so later updates revert them to the start-up config.registersends no NetInfo.updateRoutine).Go references are at tailscale/tailscale
3bb8a906.Created using Claude Code. Reviewed with Codex (GPT-6.1) before opening; the tests above were run locally.
🤖 Generated with Claude Code