tailcfg,wgengine/magicsock: set peer relay CapVer (#16531)

Updates tailscale/corp#27502
Updates tailscale/corp#30051

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited
2025-07-15 12:29:07 -07:00
committed by GitHub
parent ffe8cc9442
commit d65c0fd2d0
5 changed files with 38 additions and 19 deletions

View File

@@ -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.

View File

@@ -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.
)

View File

@@ -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 }

View File

@@ -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

View File

@@ -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{