cmd/tailscale,net/netcheck: add debug feature to force preferred DERP

This provides an interface for a user to force a preferred DERP outcome
for all future netchecks that will take precedence unless the forced
region is unreachable.

The option does not persist and will be lost when the daemon restarts.

Updates tailscale/corp#18997
Updates tailscale/corp#24755

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2024-12-04 12:02:59 -08:00
committed by James Tucker
parent 74069774be
commit 7f9ebc0a83
7 changed files with 140 additions and 1 deletions

View File

@@ -236,6 +236,10 @@ type Client struct {
// If false, the default net.Resolver will be used, with no caching.
UseDNSCache bool
// if non-zero, force this DERP region to be preferred in all reports where
// the DERP is found to be reachable.
ForcePreferredDERP int
// For tests
testEnoughRegions int
testCaptivePortalDelay time.Duration
@@ -780,6 +784,12 @@ func (o *GetReportOpts) getLastDERPActivity(region int) time.Time {
return o.GetLastDERPActivity(region)
}
func (c *Client) SetForcePreferredDERP(region int) {
c.mu.Lock()
defer c.mu.Unlock()
c.ForcePreferredDERP = region
}
// GetReport gets a report. The 'opts' argument is optional and can be nil.
// Callers are discouraged from passing a ctx with an arbitrary deadline as this
// may cause GetReport to return prematurely before all reporting methods have
@@ -1277,6 +1287,9 @@ func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
if r.CaptivePortal != "" {
fmt.Fprintf(w, " captiveportal=%v", r.CaptivePortal)
}
if c.ForcePreferredDERP != 0 {
fmt.Fprintf(w, " force=%v", c.ForcePreferredDERP)
}
fmt.Fprintf(w, " derp=%v", r.PreferredDERP)
if r.PreferredDERP != 0 {
fmt.Fprintf(w, " derpdist=")
@@ -1435,6 +1448,21 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(rs *reportState, r *Report,
// which undoes any region change we made above.
r.PreferredDERP = prevDERP
}
if c.ForcePreferredDERP != 0 {
// If the forced DERP region probed successfully, or has recent traffic,
// use it.
_, haveLatencySample := r.RegionLatency[c.ForcePreferredDERP]
var recentActivity bool
if lastHeard := rs.opts.getLastDERPActivity(c.ForcePreferredDERP); !lastHeard.IsZero() {
now := c.timeNow()
recentActivity = lastHeard.After(rs.start)
recentActivity = recentActivity || lastHeard.After(now.Add(-PreferredDERPFrameTime))
}
if haveLatencySample || recentActivity {
r.PreferredDERP = c.ForcePreferredDERP
}
}
}
func updateLatency(m map[int]time.Duration, regionID int, d time.Duration) {

View File

@@ -201,6 +201,7 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
steps []step
homeParams *tailcfg.DERPHomeParams
opts *GetReportOpts
forcedDERP int // if non-zero, force this DERP to be the preferred one
wantDERP int // want PreferredDERP on final step
wantPrevLen int // wanted len(c.prev)
}{
@@ -366,12 +367,65 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) {
wantPrevLen: 2,
wantDERP: 1, // diff is 11ms, but d2 is greater than 2/3s of d1
},
{
name: "forced_two",
steps: []step{
{time.Second, report("d1", 2, "d2", 3)},
{2 * time.Second, report("d1", 4, "d2", 3)},
},
forcedDERP: 2,
wantPrevLen: 2,
wantDERP: 2,
},
{
name: "forced_two_unavailable",
steps: []step{
{time.Second, report("d1", 2, "d2", 1)},
{2 * time.Second, report("d1", 4)},
},
forcedDERP: 2,
wantPrevLen: 2,
wantDERP: 1,
},
{
name: "forced_two_no_probe_recent_activity",
steps: []step{
{time.Second, report("d1", 2)},
{2 * time.Second, report("d1", 4)},
},
opts: &GetReportOpts{
GetLastDERPActivity: mkLDAFunc(map[int]time.Time{
1: startTime,
2: startTime.Add(time.Second),
}),
},
forcedDERP: 2,
wantPrevLen: 2,
wantDERP: 2,
},
{
name: "forced_two_no_probe_no_recent_activity",
steps: []step{
{time.Second, report("d1", 2)},
{PreferredDERPFrameTime + time.Second, report("d1", 4)},
},
opts: &GetReportOpts{
GetLastDERPActivity: mkLDAFunc(map[int]time.Time{
1: startTime,
2: startTime,
}),
},
forcedDERP: 2,
wantPrevLen: 2,
wantDERP: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeTime := startTime
c := &Client{
TimeNow: func() time.Time { return fakeTime },
TimeNow: func() time.Time { return fakeTime },
ForcePreferredDERP: tt.forcedDERP,
}
dm := &tailcfg.DERPMap{HomeParams: tt.homeParams}
rs := &reportState{