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:
@@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/routecheck/peernode"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -448,10 +449,37 @@ func (nb *nodeBackend) PeerAPIBase(p tailcfg.NodeView) string {
|
||||
return peerAPIBase(nm, p)
|
||||
}
|
||||
|
||||
// PeerIsReachable reports whether the current node can reach p. If the ctx is
|
||||
// done, this function may return a result based on stale reachability data.
|
||||
func (nb *nodeBackend) PeerIsReachable(ctx context.Context, p tailcfg.NodeView) bool {
|
||||
if !nb.SelfHasCap(tailcfg.NodeAttrClientSideReachability) {
|
||||
// RouteCheckReport is an interface that reports whether a peer is reachable by the current node.
|
||||
type RouteCheckReport interface {
|
||||
// IsReachable reports whether a peer is reachable by the current node.
|
||||
IsReachable(tailcfg.NodeID) peernode.Reachability
|
||||
}
|
||||
|
||||
// PeerIsReachable reports whether the current node can reach p.
|
||||
// This function may return a result based on stale reachability data,
|
||||
// either from the control plane or because the latest routecheck report is old.
|
||||
// If rp is nil, then this will report whether p is connected to the control plane
|
||||
// according to [tailcfg.NodeView.Online].
|
||||
//
|
||||
// The latest routecheck report will be considered if the current node has both
|
||||
// [tailcfg.NodeAttrClientSideReachability] and [tailcfg.NodeAttrClientSideReachabilityRouteCheck]
|
||||
// in its CapMap.
|
||||
func (nb *nodeBackend) PeerIsReachable(rp RouteCheckReport, p tailcfg.NodeView) bool {
|
||||
nb.mu.Lock()
|
||||
nm := nb.netMap
|
||||
nb.mu.Unlock()
|
||||
|
||||
if nm == nil || !p.Valid() {
|
||||
// If there is no netmap, then how did we get a NodeView?
|
||||
// Assuming that p came from the control plane,
|
||||
// report whether it was connected to tailcontrol.
|
||||
return p.Valid() && p.Online().Get()
|
||||
}
|
||||
|
||||
self := nm.SelfNode
|
||||
useRouteCheck := isRouteCheckEnabled(self)
|
||||
|
||||
if !useRouteCheck && !self.HasCap(tailcfg.NodeAttrClientSideReachability) {
|
||||
// Legacy behavior is to always trust the control plane, which
|
||||
// isn’t always correct because the peer could be slow to check
|
||||
// in so that control marks it as offline.
|
||||
@@ -459,27 +487,34 @@ func (nb *nodeBackend) PeerIsReachable(ctx context.Context, p tailcfg.NodeView)
|
||||
return p.Online().Get()
|
||||
}
|
||||
|
||||
nb.mu.Lock()
|
||||
nm := nb.netMap
|
||||
nb.mu.Unlock()
|
||||
|
||||
if self := nm.SelfNode; self.Valid() && self.ID() == p.ID() {
|
||||
if self.Valid() && self.ID() == p.ID() {
|
||||
// This node can always reach itself.
|
||||
return true
|
||||
}
|
||||
return nb.peerIsReachable(ctx, p)
|
||||
}
|
||||
|
||||
func (nb *nodeBackend) peerIsReachable(ctx context.Context, p tailcfg.NodeView) bool {
|
||||
// TODO(sfllaw): The following does not actually test for client-side
|
||||
// reachability. This would require a mechanism that tracks whether the
|
||||
// current node can actually reach this peer, either because they are
|
||||
// already communicating or because they can ping each other.
|
||||
//
|
||||
// Instead, it makes the client ignore p.Online completely.
|
||||
//
|
||||
// See tailscale/corp#32686.
|
||||
return true
|
||||
if !useRouteCheck && !self.HasCap(tailcfg.NodeAttrClientSideReachabilityRouteCheck) {
|
||||
// TODO(sfllaw): The following does not actually test for client-side
|
||||
// reachability. This would require a mechanism that tracks whether the
|
||||
// current node can actually reach this peer, either because they are
|
||||
// already communicating or because they can ping each other.
|
||||
//
|
||||
// Instead, it makes the client ignore p.Online completely.
|
||||
//
|
||||
// See tailscale/corp#32686.
|
||||
return true
|
||||
}
|
||||
|
||||
if rp == nil {
|
||||
// The routecheck report hasn’t been collected yet,
|
||||
// so fall back and report whether it was connected to tailcontrol.
|
||||
return p.Online().Get()
|
||||
}
|
||||
r := rp.IsReachable(p.ID())
|
||||
if r == peernode.Unknown {
|
||||
// Reachability is unknown, because the node is a new router, so fall back.
|
||||
return p.Online().Get()
|
||||
}
|
||||
return r.IsReachable()
|
||||
}
|
||||
|
||||
func nodeIP(n tailcfg.NodeView, pred func(netip.Addr) bool) netip.Addr {
|
||||
|
||||
Reference in New Issue
Block a user