|
|
@@ -392,10 +392,11 @@ type probePlan map[string][]probe
|
|
|
// sortRegions returns the regions of dm first sorted
|
|
|
// from fastest to slowest (based on the 'last' report),
|
|
|
// end in regions that have no data.
|
|
|
-func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion) {
|
|
|
+func sortRegions(dm *tailcfg.DERPMap, last *Report, preferredDERP int) (prev []*tailcfg.DERPRegion) {
|
|
|
prev = make([]*tailcfg.DERPRegion, 0, len(dm.Regions))
|
|
|
for _, reg := range dm.Regions {
|
|
|
- if reg.Avoid {
|
|
|
+ // include an otherwise avoid region if it is the current preferred region
|
|
|
+ if reg.Avoid && reg.RegionID != preferredDERP {
|
|
|
continue
|
|
|
}
|
|
|
prev = append(prev, reg)
|
|
|
@@ -420,9 +421,19 @@ func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion)
|
|
|
// a full report, all regions are scanned.)
|
|
|
const numIncrementalRegions = 3
|
|
|
|
|
|
-// makeProbePlan generates the probe plan for a DERPMap, given the most
|
|
|
-// recent report and whether IPv6 is configured on an interface.
|
|
|
-func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (plan probePlan) {
|
|
|
+// makeProbePlan generates the probe plan for a DERPMap, given the most recent
|
|
|
+// report and the current home DERP. preferredDERP is passed independently of
|
|
|
+// last (report) because last is currently nil'd to indicate a desire for a full
|
|
|
+// netcheck.
|
|
|
+//
|
|
|
+// TODO(raggi,jwhited): refactor the callers and this function to be more clear
|
|
|
+// about full vs. incremental netchecks, and remove the need for the history
|
|
|
+// hiding. This was avoided in an incremental change due to exactly this kind of
|
|
|
+// distant coupling.
|
|
|
+// TODO(raggi): change from "preferred DERP" from a historical report to "home
|
|
|
+// DERP" as in what DERP is the current home connection, this would further
|
|
|
+// reduce flap events.
|
|
|
+func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report, preferredDERP int) (plan probePlan) {
|
|
|
if last == nil || len(last.RegionLatency) == 0 {
|
|
|
return makeProbePlanInitial(dm, ifState)
|
|
|
}
|
|
|
@@ -433,9 +444,34 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
|
|
had4 := len(last.RegionV4Latency) > 0
|
|
|
had6 := len(last.RegionV6Latency) > 0
|
|
|
hadBoth := have6if && had4 && had6
|
|
|
- for ri, reg := range sortRegions(dm, last) {
|
|
|
- if ri == numIncrementalRegions {
|
|
|
- break
|
|
|
+ // #13969 ensure that the home region is always probed.
|
|
|
+ // If a netcheck has unstable latency, such as a user with large amounts of
|
|
|
+ // bufferbloat or a highly congested connection, there are cases where a full
|
|
|
+ // netcheck may observe a one-off high latency to the current home DERP. Prior
|
|
|
+ // to the forced inclusion of the home DERP, this would result in an
|
|
|
+ // incremental netcheck following such an event to cause a home DERP move, with
|
|
|
+ // restoration back to the home DERP on the next full netcheck ~5 minutes later
|
|
|
+ // - which is highly disruptive when it causes shifts in geo routed subnet
|
|
|
+ // routers. By always including the home DERP in the incremental netcheck, we
|
|
|
+ // ensure that the home DERP is always probed, even if it observed a recenet
|
|
|
+ // poor latency sample. This inclusion enables the latency history checks in
|
|
|
+ // home DERP selection to still take effect.
|
|
|
+ // planContainsHome indicates whether the home DERP has been added to the probePlan,
|
|
|
+ // if there is no prior home, then there's no home to additionally include.
|
|
|
+ planContainsHome := preferredDERP == 0
|
|
|
+ for ri, reg := range sortRegions(dm, last, preferredDERP) {
|
|
|
+ regIsHome := reg.RegionID == preferredDERP
|
|
|
+ if ri >= numIncrementalRegions {
|
|
|
+ // planned at least numIncrementalRegions regions and that includes the
|
|
|
+ // last home region (or there was none), plan complete.
|
|
|
+ if planContainsHome {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // planned at least numIncrementalRegions regions, but not the home region,
|
|
|
+ // check if this is the home region, if not, skip it.
|
|
|
+ if !regIsHome {
|
|
|
+ continue
|
|
|
+ }
|
|
|
}
|
|
|
var p4, p6 []probe
|
|
|
do4 := have4if
|
|
|
@@ -446,7 +482,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
|
|
tries := 1
|
|
|
isFastestTwo := ri < 2
|
|
|
|
|
|
- if isFastestTwo {
|
|
|
+ if isFastestTwo || regIsHome {
|
|
|
tries = 2
|
|
|
} else if hadBoth {
|
|
|
// For dual stack machines, make the 3rd & slower nodes alternate
|
|
|
@@ -457,14 +493,15 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
|
|
do4, do6 = false, true
|
|
|
}
|
|
|
}
|
|
|
- if !isFastestTwo && !had6 {
|
|
|
+ if !regIsHome && !isFastestTwo && !had6 {
|
|
|
do6 = false
|
|
|
}
|
|
|
|
|
|
- if reg.RegionID == last.PreferredDERP {
|
|
|
+ if regIsHome {
|
|
|
// But if we already had a DERP home, try extra hard to
|
|
|
// make sure it's there so we don't flip flop around.
|
|
|
tries = 4
|
|
|
+ planContainsHome = true
|
|
|
}
|
|
|
|
|
|
for try := 0; try < tries; try++ {
|
|
|
@@ -789,9 +826,10 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
|
|
|
c.curState = rs
|
|
|
last := c.last
|
|
|
|
|
|
- // Even if we're doing a non-incremental update, we may want to try our
|
|
|
- // preferred DERP region for captive portal detection. Save that, if we
|
|
|
- // have it.
|
|
|
+ // Extract preferredDERP from the last report, if available. This will be used
|
|
|
+ // in captive portal detection and DERP flapping suppression. Ideally this would
|
|
|
+ // be the current active home DERP rather than the last report preferred DERP,
|
|
|
+ // but only the latter is presently available.
|
|
|
var preferredDERP int
|
|
|
if last != nil {
|
|
|
preferredDERP = last.PreferredDERP
|
|
|
@@ -848,7 +886,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
|
|
|
|
|
|
var plan probePlan
|
|
|
if opts == nil || !opts.OnlyTCP443 {
|
|
|
- plan = makeProbePlan(dm, ifState, last)
|
|
|
+ plan = makeProbePlan(dm, ifState, last, preferredDERP)
|
|
|
}
|
|
|
|
|
|
// If we're doing a full probe, also check for a captive portal. We
|