wgengine/magicsock: consider VNI as part of peer relay handshake suppression

Otherwise we may never handshake a new peer relay server endpoint
around remote client restarts and/or disco key rotation.

Updates #20215

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited
2026-06-23 13:09:52 -07:00
committed by Jordan Whited
parent b7422fa873
commit badd0c4f93
2 changed files with 56 additions and 4 deletions
+4 -2
View File
@@ -790,8 +790,10 @@ func (r *relayManager) handleNewServerEndpointRunLoop(newServerEndpoint newRelay
go r.sendCallMeMaybeVia(newServerEndpoint.wlb.ep, newServerEndpoint.se)
}
lastBestMatchingServer := newServerEndpoint.se.ServerDisco.Compare(newServerEndpoint.wlb.lastBest.relayServerDisco) == 0
if lastBestMatchingServer && newServerEndpoint.wlb.lastBestIsTrusted {
lastBestMatchingServerVNI := newServerEndpoint.wlb.lastBest.vni.IsSet() &&
newServerEndpoint.se.ServerDisco.Compare(newServerEndpoint.wlb.lastBest.relayServerDisco) == 0 &&
newServerEndpoint.se.VNI == newServerEndpoint.wlb.lastBest.vni.Get()
if lastBestMatchingServerVNI && newServerEndpoint.wlb.lastBestIsTrusted {
// This relay server endpoint is the same as [endpoint]'s bestAddr at
// the time UDP relay path discovery was started, and it was also a
// trusted path (see endpoint.trustBestAddrUntil), so return early.
+52 -2
View File
@@ -109,12 +109,36 @@ func TestRelayManager_handleNewServerEndpointRunLoop(t *testing.T) {
serverDiscoA := key.NewDisco().Public()
serverDiscoB := key.NewDisco().Public()
// getAddrQuality returns an addrQuality with serverDisco with the provided
// VNI set. A negative vni leaves the VNI unset.
getAddrQuality := func(serverDisco key.DiscoPublic, vni int) addrQuality {
aq := addrQuality{relayServerDisco: serverDisco}
if vni >= 0 {
aq.epAddr.vni.Set(uint32(vni))
}
return aq
}
serverAendpointALamport1VNI1 := newRelayServerEndpointEvent{
wlb: endpointWithLastBest{ep: epA},
se: udprelay.ServerEndpoint{ServerDisco: serverDiscoA, LamportID: 1, VNI: 1},
}
// lastBest matches the new server endpoint on both server disco and VNI, and
// is trusted: suppression should fire.
serverAendpointALamport1VNI1LastBestMatching := newRelayServerEndpointEvent{
wlb: endpointWithLastBest{ep: epA, lastBestIsTrusted: true, lastBest: addrQuality{relayServerDisco: serverDiscoA}},
wlb: endpointWithLastBest{ep: epA, lastBestIsTrusted: true, lastBest: getAddrQuality(serverDiscoA, 1)},
se: udprelay.ServerEndpoint{ServerDisco: serverDiscoA, LamportID: 1, VNI: 1},
}
// lastBest matches the new server endpoint on server disco but NOT VNI (1 vs
// 2), and is trusted: suppression should NOT fire.
serverAendpointALamport1VNI1LastBestMatchingServerNeqVNI := newRelayServerEndpointEvent{
wlb: endpointWithLastBest{ep: epA, lastBestIsTrusted: true, lastBest: getAddrQuality(serverDiscoA, 2)},
se: udprelay.ServerEndpoint{ServerDisco: serverDiscoA, LamportID: 1, VNI: 1},
}
// lastBest matches the new server endpoint on server disco, is trusted, but
// has no VNI set: suppression should NOT fire.
serverAendpointALamport1VNI1LastBestMatchingServerUnsetVNI := newRelayServerEndpointEvent{
wlb: endpointWithLastBest{ep: epA, lastBestIsTrusted: true, lastBest: getAddrQuality(serverDiscoA, -1)},
se: udprelay.ServerEndpoint{ServerDisco: serverDiscoA, LamportID: 1, VNI: 1},
}
serverAendpointALamport2VNI1 := newRelayServerEndpointEvent{
@@ -212,12 +236,38 @@ func TestRelayManager_handleNewServerEndpointRunLoop(t *testing.T) {
},
},
{
name: "trusted-last-best-with-matching-server",
// Trusted lastBest matching on both server disco and VNI suppresses
// the new handshake.
name: "trusted-last-best-matching-server-and-vni",
events: []newRelayServerEndpointEvent{
serverAendpointALamport1VNI1LastBestMatching,
},
want: []newRelayServerEndpointEvent{},
},
{
// Trusted lastBest matching on server disco but NOT VNI must not
// suppress the new handshake, otherwise we may never handshake a new
// peer relay server endpoint around remote client restarts and/or
// disco key rotation (#20215).
name: "trusted-last-best-matching-server-neq-vni",
events: []newRelayServerEndpointEvent{
serverAendpointALamport1VNI1LastBestMatchingServerNeqVNI,
},
want: []newRelayServerEndpointEvent{
serverAendpointALamport1VNI1LastBestMatchingServerNeqVNI,
},
},
{
// Trusted lastBest matching on server disco with an unset VNI must
// not suppress the new handshake.
name: "trusted-last-best-matching-server-unset-vni",
events: []newRelayServerEndpointEvent{
serverAendpointALamport1VNI1LastBestMatchingServerUnsetVNI,
},
want: []newRelayServerEndpointEvent{
serverAendpointALamport1VNI1LastBestMatchingServerUnsetVNI,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {