WIP: rebase fork onto upstream/main (v1.103.0) #15
@@ -108,6 +108,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/net/netutil from tailscale.com/client/local
|
||||
tailscale.com/net/netx from tailscale.com/net/dnscache+
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/net/routecheck
|
||||
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/stun from tailscale.com/net/stunserver
|
||||
tailscale.com/net/stunserver from tailscale.com/cmd/derper
|
||||
|
||||
@@ -807,7 +807,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/net/portmapper from tailscale.com/feature/portmapper
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
@@ -236,6 +236,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/portmapper from tailscale.com/feature/portmapper
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/net/routecheck
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck
|
||||
tailscale.com/net/tlsdial from tailscale.com/cmd/tailscale/cli+
|
||||
|
||||
@@ -107,6 +107,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
||||
|
||||
@@ -124,6 +124,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
||||
|
||||
@@ -380,6 +380,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/feature/portmapper+
|
||||
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/net/routecheck from tailscale.com/feature/routecheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routetable from tailscale.com/doctor/routetable
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock+
|
||||
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
|
||||
|
||||
@@ -208,7 +208,7 @@ func TestOmitRouteCheck(t *testing.T) {
|
||||
GOARCH: "amd64",
|
||||
Tags: "ts_omit_routecheck,ts_include_cli",
|
||||
OnDep: func(dep string) {
|
||||
if strings.Contains(dep, "routecheck") {
|
||||
if strings.Contains(dep, "routecheck") && !strings.HasSuffix(dep, "/peernode") {
|
||||
t.Errorf("unexpected dep: %q", dep)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -206,7 +206,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
|
||||
tailscale.com/net/portmapper from tailscale.com/feature/portmapper
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"tailscale.com/ipn/ipnext"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/net/routecheck"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -36,6 +37,8 @@ func init() {
|
||||
backend: b,
|
||||
}, nil
|
||||
})
|
||||
|
||||
ipnlocal.HookRouteCheckReport.Set(routeCheckReport)
|
||||
}
|
||||
|
||||
// Extension implements the [ipnext.Extension] interface.
|
||||
@@ -162,3 +165,15 @@ func (e *Extension) reconcileLoop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func routeCheckReport(b *ipnlocal.LocalBackend) ipnlocal.RouteCheckReport {
|
||||
c := ClientFor(b)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
r := c.Report()
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
+21
-14
@@ -2530,6 +2530,9 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sfllaw): If [buildfeatures.HasRouteCheck] is enabled,
|
||||
// a probe may be triggered for an updated routecheck reachability report,
|
||||
// which should influence which exit nodes are considered valid to suggest.
|
||||
if cn.NetMap() != nil && mutationsAreWorthyOfRecalculatingSuggestedExitNode(muts, cn, b.lastSuggestedExitNode) {
|
||||
// Recompute the suggested exit node
|
||||
b.suggestExitNodeLocked()
|
||||
@@ -8604,6 +8607,9 @@ func allowedAutoRoute(ipp netip.Prefix) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// HookRouteCheckReport is the hook that returns the latest routecheck Report for this LocalBackend.
|
||||
var HookRouteCheckReport feature.Hook[func(*LocalBackend) RouteCheckReport]
|
||||
|
||||
var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later")
|
||||
|
||||
// suggestExitNodeLocked computes a suggestion based on the current netmap and
|
||||
@@ -8611,11 +8617,15 @@ var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later")
|
||||
// be selected at random, so the result is not stable. To be eligible for
|
||||
// consideration, the peer must have [tailcfg.NodeAttrSuggestExitNode] in its CapMap.
|
||||
//
|
||||
// When the client has [tailcfg.NodeAttrClientSideReachabilityRouteCheck] in its CapMap,
|
||||
// it will use [HookRouteCheckReport] to get the latest cached routecheck report.
|
||||
//
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggestionResponse, err error) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
return response, feature.ErrUnavailable
|
||||
}
|
||||
|
||||
mc := b.MagicConn()
|
||||
var preferredDERP int
|
||||
if lastReport := mc.GetLastNetcheckReport(b.ctx); lastReport != nil {
|
||||
@@ -8628,7 +8638,12 @@ func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggest
|
||||
regionLatency := mc.GetDERPRegionLatency()
|
||||
prevSuggestion := b.lastSuggestedExitNode
|
||||
|
||||
res, err := suggestExitNode(preferredDERP, regionLatency, b.currentNode(), prevSuggestion, randomRegion, randomNode, b.getAllowedSuggestions())
|
||||
var rp RouteCheckReport
|
||||
if hook := HookRouteCheckReport.GetOrNil(); hook != nil {
|
||||
rp = hook(b)
|
||||
}
|
||||
|
||||
res, err := suggestExitNode(preferredDERP, regionLatency, rp, b.currentNode(), prevSuggestion, randomRegion, randomNode, b.getAllowedSuggestions())
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -8710,11 +8725,11 @@ func fillAllowedSuggestions(polc policyclient.Client) (set.Set[tailcfg.StableNod
|
||||
// netcheck and are only used by the DERP-based algorithm.
|
||||
//
|
||||
// Errors are always logged. Suggestions are logged if they defer from prevSuggestion.
|
||||
func suggestExitNode(preferredDERP int, regionLatency map[int]time.Duration, nb *nodeBackend, prevSuggestion tailcfg.StableNodeID, selectRegion selectRegionFunc, selectNode selectNodeFunc, allowList set.Set[tailcfg.StableNodeID]) (res apitype.ExitNodeSuggestionResponse, err error) {
|
||||
func suggestExitNode(preferredDERP int, regionLatency map[int]time.Duration, rp RouteCheckReport, nb *nodeBackend, prevSuggestion tailcfg.StableNodeID, selectRegion selectRegionFunc, selectNode selectNodeFunc, allowList set.Set[tailcfg.StableNodeID]) (res apitype.ExitNodeSuggestionResponse, err error) {
|
||||
switch {
|
||||
case nb.SelfHasCap(tailcfg.NodeAttrTrafficSteering):
|
||||
// The traffic-steering feature flag is enabled on this tailnet.
|
||||
res, err = suggestExitNodeUsingTrafficSteering(nb, allowList)
|
||||
res, err = suggestExitNodeUsingTrafficSteering(rp, nb, allowList)
|
||||
default:
|
||||
// The control plane will always strip the `traffic-steering`
|
||||
// node attribute if it isn’t enabled for this tailnet, even if
|
||||
@@ -8747,10 +8762,6 @@ func suggestExitNode(preferredDERP int, regionLatency map[int]time.Duration, nb
|
||||
// the lowest latency to this device. For peers without a DERP home, we look for
|
||||
// geographic proximity to this device's DERP home.
|
||||
func suggestExitNodeUsingDERP(preferredRegionID int, regionLatency map[int]time.Duration, nb *nodeBackend, prevSuggestion tailcfg.StableNodeID, selectRegion selectRegionFunc, selectNode selectNodeFunc, allowList set.Set[tailcfg.StableNodeID]) (res apitype.ExitNodeSuggestionResponse, err error) {
|
||||
// TODO(sfllaw): Context needs to be plumbed down here to support
|
||||
// reachability testing.
|
||||
ctx := context.TODO()
|
||||
|
||||
netMap := nb.NetMap()
|
||||
if preferredRegionID == 0 || netMap == nil || netMap.DERPMap == nil {
|
||||
return res, ErrNoPreferredDERP
|
||||
@@ -8759,7 +8770,7 @@ func suggestExitNodeUsingDERP(preferredRegionID int, regionLatency map[int]time.
|
||||
// since the netmap doesn't include delta updates (e.g., home DERP or Online
|
||||
// status changes) from the control plane since the last full update.
|
||||
candidates := nb.AppendMatchingPeers(nil, func(peer tailcfg.NodeView) bool {
|
||||
if !peer.Valid() || !nb.PeerIsReachable(ctx, peer) {
|
||||
if !peer.Valid() || !nb.PeerIsReachable(nil, peer) {
|
||||
return false
|
||||
}
|
||||
if allowList != nil && !allowList.Contains(peer.StableID()) {
|
||||
@@ -8887,11 +8898,7 @@ var ErrNoNetMap = errors.New("no network map, try again later")
|
||||
// pick one of the best exit nodes. These priorities are provided by Control in
|
||||
// the node’s [tailcfg.Location]. To be eligible for consideration, the node
|
||||
// must have NodeAttrSuggestExitNode in its CapMap.
|
||||
func suggestExitNodeUsingTrafficSteering(nb *nodeBackend, allowed set.Set[tailcfg.StableNodeID]) (apitype.ExitNodeSuggestionResponse, error) {
|
||||
// TODO(sfllaw): Context needs to be plumbed down here to support
|
||||
// reachability testing.
|
||||
ctx := context.TODO()
|
||||
|
||||
func suggestExitNodeUsingTrafficSteering(rp RouteCheckReport, nb *nodeBackend, allowed set.Set[tailcfg.StableNodeID]) (apitype.ExitNodeSuggestionResponse, error) {
|
||||
nm := nb.NetMap()
|
||||
if nm == nil {
|
||||
return apitype.ExitNodeSuggestionResponse{}, ErrNoNetMap
|
||||
@@ -8910,7 +8917,7 @@ func suggestExitNodeUsingTrafficSteering(nb *nodeBackend, allowed set.Set[tailcf
|
||||
if !p.Valid() {
|
||||
return false
|
||||
}
|
||||
if !nb.PeerIsReachable(ctx, p) {
|
||||
if !nb.PeerIsReachable(rp, p) {
|
||||
return false
|
||||
}
|
||||
if allowed != nil && !allowed.Contains(p.StableID()) {
|
||||
|
||||
@@ -6062,7 +6062,7 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
regionLatency = tt.lastReport.RegionLatency
|
||||
}
|
||||
|
||||
got, err := suggestExitNode(preferredDERP, regionLatency, nb, tt.lastSuggestion, selectRegion, selectNode, allowList)
|
||||
got, err := suggestExitNode(preferredDERP, regionLatency, nil, nb, tt.lastSuggestion, selectRegion, selectNode, allowList)
|
||||
if got.Name != tt.wantName {
|
||||
t.Errorf("name=%v, want %v", got.Name, tt.wantName)
|
||||
}
|
||||
@@ -6620,7 +6620,7 @@ func TestSuggestExitNodeTrafficSteering(t *testing.T) {
|
||||
defer nb.shutdown(errShutdown)
|
||||
nb.SetNetMap(tt.netMap)
|
||||
|
||||
got, err := suggestExitNodeUsingTrafficSteering(nb, allowList)
|
||||
got, err := suggestExitNodeUsingTrafficSteering(nil, nb, allowList)
|
||||
if tt.wantErr == nil && err != nil {
|
||||
t.Fatalf("err=%v, want nil", err)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ts_omit_routecheck
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import "tailscale.com/tailcfg"
|
||||
|
||||
func isRouteCheckEnabled(self tailcfg.NodeView) bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_routecheck
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"tailscale.com/net/routecheck"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func isRouteCheckEnabled(self tailcfg.NodeView) bool {
|
||||
return routecheck.IsEnabled(self)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 prefix’s routers that were reachable by the current host,
|
||||
// at the time the report was finished.
|
||||
|
||||
+2
-1
@@ -202,7 +202,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
|
||||
tailscale.com/net/portmapper from tailscale.com/feature/portmapper
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
Reference in New Issue
Block a user