net/netcheck, tailcfg: add DERPHomeParams and use it
This allows providing additional information to the client about how to select a home DERP region, such as preferring a given DERP region over all others. Updates #8603 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I7c4a270f31d8585112fab5408799ffba5b75266f
This commit is contained in:
+32
-11
@@ -41,6 +41,7 @@ import (
|
||||
"tailscale.com/types/nettype"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/cmpx"
|
||||
"tailscale.com/util/mak"
|
||||
@@ -1110,7 +1111,7 @@ func (c *Client) finishAndStoreReport(rs *reportState, dm *tailcfg.DERPMap) *Rep
|
||||
report := rs.report.Clone()
|
||||
rs.mu.Unlock()
|
||||
|
||||
c.addReportHistoryAndSetPreferredDERP(report)
|
||||
c.addReportHistoryAndSetPreferredDERP(report, dm.View())
|
||||
c.logConciseReport(report, dm)
|
||||
|
||||
return report
|
||||
@@ -1444,7 +1445,7 @@ func (c *Client) timeNow() time.Time {
|
||||
|
||||
// addReportHistoryAndSetPreferredDERP adds r to the set of recent Reports
|
||||
// and mutates r.PreferredDERP to contain the best recent one.
|
||||
func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
|
||||
func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report, dm tailcfg.DERPMapView) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -1476,11 +1477,33 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
|
||||
}
|
||||
}
|
||||
|
||||
// Scale each region's best latency by any provided scores from the
|
||||
// DERPMap, for use in comparison below.
|
||||
var scores views.Map[int, float64]
|
||||
if hp := dm.HomeParams(); hp.Valid() {
|
||||
scores = hp.RegionScore()
|
||||
}
|
||||
for regionID, d := range bestRecent {
|
||||
if score := scores.Get(regionID); score > 0 {
|
||||
bestRecent[regionID] = time.Duration(float64(d) * score)
|
||||
}
|
||||
}
|
||||
|
||||
// Then, pick which currently-alive DERP server from the
|
||||
// current report has the best latency over the past maxAge.
|
||||
var bestAny time.Duration
|
||||
var oldRegionCurLatency time.Duration
|
||||
var (
|
||||
bestAny time.Duration // global minimum
|
||||
oldRegionCurLatency time.Duration // latency of old PreferredDERP
|
||||
)
|
||||
for regionID, d := range r.RegionLatency {
|
||||
// Scale this report's latency by any scores provided by the
|
||||
// server; we did this for the bestRecent map above, but we
|
||||
// don't mutate the actual reports in-place (in case scores
|
||||
// change), so we need to do it here as well.
|
||||
if score := scores.Get(regionID); score > 0 {
|
||||
d = time.Duration(float64(d) * score)
|
||||
}
|
||||
|
||||
if regionID == prevDERP {
|
||||
oldRegionCurLatency = d
|
||||
}
|
||||
@@ -1491,13 +1514,11 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) {
|
||||
}
|
||||
}
|
||||
|
||||
// If we're changing our preferred DERP but the old one's still
|
||||
// accessible and the new one's not much better, just stick with
|
||||
// where we are.
|
||||
if prevDERP != 0 &&
|
||||
r.PreferredDERP != prevDERP &&
|
||||
oldRegionCurLatency != 0 &&
|
||||
bestAny > oldRegionCurLatency/3*2 {
|
||||
// If we're changing our preferred DERP, the old one's still
|
||||
// accessible, and the new one's not much better, just stick
|
||||
// with where we are.
|
||||
changingPreferred := prevDERP != 0 && r.PreferredDERP != prevDERP
|
||||
if changingPreferred && oldRegionCurLatency != 0 && bestAny > oldRegionCurLatency/3*2 {
|
||||
r.PreferredDERP = prevDERP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,7 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
steps []step
|
||||
homeParams *tailcfg.DERPHomeParams
|
||||
wantDERP int // want PreferredDERP on final step
|
||||
wantPrevLen int // wanted len(c.prev)
|
||||
}{
|
||||
@@ -335,6 +336,52 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
|
||||
wantPrevLen: 2,
|
||||
wantDERP: 2, // 2 got fast enough
|
||||
},
|
||||
{
|
||||
name: "derp_home_params",
|
||||
homeParams: &tailcfg.DERPHomeParams{
|
||||
RegionScore: map[int]float64{
|
||||
1: 2.0 / 3, // 66%
|
||||
},
|
||||
},
|
||||
steps: []step{
|
||||
// We only use a single step here to avoid
|
||||
// conflating DERP selection as a result of
|
||||
// weight hints with the "stickiness" check
|
||||
// that tries to not change the home DERP
|
||||
// between steps.
|
||||
{1 * time.Second, report("d1", 10, "d2", 8)},
|
||||
},
|
||||
wantPrevLen: 1,
|
||||
wantDERP: 1, // 2 was faster, but not by 50%+
|
||||
},
|
||||
{
|
||||
name: "derp_home_params_high_latency",
|
||||
homeParams: &tailcfg.DERPHomeParams{
|
||||
RegionScore: map[int]float64{
|
||||
1: 2.0 / 3, // 66%
|
||||
},
|
||||
},
|
||||
steps: []step{
|
||||
// See derp_home_params for why this is a single step.
|
||||
{1 * time.Second, report("d1", 100, "d2", 10)},
|
||||
},
|
||||
wantPrevLen: 1,
|
||||
wantDERP: 2, // 2 was faster by more than 50%
|
||||
},
|
||||
{
|
||||
name: "derp_home_params_invalid",
|
||||
homeParams: &tailcfg.DERPHomeParams{
|
||||
RegionScore: map[int]float64{
|
||||
1: 0.0,
|
||||
2: -1.0,
|
||||
},
|
||||
},
|
||||
steps: []step{
|
||||
{1 * time.Second, report("d1", 4, "d2", 5)},
|
||||
},
|
||||
wantPrevLen: 1,
|
||||
wantDERP: 1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -342,9 +389,10 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
|
||||
c := &Client{
|
||||
TimeNow: func() time.Time { return fakeTime },
|
||||
}
|
||||
dm := &tailcfg.DERPMap{HomeParams: tt.homeParams}
|
||||
for _, s := range tt.steps {
|
||||
fakeTime = fakeTime.Add(s.after)
|
||||
c.addReportHistoryAndSetPreferredDERP(s.r)
|
||||
c.addReportHistoryAndSetPreferredDERP(s.r, dm.View())
|
||||
}
|
||||
lastReport := tt.steps[len(tt.steps)-1].r
|
||||
if got, want := len(c.prev), tt.wantPrevLen; got != want {
|
||||
|
||||
Reference in New Issue
Block a user