@@ -391,10 +391,11 @@ type probePlan map[string][]probe
391
391
// sortRegions returns the regions of dm first sorted
392
392
// from fastest to slowest (based on the 'last' report),
393
393
// end in regions that have no data.
394
- func sortRegions (dm * tailcfg.DERPMap , last * Report ) (prev []* tailcfg.DERPRegion ) {
394
+ func sortRegions (dm * tailcfg.DERPMap , last * Report , preferredDERP int ) (prev []* tailcfg.DERPRegion ) {
395
395
prev = make ([]* tailcfg.DERPRegion , 0 , len (dm .Regions ))
396
396
for _ , reg := range dm .Regions {
397
- if reg .Avoid {
397
+ // include an otherwise avoid region if it is the current preferred region
398
+ if reg .Avoid && reg .RegionID != preferredDERP {
398
399
continue
399
400
}
400
401
prev = append (prev , reg )
@@ -419,9 +420,19 @@ func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion)
419
420
// a full report, all regions are scanned.)
420
421
const numIncrementalRegions = 3
421
422
422
- // makeProbePlan generates the probe plan for a DERPMap, given the most
423
- // recent report and whether IPv6 is configured on an interface.
424
- func makeProbePlan (dm * tailcfg.DERPMap , ifState * netmon.State , last * Report ) (plan probePlan ) {
423
+ // makeProbePlan generates the probe plan for a DERPMap, given the most recent
424
+ // report and the current home DERP. preferredDERP is passed independently of
425
+ // last (report) because last is currently nil'd to indicate a desire for a full
426
+ // netcheck.
427
+ //
428
+ // TODO(raggi,jwhited): refactor the callers and this function to be more clear
429
+ // about full vs. incremental netchecks, and remove the need for the history
430
+ // hiding. This was avoided in an incremental change due to exactly this kind of
431
+ // distant coupling.
432
+ // TODO(raggi): change from "preferred DERP" from a historical report to "home
433
+ // DERP" as in what DERP is the current home connection, this would further
434
+ // reduce flap events.
435
+ func makeProbePlan (dm * tailcfg.DERPMap , ifState * netmon.State , last * Report , preferredDERP int ) (plan probePlan ) {
425
436
if last == nil || len (last .RegionLatency ) == 0 {
426
437
return makeProbePlanInitial (dm , ifState )
427
438
}
@@ -432,9 +443,34 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
432
443
had4 := len (last .RegionV4Latency ) > 0
433
444
had6 := len (last .RegionV6Latency ) > 0
434
445
hadBoth := have6if && had4 && had6
435
- for ri , reg := range sortRegions (dm , last ) {
436
- if ri == numIncrementalRegions {
437
- break
446
+ // #13969 ensure that the home region is always probed.
447
+ // If a netcheck has unstable latency, such as a user with large amounts of
448
+ // bufferbloat or a highly congested connection, there are cases where a full
449
+ // netcheck may observe a one-off high latency to the current home DERP. Prior
450
+ // to the forced inclusion of the home DERP, this would result in an
451
+ // incremental netcheck following such an event to cause a home DERP move, with
452
+ // restoration back to the home DERP on the next full netcheck ~5 minutes later
453
+ // - which is highly disruptive when it causes shifts in geo routed subnet
454
+ // routers. By always including the home DERP in the incremental netcheck, we
455
+ // ensure that the home DERP is always probed, even if it observed a recenet
456
+ // poor latency sample. This inclusion enables the latency history checks in
457
+ // home DERP selection to still take effect.
458
+ // planContainsHome indicates whether the home DERP has been added to the probePlan,
459
+ // if there is no prior home, then there's no home to additionally include.
460
+ planContainsHome := preferredDERP == 0
461
+ for ri , reg := range sortRegions (dm , last , preferredDERP ) {
462
+ regIsHome := reg .RegionID == preferredDERP
463
+ if ri >= numIncrementalRegions {
464
+ // planned at least numIncrementalRegions regions and that includes the
465
+ // last home region (or there was none), plan complete.
466
+ if planContainsHome {
467
+ break
468
+ }
469
+ // planned at least numIncrementalRegions regions, but not the home region,
470
+ // check if this is the home region, if not, skip it.
471
+ if ! regIsHome {
472
+ continue
473
+ }
438
474
}
439
475
var p4 , p6 []probe
440
476
do4 := have4if
@@ -445,7 +481,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
445
481
tries := 1
446
482
isFastestTwo := ri < 2
447
483
448
- if isFastestTwo {
484
+ if isFastestTwo || regIsHome {
449
485
tries = 2
450
486
} else if hadBoth {
451
487
// For dual stack machines, make the 3rd & slower nodes alternate
@@ -456,14 +492,15 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
456
492
do4 , do6 = false , true
457
493
}
458
494
}
459
- if ! isFastestTwo && ! had6 {
495
+ if ! regIsHome && ! isFastestTwo && ! had6 {
460
496
do6 = false
461
497
}
462
498
463
- if reg . RegionID == last . PreferredDERP {
499
+ if regIsHome {
464
500
// But if we already had a DERP home, try extra hard to
465
501
// make sure it's there so we don't flip flop around.
466
502
tries = 4
503
+ planContainsHome = true
467
504
}
468
505
469
506
for try := 0 ; try < tries ; try ++ {
@@ -788,9 +825,10 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
788
825
c .curState = rs
789
826
last := c .last
790
827
791
- // Even if we're doing a non-incremental update, we may want to try our
792
- // preferred DERP region for captive portal detection. Save that, if we
793
- // have it.
828
+ // Extract preferredDERP from the last report, if available. This will be used
829
+ // in captive portal detection and DERP flapping suppression. Ideally this would
830
+ // be the current active home DERP rather than the last report preferred DERP,
831
+ // but only the latter is presently available.
794
832
var preferredDERP int
795
833
if last != nil {
796
834
preferredDERP = last .PreferredDERP
@@ -847,7 +885,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
847
885
848
886
var plan probePlan
849
887
if opts == nil || ! opts .OnlyTCP443 {
850
- plan = makeProbePlan (dm , ifState , last )
888
+ plan = makeProbePlan (dm , ifState , last , preferredDERP )
851
889
}
852
890
853
891
// If we're doing a full probe, also check for a captive portal. We
0 commit comments