From 2fbd30824b5a51c45fd1ba4f40233e27a9c74365 Mon Sep 17 00:00:00 2001 From: Simon Law Date: Thu, 25 Jun 2026 18:22:15 -0700 Subject: [PATCH] tailcfg,net/routecheck: add NodeAttrClientSideReachabilityRouteCheck (#20169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a new `client-side-reachability-routecheck` node attribute to allow admins to selectively enable background routecheck probing on trial nodes. The current implementation is still experimental. It adds the routecheck.IsEnabled helper to check for the new `client-side-reachability-routecheck` node attribute alongside the existing `client-side-reachability` node attribute in this node’s self capabilities. This allows administrators to turn on and off this feature by editing the policy file. It adds the `TS_DEBUG_FORCE_CLIENT_SIDE_REACHABILITY_ROUTECHECK` environment variable which can be set to override the policy file. When set to `true`, it forcibly enables this feature. And when set to `false`, it forcibly disables it. Updates #17366 Updates tailscale/corp#33033 Signed-off-by: Simon Law --- feature/routecheck/routecheck.go | 4 ++++ net/routecheck/routecheck.go | 21 +++++++++++++++++++++ tailcfg/tailcfg.go | 12 +++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/feature/routecheck/routecheck.go b/feature/routecheck/routecheck.go index 888e9bbfa..c22332f81 100644 --- a/feature/routecheck/routecheck.go +++ b/feature/routecheck/routecheck.go @@ -53,6 +53,10 @@ func (e *Extension) Name() string { // Init implements the [ipnext.Extension.Init] interface method. func (e *Extension) Init(h ipnext.Host) error { + if routecheck.DebugForceClientSideReachabilityRoutecheck().EqualBool(false) { + return ipnext.SkipExtension + } + e.nb = nodeBackender{h} nm, ok := e.backend.(routecheck.NetMapper) diff --git a/net/routecheck/routecheck.go b/net/routecheck/routecheck.go index fb57b5fa2..5c562c6c4 100644 --- a/net/routecheck/routecheck.go +++ b/net/routecheck/routecheck.go @@ -12,6 +12,7 @@ import ( "sync/atomic" "time" + "tailscale.com/envknob" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/logger" @@ -23,6 +24,26 @@ var ( metricRefresh = clientmetric.NewCounter("routecheck_refresh") ) +// DebugForceClientSideReachabilityRoutecheck reports whether routecheck should be forced on or off. +// If the TS_DEBUG_FORCE_CLIENT_SIDE_REACHABILITY_ROUTECHECK environment variable is true, +// then routecheck is forced on. If it is false, then routecheck is forced off. +// If unset, then the client respects the client-side-reachability and +// client-side-reachability-routecheck node attributes. +var DebugForceClientSideReachabilityRoutecheck = envknob.RegisterOptBool("TS_DEBUG_FORCE_CLIENT_SIDE_REACHABILITY_ROUTECHECK") + +// IsEnabled reports whether routecheck probing has been enabled for this client. +func IsEnabled(self tailcfg.NodeView) bool { + if v, ok := DebugForceClientSideReachabilityRoutecheck().Get(); ok { + return v // forced + } + if !self.Valid() { + return false + } + // TODO(sfllaw): We intend to eventually enable this behaviour by default. + return self.HasCap(tailcfg.NodeAttrClientSideReachability) && + self.HasCap(tailcfg.NodeAttrClientSideReachabilityRouteCheck) +} + // Client generates Reports describing the result of both passive and active // reachability probing. type Client struct { diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 66cf23fee..55df5938f 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2756,7 +2756,17 @@ const ( // reachability itself when choosing connectors. When absent, the // default behavior is to trust the control plane when it claims that a // node is no longer online, but that is not a reliable signal. - NodeAttrClientSideReachability = "client-side-reachability" + // + // It is temporary and will be ignored once its behaviour becomes the default. + NodeAttrClientSideReachability NodeCapability = "client-side-reachability" + + // NodeAttrClientSideReachabilityRouteCheck configures the node to use + // the routecheck subsystem to determine reachability when choosing + // connectors. This relies on [NodeAttrClientSideReachability] being set. + // See tailscale/tailscale#17367. + // + // It is temporary and will be ignored once its behaviour becomes the default. + NodeAttrClientSideReachabilityRouteCheck NodeCapability = "client-side-reachability-routecheck" // NodeAttrDefaultAutoUpdate advertises the default node auto-update setting // for this tailnet. The node is free to opt-in or out locally regardless of