From 6a93b17c8cafc1d8e1c52e133511e52ed9086355 Mon Sep 17 00:00:00 2001 From: Jordan Whited Date: Tue, 10 Jun 2025 17:31:14 -0700 Subject: [PATCH] types/netmap,wgengine/magicsock: propagate CapVer to magicsock.endpoint (#16244) This enables us to mark nodes as relay capable or not. We don't actually do that yet, as we haven't established a relay CapVer. Updates tailscale/corp#27502 Signed-off-by: Jordan Whited --- types/netmap/nodemut.go | 13 +++++++++++++ types/netmap/nodemut_test.go | 9 +++++++++ wgengine/magicsock/endpoint.go | 2 ++ wgengine/magicsock/magicsock.go | 9 +++++++++ 4 files changed, 33 insertions(+) diff --git a/types/netmap/nodemut.go b/types/netmap/nodemut.go index f4de1bf0b..ab30ef1e6 100644 --- a/types/netmap/nodemut.go +++ b/types/netmap/nodemut.go @@ -69,6 +69,17 @@ func (m NodeMutationLastSeen) Apply(n *tailcfg.Node) { n.LastSeen = ptr.To(m.LastSeen) } +// NodeMutationCap is a NodeMutation that says a node's +// [tailcfg.CapabilityVersion] value has changed. +type NodeMutationCap struct { + mutatingNodeID + Cap tailcfg.CapabilityVersion +} + +func (m NodeMutationCap) Apply(n *tailcfg.Node) { + n.Cap = m.Cap +} + var peerChangeFields = sync.OnceValue(func() []reflect.StructField { var fields []reflect.StructField rt := reflect.TypeFor[tailcfg.PeerChange]() @@ -105,6 +116,8 @@ func NodeMutationsFromPatch(p *tailcfg.PeerChange) (_ []NodeMutation, ok bool) { ret = append(ret, NodeMutationOnline{mutatingNodeID(p.NodeID), *p.Online}) case "LastSeen": ret = append(ret, NodeMutationLastSeen{mutatingNodeID(p.NodeID), *p.LastSeen}) + case "Cap": + ret = append(ret, NodeMutationCap{mutatingNodeID(p.NodeID), p.Cap}) } } return ret, true diff --git a/types/netmap/nodemut_test.go b/types/netmap/nodemut_test.go index 374f8623a..0f1cac6b2 100644 --- a/types/netmap/nodemut_test.go +++ b/types/netmap/nodemut_test.go @@ -177,6 +177,14 @@ func TestMutationsFromMapResponse(t *testing.T) { }, want: nil, }, + { + name: "patch-cap", + mr: fromChanges(&tailcfg.PeerChange{ + NodeID: 1, + Cap: 2, + }), + want: muts(NodeMutationCap{1, 2}), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -195,6 +203,7 @@ func TestMutationsFromMapResponse(t *testing.T) { NodeMutationDERPHome{}, NodeMutationOnline{}, NodeMutationLastSeen{}, + NodeMutationCap{}, )); diff != "" { t.Errorf("wrong result (-want +got):\n%s", diff) } diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index bf7758fb8..23316dcb4 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -1423,6 +1423,8 @@ func (de *endpoint) updateFromNode(n tailcfg.NodeView, heartbeatDisabled bool, p } de.setEndpointsLocked(n.Endpoints()) + + de.relayCapable = capVerIsRelayCapable(n.Cap()) } func (de *endpoint) setEndpointsLocked(eps interface { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 2e2882110..e5cc87dc3 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2507,6 +2507,11 @@ func (c *Conn) SetProbeUDPLifetime(v bool) { }) } +func capVerIsRelayCapable(version tailcfg.CapabilityVersion) bool { + // TODO(jwhited): implement once capVer is bumped + return false +} + // SetNetworkMap is called when the control client gets a new network // map from the control server. It must always be non-nil. // @@ -3203,6 +3208,10 @@ func (c *Conn) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bool) { ep.mu.Lock() ep.setEndpointsLocked(views.SliceOf(m.Endpoints)) ep.mu.Unlock() + case netmap.NodeMutationCap: + ep.mu.Lock() + ep.relayCapable = capVerIsRelayCapable(m.Cap) + ep.mu.Unlock() } } return true