mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-10 09:48:35 +00:00
ipn/ipnlocal: handle auto value for ExitNodeID syspolicy
Updates tailscale/corp#19681 Signed-off-by: Claire Wang <claire@tailscale.com>
This commit is contained in:
parent
9a64c06a20
commit
d541079e78
@ -296,7 +296,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/dnsfallback from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock+
|
||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
|
||||
tailscale.com/net/netkernelconf from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/netknob from tailscale.com/logpolicy+
|
||||
|
@ -59,7 +59,6 @@ import (
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/netcheck"
|
||||
"tailscale.com/net/netkernelconf"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
@ -333,6 +332,9 @@ type LocalBackend struct {
|
||||
// lastSuggestedExitNode stores the last suggested exit node ID and name.
|
||||
// lastSuggestedExitNode updates whenever the suggestion changes.
|
||||
lastSuggestedExitNode lastSuggestedExitNode
|
||||
// refreshReportForExitNodeSuggestion indicates whether to update the netcheck report for an exit node suggestion.
|
||||
// Value is set to true when a rebind happens.
|
||||
refreshReportForExitNodeSuggestion bool
|
||||
}
|
||||
|
||||
// HealthTracker returns the health tracker for the backend.
|
||||
@ -1167,7 +1169,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
prefs.WantRunning = true
|
||||
prefs.LoggedOut = false
|
||||
}
|
||||
if setExitNodeID(prefs, st.NetMap) {
|
||||
if b.setExitNodeIDLocked(prefs, st.NetMap) {
|
||||
prefsChanged = true
|
||||
}
|
||||
if applySysPolicy(prefs) {
|
||||
@ -1379,6 +1381,15 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
nm.Peers = make([]tailcfg.NodeView, 0, len(b.peers))
|
||||
for _, p := range b.peers {
|
||||
nm.Peers = append(nm.Peers, p)
|
||||
if !*p.Online() && p.StableID() == b.pm.CurrentPrefs().ExitNodeID() {
|
||||
if exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, ""); exitNodeIDStr == "auto" {
|
||||
prefs := b.pm.CurrentPrefs().AsStruct()
|
||||
b.setExitNodeIDLocked(prefs, nm)
|
||||
if err := b.pm.SetPrefs(prefs.View(), ipn.NetworkProfile{}); err != nil {
|
||||
b.logf("UpdateNetmapDelta: unable to set auto exit node; err=%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.SortFunc(nm.Peers, func(a, b tailcfg.NodeView) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
@ -1438,13 +1449,29 @@ func (b *LocalBackend) updateNetmapDeltaLocked(muts []netmap.NodeMutation) (hand
|
||||
return true
|
||||
}
|
||||
|
||||
// setExitNodeID updates prefs to reference an exit node by ID, rather
|
||||
// setExitNodeIDLocked updates prefs to reference an exit node by ID, rather
|
||||
// than by IP. It returns whether prefs was mutated.
|
||||
func setExitNodeID(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) {
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) setExitNodeIDLocked(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) {
|
||||
if exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, ""); exitNodeIDStr != "" {
|
||||
exitNodeID := tailcfg.StableNodeID(exitNodeIDStr)
|
||||
changed := prefs.ExitNodeID != exitNodeID || prefs.ExitNodeIP.IsValid()
|
||||
prefs.ExitNodeID = exitNodeID
|
||||
var changed bool
|
||||
if exitNodeIDStr == "auto" {
|
||||
if b.lastSuggestedExitNode.id != "" {
|
||||
changed = prefs.ExitNodeID != b.lastSuggestedExitNode.id || prefs.ExitNodeIP.IsValid()
|
||||
prefs.ExitNodeID = b.lastSuggestedExitNode.id
|
||||
} else {
|
||||
res, err := b.suggestExitNodeLocked(true, nil)
|
||||
if err != nil {
|
||||
b.logf("setExitNodeIDLocked: Unable to suggest exit node. Error=%v", err)
|
||||
}
|
||||
changed = prefs.ExitNodeID != res.ID || prefs.ExitNodeIP.IsValid()
|
||||
prefs.ExitNodeID = res.ID
|
||||
}
|
||||
} else {
|
||||
exitNodeID := tailcfg.StableNodeID(exitNodeIDStr)
|
||||
changed = prefs.ExitNodeID != exitNodeID || prefs.ExitNodeIP.IsValid()
|
||||
prefs.ExitNodeID = exitNodeID
|
||||
}
|
||||
prefs.ExitNodeIP = netip.Addr{}
|
||||
return changed
|
||||
}
|
||||
@ -3278,10 +3305,11 @@ func (b *LocalBackend) setPrefsLockedOnEntry(newp *ipn.Prefs, unlock unlockOnce)
|
||||
if oldp.Valid() {
|
||||
newp.Persist = oldp.Persist().AsStruct() // caller isn't allowed to override this
|
||||
}
|
||||
// setExitNodeID returns whether it updated b.prefs, but
|
||||
// setExitNodeIDLocked returns whether it updated b.prefs, but
|
||||
// everything in this function treats b.prefs as completely new
|
||||
// anyway. No-op if no exit node resolution is needed.
|
||||
setExitNodeID(newp, netMap)
|
||||
b.setExitNodeIDLocked(newp, netMap)
|
||||
//}
|
||||
// applySysPolicy does likewise so we can also ignore its return value.
|
||||
applySysPolicy(newp)
|
||||
// We do this to avoid holding the lock while doing everything else.
|
||||
@ -4760,12 +4788,19 @@ func (b *LocalBackend) Logout(ctx context.Context) error {
|
||||
func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) {
|
||||
b.mu.Lock()
|
||||
cc := b.cc
|
||||
refresh := b.refreshReportForExitNodeSuggestion
|
||||
b.refreshReportForExitNodeSuggestion = false
|
||||
b.mu.Unlock()
|
||||
|
||||
if cc == nil {
|
||||
return
|
||||
}
|
||||
cc.SetNetInfo(ni)
|
||||
if refresh {
|
||||
if exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, ""); exitNodeIDStr == "auto" {
|
||||
b.suggestExitNodeLocked(true, ni)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setNetMapLocked updates the LocalBackend state to reflect the newly
|
||||
@ -6400,20 +6435,68 @@ var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later")
|
||||
var ErrCannotSuggestExitNode = errors.New("unable to suggest an exit node, try again later")
|
||||
var ErrUnableToSuggestLastExitNode = errors.New("unable to suggest last exit node")
|
||||
|
||||
// SuggestExitNode computes a suggestion based on the current netmap and last netcheck report. If
|
||||
// latencyInfo is used to extra needed information from a netcheck report or NetInfo to make an exit node suggestion.
|
||||
type latencyInfo struct {
|
||||
preferredDERP int
|
||||
regionLatency map[int]time.Duration
|
||||
}
|
||||
|
||||
// processNetInfo extracts the needed values from tailcfg.NetInfo to make an exit node suggestion.
|
||||
func processNetInfo(netInfo *tailcfg.NetInfo) (info latencyInfo, err error) {
|
||||
if netInfo == nil {
|
||||
return info, ErrCannotSuggestExitNode
|
||||
}
|
||||
if netInfo.PreferredDERP == 0 {
|
||||
return info, ErrNoPreferredDERP
|
||||
}
|
||||
info.preferredDERP = netInfo.PreferredDERP
|
||||
info.regionLatency = make(map[int]time.Duration)
|
||||
for region, duration := range netInfo.DERPLatency {
|
||||
parsed := strings.Split(region, "-")
|
||||
r, _ := strconv.Atoi(parsed[0])
|
||||
val, ok := info.regionLatency[r]
|
||||
converted := time.Duration(duration * float64(time.Second))
|
||||
if !ok {
|
||||
info.regionLatency[r] = converted
|
||||
} else {
|
||||
if converted < val {
|
||||
info.regionLatency[r] = converted
|
||||
}
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// suggestExitNodeIDLocked computes a suggestion based on the current netmap and last netcheck report. If
|
||||
// there are multiple equally good options, one is selected at random, so the result is not stable. To be
|
||||
// eligible for consideration, the peer must have NodeAttrSuggestExitNode in its CapMap.
|
||||
//
|
||||
// Currently, peers with a DERP home are preferred over those without (typically this means Mullvad).
|
||||
// Peers are selected based on having a DERP home that is the lowest latency to this device. For peers
|
||||
// without a DERP home, we look for geographic proximity to this device's DERP home.
|
||||
func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionResponse, err error) {
|
||||
b.mu.Lock()
|
||||
lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx)
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) suggestExitNodeLocked(isAuto bool, netInfo *tailcfg.NetInfo) (response apitype.ExitNodeSuggestionResponse, err error) {
|
||||
var latency latencyInfo
|
||||
if netInfo != nil {
|
||||
latency, err = processNetInfo(netInfo)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
} else {
|
||||
lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx)
|
||||
if lastReport == nil {
|
||||
last, err := b.lastSuggestedExitNode.asAPIType()
|
||||
if err != nil {
|
||||
return response, ErrCannotSuggestExitNode
|
||||
}
|
||||
return last, err
|
||||
}
|
||||
latency.preferredDERP = lastReport.PreferredDERP
|
||||
latency.regionLatency = lastReport.RegionLatency
|
||||
}
|
||||
netMap := b.netMap
|
||||
lastSuggestedExitNode := b.lastSuggestedExitNode
|
||||
b.mu.Unlock()
|
||||
if lastReport == nil || netMap == nil {
|
||||
if netMap == nil {
|
||||
last, err := lastSuggestedExitNode.asAPIType()
|
||||
if err != nil {
|
||||
return response, ErrCannotSuggestExitNode
|
||||
@ -6422,7 +6505,7 @@ func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionRes
|
||||
}
|
||||
seed := time.Now().UnixNano()
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
res, err := suggestExitNode(lastReport, netMap, r)
|
||||
res, err := suggestExitNode(&latency, netMap, r, isAuto, b.pm.prefs)
|
||||
if err != nil {
|
||||
last, err := lastSuggestedExitNode.asAPIType()
|
||||
if err != nil {
|
||||
@ -6430,9 +6513,14 @@ func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionRes
|
||||
}
|
||||
return last, err
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.lastSuggestedExitNode.id = res.ID
|
||||
b.lastSuggestedExitNode.name = res.Name
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (b *LocalBackend) HandleSuggestExitNode() (response apitype.ExitNodeSuggestionResponse, err error) {
|
||||
b.mu.Lock()
|
||||
res, err := b.suggestExitNodeLocked(false, nil)
|
||||
b.mu.Unlock()
|
||||
return res, err
|
||||
}
|
||||
@ -6449,13 +6537,29 @@ func (n lastSuggestedExitNode) asAPIType() (res apitype.ExitNodeSuggestionRespon
|
||||
return res, ErrUnableToSuggestLastExitNode
|
||||
}
|
||||
|
||||
func suggestExitNode(report *netcheck.Report, netMap *netmap.NetworkMap, r *rand.Rand) (res apitype.ExitNodeSuggestionResponse, err error) {
|
||||
if report.PreferredDERP == 0 {
|
||||
func suggestExitNode(latencyInfo *latencyInfo, netMap *netmap.NetworkMap, r *rand.Rand, isAuto bool, prefs ipn.PrefsView) (res apitype.ExitNodeSuggestionResponse, err error) {
|
||||
if latencyInfo.preferredDERP == 0 {
|
||||
return res, ErrNoPreferredDERP
|
||||
}
|
||||
candidates := make([]tailcfg.NodeView, 0, len(netMap.Peers))
|
||||
for _, peer := range netMap.Peers {
|
||||
if peer.CapMap().Has(tailcfg.NodeAttrSuggestExitNode) && tsaddr.ContainsExitRoutes(peer.AllowedIPs()) {
|
||||
var isCandidate bool
|
||||
if isAuto {
|
||||
isCandidate = peer.CapMap().Has(tailcfg.NodeAttrSuggestExitNode) && peer.CapMap().Has(tailcfg.NodeAttrAutoExitNode)
|
||||
if peer.StableID() == prefs.ExitNodeID() && isCandidate {
|
||||
if hi := peer.Hostinfo(); hi.Valid() {
|
||||
if loc := hi.Location(); loc != nil {
|
||||
res.Location = loc.View()
|
||||
}
|
||||
}
|
||||
res.ID = peer.StableID()
|
||||
res.Name = peer.Name()
|
||||
return res, nil
|
||||
}
|
||||
} else {
|
||||
isCandidate = peer.CapMap().Has(tailcfg.NodeAttrSuggestExitNode)
|
||||
}
|
||||
if isCandidate && tsaddr.ContainsExitRoutes(peer.AllowedIPs()) {
|
||||
candidates = append(candidates, peer)
|
||||
}
|
||||
}
|
||||
@ -6475,7 +6579,7 @@ func suggestExitNode(report *netcheck.Report, netMap *netmap.NetworkMap, r *rand
|
||||
}
|
||||
|
||||
candidatesByRegion := make(map[int][]tailcfg.NodeView, len(netMap.DERPMap.Regions))
|
||||
var preferredDERP *tailcfg.DERPRegion = netMap.DERPMap.Regions[report.PreferredDERP]
|
||||
var preferredDERP *tailcfg.DERPRegion = netMap.DERPMap.Regions[latencyInfo.preferredDERP]
|
||||
var minDistance float64 = math.MaxFloat64
|
||||
type nodeDistance struct {
|
||||
nv tailcfg.NodeView
|
||||
@ -6522,7 +6626,7 @@ func suggestExitNode(report *netcheck.Report, netMap *netmap.NetworkMap, r *rand
|
||||
// First, try to select an exit node that has the closest DERP home, based on lastReport's DERP latency.
|
||||
// If there are no latency values, it returns an arbitrary region
|
||||
if len(candidatesByRegion) > 0 {
|
||||
minRegion := minLatencyDERPRegion(xmaps.Keys(candidatesByRegion), report)
|
||||
minRegion := minLatencyDERPRegion(xmaps.Keys(candidatesByRegion), latencyInfo.regionLatency)
|
||||
if minRegion == 0 {
|
||||
minRegion = randomRegion(xmaps.Keys(candidatesByRegion), r)
|
||||
}
|
||||
@ -6605,14 +6709,14 @@ func randomRegion(regions []int, r *rand.Rand) int {
|
||||
|
||||
// minLatencyDERPRegion returns the region with the lowest latency value given the last netcheck report.
|
||||
// If there are no latency values, it returns 0.
|
||||
func minLatencyDERPRegion(regions []int, report *netcheck.Report) int {
|
||||
func minLatencyDERPRegion(regions []int, regionLatency map[int]time.Duration) int {
|
||||
min := slices.MinFunc(regions, func(i, j int) int {
|
||||
const largeDuration time.Duration = math.MaxInt64
|
||||
iLatency, ok := report.RegionLatency[i]
|
||||
iLatency, ok := regionLatency[i]
|
||||
if !ok {
|
||||
iLatency = largeDuration
|
||||
}
|
||||
jLatency, ok := report.RegionLatency[j]
|
||||
jLatency, ok := regionLatency[j]
|
||||
if !ok {
|
||||
jLatency = largeDuration
|
||||
}
|
||||
@ -6621,7 +6725,7 @@ func minLatencyDERPRegion(regions []int, report *netcheck.Report) int {
|
||||
}
|
||||
return cmp.Compare(i, j)
|
||||
})
|
||||
latency, ok := report.RegionLatency[min]
|
||||
latency, ok := regionLatency[min]
|
||||
if !ok || latency == 0 {
|
||||
return 0
|
||||
} else {
|
||||
|
@ -1855,7 +1855,9 @@ func TestSetExitNodeIDPolicy(t *testing.T) {
|
||||
pm.prefs = test.prefs.View()
|
||||
b.netMap = test.nm
|
||||
b.pm = pm
|
||||
changed := setExitNodeID(b.pm.prefs.AsStruct(), test.nm)
|
||||
b.mu.Lock()
|
||||
changed := b.setExitNodeIDLocked(b.pm.prefs.AsStruct(), test.nm)
|
||||
b.mu.Unlock()
|
||||
b.SetPrefsForTest(pm.CurrentPrefs().AsStruct())
|
||||
|
||||
if got := b.pm.prefs.ExitNodeID(); got != tailcfg.StableNodeID(test.exitNodeIDWant) {
|
||||
@ -2710,7 +2712,7 @@ func (b *LocalBackend) SetPrefsForTest(newp *ipn.Prefs) {
|
||||
func TestSuggestExitNode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lastReport netcheck.Report
|
||||
latencyInfo latencyInfo
|
||||
netMap netmap.NetworkMap
|
||||
wantID tailcfg.StableNodeID
|
||||
wantName string
|
||||
@ -2719,13 +2721,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "2 exit nodes in same region",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 10 * time.Millisecond,
|
||||
2: 20 * time.Millisecond,
|
||||
3: 30 * time.Millisecond,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -2773,13 +2775,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "2 derp based exit nodes, different regions, no latency measurements",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -2827,13 +2829,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "2 derp based exit nodes, different regions, same latency",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 10,
|
||||
2: 10,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -2881,13 +2883,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "mullvad nodes, no derp based exit nodes",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -2955,13 +2957,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "mullvad nodes close to each other, different priorities",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -3029,13 +3031,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "mullvad nodes, no preferred derp region exit nodes",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -3110,13 +3112,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no mullvad nodes; no derp nodes",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -3136,8 +3138,8 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no preferred derp region",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: -1,
|
||||
3: 0,
|
||||
@ -3162,13 +3164,13 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "derp exit node and mullvad exit node both with no suggest exit node attribute",
|
||||
lastReport: netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
latencyInfo: latencyInfo{
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 0,
|
||||
2: 0,
|
||||
3: 0,
|
||||
},
|
||||
PreferredDERP: 1,
|
||||
preferredDERP: 1,
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
@ -3217,7 +3219,7 @@ func TestSuggestExitNode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := rand.New(rand.NewSource(100))
|
||||
got, err := suggestExitNode(&tt.lastReport, &tt.netMap, r)
|
||||
got, err := suggestExitNode(&tt.latencyInfo, &tt.netMap, r, false, ipn.PrefsView{})
|
||||
if got.Name != tt.wantName {
|
||||
t.Errorf("name=%v, want %v", got.Name, tt.wantName)
|
||||
}
|
||||
@ -3391,46 +3393,41 @@ func TestSuggestExitNodeLongLatDistance(t *testing.T) {
|
||||
|
||||
func TestMinLatencyDERPregion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
regions []int
|
||||
report *netcheck.Report
|
||||
wantRegion int
|
||||
name string
|
||||
regions []int
|
||||
regionLatency map[int]time.Duration
|
||||
wantRegion int
|
||||
}{
|
||||
{
|
||||
name: "regions, no latency values",
|
||||
regions: []int{1, 2, 3},
|
||||
wantRegion: 0,
|
||||
report: &netcheck.Report{},
|
||||
},
|
||||
{
|
||||
name: "regions, different latency values",
|
||||
regions: []int{1, 2, 3},
|
||||
wantRegion: 2,
|
||||
report: &netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
1: 10 * time.Millisecond,
|
||||
2: 5 * time.Millisecond,
|
||||
3: 30 * time.Millisecond,
|
||||
},
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 10 * time.Millisecond,
|
||||
2: 5 * time.Millisecond,
|
||||
3: 30 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regions, same values",
|
||||
regions: []int{1, 2, 3},
|
||||
wantRegion: 1,
|
||||
report: &netcheck.Report{
|
||||
RegionLatency: map[int]time.Duration{
|
||||
1: 10 * time.Millisecond,
|
||||
2: 10 * time.Millisecond,
|
||||
3: 10 * time.Millisecond,
|
||||
},
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: 10 * time.Millisecond,
|
||||
2: 10 * time.Millisecond,
|
||||
3: 10 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := minLatencyDERPRegion(tt.regions, tt.report)
|
||||
got := minLatencyDERPRegion(tt.regions, tt.regionLatency)
|
||||
if got != tt.wantRegion {
|
||||
t.Errorf("got region %v want region %v", got, tt.wantRegion)
|
||||
}
|
||||
@ -3468,11 +3465,60 @@ func TestLastSuggestedExitNodeAsAPIType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessNetInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
netinfo *tailcfg.NetInfo
|
||||
wantInfo latencyInfo
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
name: "valid net info",
|
||||
netinfo: &tailcfg.NetInfo{
|
||||
PreferredDERP: 1,
|
||||
DERPLatency: map[string]float64{
|
||||
"1-v4": 1.0,
|
||||
"1-v6": 2.0,
|
||||
},
|
||||
},
|
||||
wantInfo: latencyInfo{
|
||||
preferredDERP: 1,
|
||||
regionLatency: map[int]time.Duration{
|
||||
1: time.Duration(1 * float64(time.Second)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no preferred derp",
|
||||
netinfo: &tailcfg.NetInfo{
|
||||
PreferredDERP: 0,
|
||||
},
|
||||
wantError: ErrNoPreferredDERP,
|
||||
},
|
||||
{
|
||||
name: "nil netinfo",
|
||||
wantError: ErrCannotSuggestExitNode,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := processNetInfo(tt.netinfo)
|
||||
if !reflect.DeepEqual(got, tt.wantInfo) {
|
||||
t.Errorf("got %v want %v", got, tt.wantInfo)
|
||||
}
|
||||
if err != tt.wantError {
|
||||
t.Errorf("got error %v want error %v", err, tt.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalBackendSuggestExitNode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lastSuggestedExitNode lastSuggestedExitNode
|
||||
report *netcheck.Report
|
||||
netInfo *tailcfg.NetInfo
|
||||
netMap netmap.NetworkMap
|
||||
wantID tailcfg.StableNodeID
|
||||
wantName string
|
||||
@ -3766,6 +3812,78 @@ func TestLocalBackendSuggestExitNode(t *testing.T) {
|
||||
},
|
||||
wantErr: ErrCannotSuggestExitNode,
|
||||
},
|
||||
{
|
||||
name: "use netinfo instead of last report",
|
||||
netInfo: &tailcfg.NetInfo{
|
||||
PreferredDERP: 1,
|
||||
DERPLatency: map[string]float64{
|
||||
"1-v4": 10,
|
||||
"2-v6": 10,
|
||||
"3-v4": 5,
|
||||
},
|
||||
},
|
||||
netMap: netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Addresses: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.1.1/32"),
|
||||
netip.MustParsePrefix("fe70::1/128"),
|
||||
},
|
||||
}).View(),
|
||||
DERPMap: &tailcfg.DERPMap{
|
||||
Regions: map[int]*tailcfg.DERPRegion{
|
||||
1: {},
|
||||
2: {},
|
||||
3: {},
|
||||
},
|
||||
},
|
||||
Peers: []tailcfg.NodeView{
|
||||
(&tailcfg.Node{
|
||||
ID: 1,
|
||||
StableID: "test",
|
||||
Name: "test",
|
||||
DERP: "127.3.3.40:1",
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
||||
},
|
||||
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
||||
tailcfg.NodeAttrSuggestExitNode: {},
|
||||
}),
|
||||
}).View(),
|
||||
(&tailcfg.Node{
|
||||
ID: 2,
|
||||
StableID: "test",
|
||||
Name: "test",
|
||||
DERP: "127.3.3.40:2",
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
||||
},
|
||||
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
||||
tailcfg.NodeAttrSuggestExitNode: {},
|
||||
}),
|
||||
}).View(),
|
||||
(&tailcfg.Node{
|
||||
ID: 3,
|
||||
StableID: "foo",
|
||||
Name: "foo",
|
||||
DERP: "127.3.3.40:3",
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
||||
},
|
||||
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
||||
tailcfg.NodeAttrSuggestExitNode: {},
|
||||
}),
|
||||
}).View(),
|
||||
},
|
||||
},
|
||||
wantID: "foo",
|
||||
wantName: "foo",
|
||||
wantLastSuggestedExitNode: lastSuggestedExitNode{name: "foo", id: "foo"},
|
||||
},
|
||||
{
|
||||
name: "invalid netinfo returns error",
|
||||
netInfo: &tailcfg.NetInfo{},
|
||||
wantErr: ErrCannotSuggestExitNode,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@ -3773,7 +3891,9 @@ func TestLocalBackendSuggestExitNode(t *testing.T) {
|
||||
lb.lastSuggestedExitNode = tt.lastSuggestedExitNode
|
||||
lb.netMap = &tt.netMap
|
||||
lb.sys.MagicSock.Get().SetLastNetcheckReportForTest(context.Background(), tt.report)
|
||||
got, err := lb.SuggestExitNode()
|
||||
lb.mu.Lock()
|
||||
got, err := lb.suggestExitNodeLocked(false, tt.netInfo)
|
||||
lb.mu.Unlock()
|
||||
if got.ID != tt.wantID {
|
||||
t.Errorf("ID=%v, want=%v", got.ID, tt.wantID)
|
||||
}
|
||||
|
@ -2884,7 +2884,7 @@ func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
res, err := h.b.SuggestExitNode()
|
||||
res, err := h.b.HandleSuggestExitNode()
|
||||
if err != nil {
|
||||
writeErrorJSON(w, err)
|
||||
return
|
||||
|
Loading…
x
Reference in New Issue
Block a user