wgengine/magicsock: support self as candidate peer relay (#16499)

Updates tailscale/corp#30247

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited
2025-07-09 09:16:29 -07:00
committed by GitHub
parent 27fa2ad868
commit 008a238acd
2 changed files with 102 additions and 63 deletions

View File

@@ -2618,8 +2618,8 @@ func (c *Conn) onFilterUpdate(f FilterUpdate) {
c.updateRelayServersSet(f.Filter, self, peers) c.updateRelayServersSet(f.Filter, self, peers)
} }
// updateRelayServersSet iterates all peers, evaluating filt for each one in // updateRelayServersSet iterates all peers and self, evaluating filt for each
// order to determine which peers are relay server candidates. filt, self, and // one in order to determine which are relay server candidates. filt, self, and
// peers are passed as args (vs c.mu-guarded fields) to enable callers to // peers are passed as args (vs c.mu-guarded fields) to enable callers to
// release c.mu before calling as this is O(m * n) (we iterate all cap rules 'm' // release c.mu before calling as this is O(m * n) (we iterate all cap rules 'm'
// in filt for every peer 'n'). // in filt for every peer 'n').
@@ -2631,8 +2631,9 @@ func (c *Conn) onFilterUpdate(f FilterUpdate) {
// the computed result over the eventbus instead. // the computed result over the eventbus instead.
func (c *Conn) updateRelayServersSet(filt *filter.Filter, self tailcfg.NodeView, peers views.Slice[tailcfg.NodeView]) { func (c *Conn) updateRelayServersSet(filt *filter.Filter, self tailcfg.NodeView, peers views.Slice[tailcfg.NodeView]) {
relayServers := make(set.Set[netip.AddrPort]) relayServers := make(set.Set[netip.AddrPort])
for _, peer := range peers.All() { nodes := append(peers.AsSlice(), self)
peerAPI := peerAPIIfCandidateRelayServer(filt, self, peer) for _, maybeCandidate := range nodes {
peerAPI := peerAPIIfCandidateRelayServer(filt, self, maybeCandidate)
if peerAPI.IsValid() { if peerAPI.IsValid() {
relayServers.Add(peerAPI) relayServers.Add(peerAPI)
} }
@@ -2640,33 +2641,34 @@ func (c *Conn) updateRelayServersSet(filt *filter.Filter, self tailcfg.NodeView,
c.relayManager.handleRelayServersSet(relayServers) c.relayManager.handleRelayServersSet(relayServers)
} }
// peerAPIIfCandidateRelayServer returns the peer API address of peer if it // peerAPIIfCandidateRelayServer returns the peer API address of maybeCandidate
// is considered to be a candidate relay server upon evaluation against filt and // if it is considered to be a candidate relay server upon evaluation against
// self, otherwise it returns a zero value. // filt and self, otherwise it returns a zero value. self and maybeCandidate
func peerAPIIfCandidateRelayServer(filt *filter.Filter, self, peer tailcfg.NodeView) netip.AddrPort { // may be equal.
func peerAPIIfCandidateRelayServer(filt *filter.Filter, self, maybeCandidate tailcfg.NodeView) netip.AddrPort {
if filt == nil || if filt == nil ||
!self.Valid() || !self.Valid() ||
!peer.Valid() || !maybeCandidate.Valid() ||
!capVerIsRelayServerCapable(peer.Cap()) || !capVerIsRelayServerCapable(maybeCandidate.Cap()) ||
!peer.Hostinfo().Valid() { !maybeCandidate.Hostinfo().Valid() {
return netip.AddrPort{} return netip.AddrPort{}
} }
for _, peerPrefix := range peer.Addresses().All() { for _, maybeCandidatePrefix := range maybeCandidate.Addresses().All() {
if !peerPrefix.IsSingleIP() { if !maybeCandidatePrefix.IsSingleIP() {
continue continue
} }
peerAddr := peerPrefix.Addr() maybeCandidateAddr := maybeCandidatePrefix.Addr()
for _, selfPrefix := range self.Addresses().All() { for _, selfPrefix := range self.Addresses().All() {
if !selfPrefix.IsSingleIP() { if !selfPrefix.IsSingleIP() {
continue continue
} }
selfAddr := selfPrefix.Addr() selfAddr := selfPrefix.Addr()
if selfAddr.BitLen() == peerAddr.BitLen() { // same address family if selfAddr.BitLen() == maybeCandidateAddr.BitLen() { // same address family
if filt.CapsWithValues(peerAddr, selfAddr).HasCapability(tailcfg.PeerCapabilityRelayTarget) { if filt.CapsWithValues(maybeCandidateAddr, selfAddr).HasCapability(tailcfg.PeerCapabilityRelayTarget) {
for _, s := range peer.Hostinfo().Services().All() { for _, s := range maybeCandidate.Hostinfo().Services().All() {
if peerAddr.Is4() && s.Proto == tailcfg.PeerAPI4 || if maybeCandidateAddr.Is4() && s.Proto == tailcfg.PeerAPI4 ||
peerAddr.Is6() && s.Proto == tailcfg.PeerAPI6 { maybeCandidateAddr.Is6() && s.Proto == tailcfg.PeerAPI6 {
return netip.AddrPortFrom(peerAddr, s.Port) return netip.AddrPortFrom(maybeCandidateAddr, s.Port)
} }
} }
return netip.AddrPort{} // no peerAPI return netip.AddrPort{} // no peerAPI
@@ -2674,10 +2676,11 @@ func peerAPIIfCandidateRelayServer(filt *filter.Filter, self, peer tailcfg.NodeV
// [nodeBackend.peerCapsLocked] only returns/considers the // [nodeBackend.peerCapsLocked] only returns/considers the
// [tailcfg.PeerCapMap] between the passed src and the // [tailcfg.PeerCapMap] between the passed src and the
// _first_ host (/32 or /128) address for self. We are // _first_ host (/32 or /128) address for self. We are
// consistent with that behavior here. If self and peer // consistent with that behavior here. If self and
// host addresses are of the same address family they either // maybeCandidate host addresses are of the same address
// have the capability or not. We do not check against // family they either have the capability or not. We do not
// additional host addresses of the same address family. // check against additional host addresses of the same
// address family.
return netip.AddrPort{} return netip.AddrPort{}
} }
} }

View File

@@ -3385,16 +3385,7 @@ func Test_virtualNetworkID(t *testing.T) {
} }
func Test_peerAPIIfCandidateRelayServer(t *testing.T) { func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
selfOnlyIPv4 := &tailcfg.Node{ hostInfo := &tailcfg.Hostinfo{
Cap: math.MinInt32,
Addresses: []netip.Prefix{
netip.MustParsePrefix("1.1.1.1/32"),
},
}
selfOnlyIPv6 := selfOnlyIPv4.Clone()
selfOnlyIPv6.Addresses[0] = netip.MustParsePrefix("::1/128")
peerHostinfo := &tailcfg.Hostinfo{
Services: []tailcfg.Service{ Services: []tailcfg.Service{
{ {
Proto: tailcfg.PeerAPI4, Proto: tailcfg.PeerAPI4,
@@ -3406,12 +3397,23 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, },
} }
selfOnlyIPv4 := &tailcfg.Node{
Cap: math.MinInt32,
Addresses: []netip.Prefix{
netip.MustParsePrefix("1.1.1.1/32"),
},
Hostinfo: hostInfo.View(),
}
selfOnlyIPv6 := selfOnlyIPv4.Clone()
selfOnlyIPv6.Addresses[0] = netip.MustParsePrefix("::1/128")
peerOnlyIPv4 := &tailcfg.Node{ peerOnlyIPv4 := &tailcfg.Node{
Cap: math.MinInt32, Cap: math.MinInt32,
Addresses: []netip.Prefix{ Addresses: []netip.Prefix{
netip.MustParsePrefix("2.2.2.2/32"), netip.MustParsePrefix("2.2.2.2/32"),
}, },
Hostinfo: peerHostinfo.View(), Hostinfo: hostInfo.View(),
} }
peerOnlyIPv6 := peerOnlyIPv4.Clone() peerOnlyIPv6 := peerOnlyIPv4.Clone()
@@ -3427,7 +3429,7 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
name string name string
filt *filter.Filter filt *filter.Filter
self tailcfg.NodeView self tailcfg.NodeView
peer tailcfg.NodeView maybeCandidate tailcfg.NodeView
want netip.AddrPort want netip.AddrPort
}{ }{
{ {
@@ -3444,9 +3446,26 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv4.View(), self: selfOnlyIPv4.View(),
peer: peerOnlyIPv4.View(), maybeCandidate: peerOnlyIPv4.View(),
want: netip.MustParseAddrPort("2.2.2.2:4"), want: netip.MustParseAddrPort("2.2.2.2:4"),
}, },
{
name: "match v4 self",
filt: filter.New([]filtertype.Match{
{
Srcs: []netip.Prefix{selfOnlyIPv4.Addresses[0]},
Caps: []filtertype.CapMatch{
{
Dst: selfOnlyIPv4.Addresses[0],
Cap: tailcfg.PeerCapabilityRelayTarget,
},
},
},
}, nil, nil, nil, nil, nil),
self: selfOnlyIPv4.View(),
maybeCandidate: selfOnlyIPv4.View(),
want: netip.AddrPortFrom(selfOnlyIPv4.Addresses[0].Addr(), 4),
},
{ {
name: "match v6", name: "match v6",
filt: filter.New([]filtertype.Match{ filt: filter.New([]filtertype.Match{
@@ -3461,9 +3480,26 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv6.View(), self: selfOnlyIPv6.View(),
peer: peerOnlyIPv6.View(), maybeCandidate: peerOnlyIPv6.View(),
want: netip.MustParseAddrPort("[::2]:6"), want: netip.MustParseAddrPort("[::2]:6"),
}, },
{
name: "match v6 self",
filt: filter.New([]filtertype.Match{
{
Srcs: []netip.Prefix{selfOnlyIPv6.Addresses[0]},
Caps: []filtertype.CapMatch{
{
Dst: selfOnlyIPv6.Addresses[0],
Cap: tailcfg.PeerCapabilityRelayTarget,
},
},
},
}, nil, nil, nil, nil, nil),
self: selfOnlyIPv6.View(),
maybeCandidate: selfOnlyIPv6.View(),
want: netip.AddrPortFrom(selfOnlyIPv6.Addresses[0].Addr(), 6),
},
{ {
name: "no match dst", name: "no match dst",
filt: filter.New([]filtertype.Match{ filt: filter.New([]filtertype.Match{
@@ -3478,7 +3514,7 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv6.View(), self: selfOnlyIPv6.View(),
peer: peerOnlyIPv6.View(), maybeCandidate: peerOnlyIPv6.View(),
}, },
{ {
name: "no match peer cap", name: "no match peer cap",
@@ -3494,7 +3530,7 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv6.View(), self: selfOnlyIPv6.View(),
peer: peerOnlyIPv6.View(), maybeCandidate: peerOnlyIPv6.View(),
}, },
{ {
name: "cap ver not relay capable", name: "cap ver not relay capable",
@@ -3510,13 +3546,13 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: peerOnlyIPv4.View(), self: peerOnlyIPv4.View(),
peer: peerOnlyIPv4ZeroCapVer.View(), maybeCandidate: peerOnlyIPv4ZeroCapVer.View(),
}, },
{ {
name: "nil filt", name: "nil filt",
filt: nil, filt: nil,
self: selfOnlyIPv4.View(), self: selfOnlyIPv4.View(),
peer: peerOnlyIPv4.View(), maybeCandidate: peerOnlyIPv4.View(),
}, },
{ {
name: "nil self", name: "nil self",
@@ -3532,7 +3568,7 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: tailcfg.NodeView{}, self: tailcfg.NodeView{},
peer: peerOnlyIPv4.View(), maybeCandidate: peerOnlyIPv4.View(),
}, },
{ {
name: "nil peer", name: "nil peer",
@@ -3548,7 +3584,7 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv4.View(), self: selfOnlyIPv4.View(),
peer: tailcfg.NodeView{}, maybeCandidate: tailcfg.NodeView{},
}, },
{ {
name: "nil peer hostinfo", name: "nil peer hostinfo",
@@ -3564,12 +3600,12 @@ func Test_peerAPIIfCandidateRelayServer(t *testing.T) {
}, },
}, nil, nil, nil, nil, nil), }, nil, nil, nil, nil, nil),
self: selfOnlyIPv4.View(), self: selfOnlyIPv4.View(),
peer: peerOnlyIPv4NilHostinfo.View(), maybeCandidate: peerOnlyIPv4NilHostinfo.View(),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := peerAPIIfCandidateRelayServer(tt.filt, tt.self, tt.peer); !reflect.DeepEqual(got, tt.want) { if got := peerAPIIfCandidateRelayServer(tt.filt, tt.self, tt.maybeCandidate); !reflect.DeepEqual(got, tt.want) {
t.Errorf("peerAPIIfCandidateRelayServer() = %v, want %v", got, tt.want) t.Errorf("peerAPIIfCandidateRelayServer() = %v, want %v", got, tt.want)
} }
}) })