ipn/ipnlocal: use routecheck reports to make exit node suggestions
Now that the routecheck subsystem is continuously collecting reachability reports in the background, we can add a hook to LocalBackend for fetching its report. That allows suggestExitNodeUsingTrafficSteering to consult that report when disqualifying candidates, instead of blocking on an immediate probe. Exit node suggestions will only consult the report when the `client-side-reachability` and `client-side-reachability-routecheck` node attributes are both set on the current node. Updates #17366 Updates tailscale/corp#33033 Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
@@ -6,13 +6,18 @@ package ipnlocal
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"maps"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/routecheck/peernode"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
func TestNodeBackendReadiness(t *testing.T) {
|
||||
@@ -134,58 +139,135 @@ func TestNodeBackendReachability(t *testing.T) {
|
||||
//
|
||||
// When disabled, the client relies on the control plane sending
|
||||
// an accurate peer.Online flag. When enabled, the client
|
||||
// ignores peer.Online and determines whether it can reach the
|
||||
// peer node.
|
||||
// ignores peer.Online and is forced to return true.
|
||||
cap bool
|
||||
// rchk sets [tailcfg.NodeAttrClientSideReachabilityRouteCheck]
|
||||
// on the self node.
|
||||
//
|
||||
// When enabled with [tailcfg.NodeAttrClientSideReachability]
|
||||
// above, the client ignores peer.Online and determines whether
|
||||
// it can reach the peer node using [routecheck] reports.
|
||||
rchk bool
|
||||
|
||||
peer tailcfg.Node
|
||||
want bool
|
||||
online bool
|
||||
pong peernode.Reachability
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "disabled/offline",
|
||||
cap: false,
|
||||
peer: tailcfg.Node{
|
||||
Online: new(false),
|
||||
},
|
||||
want: false,
|
||||
name: "disabled/offline",
|
||||
cap: false,
|
||||
online: false,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "disabled/online",
|
||||
cap: false,
|
||||
peer: tailcfg.Node{
|
||||
Online: new(true),
|
||||
},
|
||||
want: true,
|
||||
name: "disabled/online",
|
||||
cap: false,
|
||||
online: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "enabled/offline",
|
||||
cap: true,
|
||||
peer: tailcfg.Node{
|
||||
Online: new(false),
|
||||
},
|
||||
want: true,
|
||||
name: "forced/offline",
|
||||
cap: true,
|
||||
rchk: false,
|
||||
online: false,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "enabled/online",
|
||||
cap: true,
|
||||
peer: tailcfg.Node{
|
||||
Online: new(true),
|
||||
},
|
||||
want: true,
|
||||
name: "forced/online",
|
||||
cap: true,
|
||||
rchk: false,
|
||||
online: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "routecheck/offline/needs-probe",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: false,
|
||||
pong: peernode.Unknown,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "routecheck/offline/unreachable",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: false,
|
||||
pong: peernode.Unreachable,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "routecheck/offline/reachable",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: false,
|
||||
pong: peernode.Reachable,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "routecheck/online/needs-probe",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: true,
|
||||
pong: peernode.Unknown,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "routecheck/online/unreachable",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: true,
|
||||
pong: peernode.Unreachable,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "routecheck/online/reachable",
|
||||
cap: true,
|
||||
rchk: true,
|
||||
online: true,
|
||||
pong: peernode.Reachable,
|
||||
want: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
|
||||
nb.netMap = &netmap.NetworkMap{}
|
||||
self := &tailcfg.Node{
|
||||
ID: 1,
|
||||
StableID: "stable1",
|
||||
Name: "self",
|
||||
}
|
||||
if tc.cap {
|
||||
nb.netMap.AllCaps.Make()
|
||||
nb.netMap.AllCaps.Add(tailcfg.NodeAttrClientSideReachability)
|
||||
mak.Set(&self.CapMap, tailcfg.NodeAttrClientSideReachability, nil)
|
||||
}
|
||||
if tc.rchk {
|
||||
mak.Set(&self.CapMap, tailcfg.NodeAttrClientSideReachabilityRouteCheck, nil)
|
||||
}
|
||||
|
||||
got := nb.PeerIsReachable(t.Context(), tc.peer.View())
|
||||
peer := &tailcfg.Node{
|
||||
ID: 2,
|
||||
StableID: "stable2",
|
||||
Name: "peer",
|
||||
Online: &tc.online,
|
||||
}
|
||||
|
||||
nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
|
||||
nb.netMap = &netmap.NetworkMap{
|
||||
SelfNode: self.View(),
|
||||
Peers: []tailcfg.NodeView{peer.View()},
|
||||
// HACK: AllCaps is usually populated by Control
|
||||
AllCaps: set.SetOf(slices.Collect(maps.Keys(self.CapMap))),
|
||||
}
|
||||
|
||||
got := nb.PeerIsReachable(routecheckReport(tc.pong), peer.View())
|
||||
if got != tc.want {
|
||||
t.Errorf("got %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type routecheckReport peernode.Reachability
|
||||
|
||||
var _ RouteCheckReport = *new(routecheckReport)
|
||||
|
||||
func (rp routecheckReport) IsReachable(_ tailcfg.NodeID) peernode.Reachability {
|
||||
return peernode.Reachability(rp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user