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:
Andrew Dunham
2023-07-12 14:45:46 -04:00
parent 7a82fd8dbe
commit 7aba0b0d78
8 changed files with 328 additions and 18 deletions
+32 -11
View File
@@ -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
}
}
+49 -1
View File
@@ -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 {