change: do not echo a node's own report back to it

An endpoint or DERP report came back to the node as a self update;
SaaS sends it to the node's peers only.
This commit is contained in:
Kristoffer Dalby committed 2026-10-28 16:35:22 +00:00
1 parent c3b61f6d58
commit 1a92777b4f
4 files changed
+8 -14

No files matched your search

+1 -1
View File
@@ -274,7 +274,7 @@ func TestAddToBatchDifferential(t *testing.T) {
lb.b.nodes.Store(99, nil)
}
endpoint := change.EndpointOrDERPUpdate(1, &tailcfg.PeerChange{
endpoint := change.EndpointOrDERPUpdate(&tailcfg.PeerChange{
NodeID: 1, DERPRegion: 7, Online: new(true),
})
ping := change.PingNode(2, &tailcfg.PingRequest{URL: "https://example.com/one"})
@@ -59,10 +59,6 @@ var mapStreamSkipReasons = map[string]string{
"map-delivery/S3.5-remove/db1/frames": policyResponseToEveryNode,
"map-delivery/S3.5-restore/db1/frames": policyResponseToEveryNode,
"map-delivery/S2.1/laptop/frames": ownReportEchoed,
"map-delivery/S2.3/laptop/frames": ownReportEchoed,
"map-delivery/S2.5/laptop/self-endpoints": ownReportEchoed,
"map-delivery/S2.5/laptop/endpoints-gap-bystander": restartEndpointsKept,
"map-delivery/S2.5/laptop/endpoints-gap-svc": restartEndpointsKept,
@@ -121,7 +117,6 @@ var mapStreamSkipReasons = map[string]string{
const (
policyResponseToEveryNode = "POLICY_RESPONSE_TO_EVERY_NODE: headscale sends every connected node a policy response when the policy, a node's tags or approved routes, or the set of nodes change; SaaS sends only the nodes whose peers or filter change"
ownReportEchoed = "OWN_REPORT_ECHOED: headscale sends a node its own endpoint or DERP report back; SaaS sends it nothing, so its own entry keeps the endpoints of its last full map"
restartEndpointsKept = "RESTART_ENDPOINTS_KEPT: headscale keeps a restarted node's last endpoints at its peers until it reports new ones; SaaS sends peers the node without endpoints until then"
reconnectAnnounced = "RECONNECT_ANNOUNCED: headscale tells peers a node is online after a stream gap of a few seconds; SaaS sends them nothing"
routerReconnectRecompute = "ROUTER_RECONNECT_RECOMPUTE: headscale sends every node a policy response when a subnet router's stream comes back after a few seconds; SaaS sends nothing"
+1 -1
View File
@@ -4207,7 +4207,7 @@ func buildMapRequestChangeResponse(
patch.DERPRegion = currentDERP
}
return change.EndpointOrDERPUpdate(id, patch)
return change.EndpointOrDERPUpdate(patch)
}
return change.Change{}
+6 -7
View File
@@ -595,13 +595,12 @@ func KeyExpiryFor(id types.NodeID, expiry time.Time) Change {
return c
}
// EndpointOrDERPUpdate returns a [Change] for when a node's endpoints or DERP region changes.
// The [Change.OriginNode] field enables self-update detection by the mapper.
func EndpointOrDERPUpdate(id types.NodeID, patch *tailcfg.PeerChange) Change {
c := PeerPatched("endpoint/DERP update", patch)
c.OriginNode = id
return c
// EndpointOrDERPUpdate returns a [Change] for when a node reports new
// endpoints or a new DERP region. It has no [Change.OriginNode]: the node
// already knows what it reported, so, as on Tailscale SaaS, only its peers
// are sent the patch.
func EndpointOrDERPUpdate(patch *tailcfg.PeerChange) Change {
return PeerPatched("endpoint/DERP update", patch)
}
// NodeKeyRotated returns a [Change] for a node re-logging in: its NodeKey (and