From d65c0fd2d04a49fb11964cf0457df499a0e6e366 Mon Sep 17 00:00:00 2001 From: Jordan Whited Date: Tue, 15 Jul 2025 12:29:07 -0700 Subject: [PATCH] tailcfg,wgengine/magicsock: set peer relay CapVer (#16531) Updates tailscale/corp#27502 Updates tailscale/corp#30051 Signed-off-by: Jordan Whited --- tailcfg/tailcfg.go | 3 ++- wgengine/magicsock/debugknobs.go | 6 ------ wgengine/magicsock/debugknobs_stubs.go | 1 - wgengine/magicsock/magicsock.go | 19 ++++++++--------- wgengine/magicsock/magicsock_test.go | 28 ++++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 0f13c725e..636e2434d 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -164,7 +164,8 @@ type CapabilityVersion int // - 117: 2025-05-28: Client understands DisplayMessages (structured health messages), but not necessarily PrimaryAction. // - 118: 2025-07-01: Client sends Hostinfo.StateEncrypted to report whether the state file is encrypted at rest (#15830) // - 119: 2025-07-10: Client uses Hostinfo.Location.Priority to prioritize one route over another. -const CurrentCapabilityVersion CapabilityVersion = 119 +// - 120: 2025-07-15: Client understands peer relay disco messages, and implements peer client and relay server functions +const CurrentCapabilityVersion CapabilityVersion = 120 // ID is an integer ID for a user, node, or login allocated by the // control plane. diff --git a/wgengine/magicsock/debugknobs.go b/wgengine/magicsock/debugknobs.go index 055895388..f8fd9f040 100644 --- a/wgengine/magicsock/debugknobs.go +++ b/wgengine/magicsock/debugknobs.go @@ -62,12 +62,6 @@ var ( // //lint:ignore U1000 used on Linux/Darwin only debugPMTUD = envknob.RegisterBool("TS_DEBUG_PMTUD") - // debugAssumeUDPRelayCapable forces magicsock to assume that all peers are - // UDP relay capable clients and servers. This will eventually be replaced - // by a [tailcfg.CapabilityVersion] comparison. It enables early testing of - // the UDP relay feature before we have established related - // [tailcfg.CapabilityVersion]'s. - debugAssumeUDPRelayCapable = envknob.RegisterBool("TS_DEBUG_ASSUME_UDP_RELAY_CAPABLE") // Hey you! Adding a new debugknob? Make sure to stub it out in the // debugknobs_stubs.go file too. ) diff --git a/wgengine/magicsock/debugknobs_stubs.go b/wgengine/magicsock/debugknobs_stubs.go index 3d23b1f8e..336d7baa1 100644 --- a/wgengine/magicsock/debugknobs_stubs.go +++ b/wgengine/magicsock/debugknobs_stubs.go @@ -31,4 +31,3 @@ func debugRingBufferMaxSizeBytes() int { return 0 } func inTest() bool { return false } func debugPeerMap() bool { return false } func pretendpoints() []netip.AddrPort { return []netip.AddrPort{} } -func debugAssumeUDPRelayCapable() bool { return false } diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 14feed32b..a8b1c8f15 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -14,7 +14,6 @@ import ( "expvar" "fmt" "io" - "math" "net" "net/netip" "reflect" @@ -2616,14 +2615,10 @@ func (c *Conn) SetProbeUDPLifetime(v bool) { }) } +// capVerIsRelayCapable returns true if version is relay client and server +// capable, otherwise it returns false. func capVerIsRelayCapable(version tailcfg.CapabilityVersion) bool { - // TODO(jwhited): implement once capVer is bumped - return version == math.MinInt32 || debugAssumeUDPRelayCapable() -} - -func capVerIsRelayServerCapable(version tailcfg.CapabilityVersion) bool { - // TODO(jwhited): implement once capVer is bumped & update Test_peerAPIIfCandidateRelayServer - return version == math.MinInt32 || debugAssumeUDPRelayCapable() + return version >= 120 } // onFilterUpdate is called when a [FilterUpdate] is received over the @@ -2677,10 +2672,16 @@ func peerAPIIfCandidateRelayServer(filt *filter.Filter, self, maybeCandidate tai if filt == nil || !self.Valid() || !maybeCandidate.Valid() || - !capVerIsRelayServerCapable(maybeCandidate.Cap()) || !maybeCandidate.Hostinfo().Valid() { return netip.AddrPort{} } + if maybeCandidate.ID() != self.ID() && !capVerIsRelayCapable(maybeCandidate.Cap()) { + // If maybeCandidate's [tailcfg.CapabilityVersion] is not relay-capable, + // we skip it. If maybeCandidate happens to be self, then this check is + // unnecessary as self is always capable from this point (the statically + // compiled [tailcfg.CurrentCapabilityVersion]) forward. + return netip.AddrPort{} + } for _, maybeCandidatePrefix := range maybeCandidate.Addresses().All() { if !maybeCandidatePrefix.IsSingleIP() { continue diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 0515162c7..1d76e6c59 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -3399,7 +3399,11 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) { } selfOnlyIPv4 := &tailcfg.Node{ - Cap: math.MinInt32, + ID: 1, + // Intentionally set a value < 120 to verify the statically compiled + // [tailcfg.CurrentCapabilityVersion] is used when self is + // maybeCandidate. + Cap: 119, Addresses: []netip.Prefix{ netip.MustParsePrefix("1.1.1.1/32"), }, @@ -3409,13 +3413,17 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) { selfOnlyIPv6.Addresses[0] = netip.MustParsePrefix("::1/128") peerOnlyIPv4 := &tailcfg.Node{ - Cap: math.MinInt32, + ID: 2, + Cap: 120, Addresses: []netip.Prefix{ netip.MustParsePrefix("2.2.2.2/32"), }, Hostinfo: hostInfo.View(), } + peerOnlyIPv4NotCapable := peerOnlyIPv4.Clone() + peerOnlyIPv4NotCapable.Cap = 119 + peerOnlyIPv6 := peerOnlyIPv4.Clone() peerOnlyIPv6.Addresses[0] = netip.MustParsePrefix("::2/128") @@ -3500,6 +3508,22 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) { maybeCandidate: selfOnlyIPv6.View(), want: netip.AddrPortFrom(selfOnlyIPv6.Addresses[0].Addr(), 6), }, + { + name: "peer incapable", + filt: filter.New([]filtertype.Match{ + { + Srcs: []netip.Prefix{netip.MustParsePrefix("2.2.2.2/32")}, + Caps: []filtertype.CapMatch{ + { + Dst: netip.MustParsePrefix("1.1.1.1/32"), + Cap: tailcfg.PeerCapabilityRelayTarget, + }, + }, + }, + }, nil, nil, nil, nil, nil), + self: selfOnlyIPv4.View(), + maybeCandidate: peerOnlyIPv4NotCapable.View(), + }, { name: "no match dst", filt: filter.New([]filtertype.Match{