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:
Simon Law
2026-07-02 20:26:27 -07:00
committed by Simon Law
parent cb7e536804
commit 932260511e
19 changed files with 306 additions and 74 deletions
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package peernode contains constants that describe a peer node in the routecheck report.
package peernode
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package peernode
import "fmt"
// Reachability reports whether a peer node is reachable.
type Reachability int
const (
// Unknown is a peer node whose reachability is unknown.
// This peer is new and we have yet to probe it.
Unknown Reachability = iota
// Reachable is a peer node that is reachable from this node.
Reachable
// Unreachable is a peer node that is unreachable from this node.
// This peer is not responding to probes.
Unreachable
)
// String implements [fmt.Stringer].
func (r Reachability) String() string {
switch r {
case Unknown:
return "Unknown"
case Reachable:
return "Reachable"
case Unreachable:
return "Unreachable"
default:
panic(fmt.Sprintf("unknown %#v", r))
}
}
// IsReachable reports if r is [Reachable].
func (r Reachability) IsReachable() bool {
return r == Reachable
}
+14
View File
@@ -15,6 +15,7 @@ import (
"github.com/go-json-experiment/json/jsontext"
jsonv1 "github.com/go-json-experiment/json/v1"
"tailscale.com/net/routecheck/peernode"
"tailscale.com/tailcfg"
"tailscale.com/util/clientmetric"
"tailscale.com/util/mak"
@@ -46,6 +47,19 @@ type Report struct {
LastProbed map[tailcfg.NodeID]time.Time `json:"-"` // not marshaled
}
// IsReachable reports whether a peer is reachable by the current node.
func (rp Report) IsReachable(id tailcfg.NodeID) peernode.Reachability {
// TODO(sfllaw): We should actually track all routers and consider the
// absence of a router in the report as it being recently added for
// consideration, so it is unknown. Then we should positively track
// whether a node was reachable or not.
_, k := rp.Reachable[id]
if k {
return peernode.Reachable
}
return peernode.Unknown
}
// RoutablePrefixes returns a map of routable network prefixes associated with
// each prefixs routers that were reachable by the current host,
// at the time the report was finished.