@ -23,6 +23,7 @@ import (
"syscall"
"syscall"
"time"
"time"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derphttp"
"tailscale.com/envknob"
"tailscale.com/envknob"
"tailscale.com/hostinfo"
"tailscale.com/hostinfo"
@ -449,7 +450,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report, pre
// restoration back to the home DERP on the next full netcheck ~5 minutes later
// 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
// - which is highly disruptive when it causes shifts in geo routed subnet
// routers. By always including the home DERP in the incremental netcheck, we
// 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 recen e t
// ensure that the home DERP is always probed, even if it observed a recen t
// poor latency sample. This inclusion enables the latency history checks in
// poor latency sample. This inclusion enables the latency history checks in
// home DERP selection to still take effect.
// home DERP selection to still take effect.
// planContainsHome indicates whether the home DERP has been added to the probePlan,
// planContainsHome indicates whether the home DERP has been added to the probePlan,
@ -989,7 +990,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
var wg sync . WaitGroup
var wg sync . WaitGroup
var need [ ] * tailcfg . DERPRegion
var need [ ] * tailcfg . DERPRegion
for rid , reg := range dm . Regions {
for rid , reg := range dm . Regions {
if ! rs . haveRegionLatency ( rid ) && regionHasDERPNode ( reg ) {
if ! rs . haveRegionLatency ( rid ) && regionHasDERPNode ( reg ) && ! reg . Avoid && ! reg . NoMeasureNoHome {
need = append ( need , reg )
need = append ( need , reg )
}
}
}
}
@ -1371,6 +1372,15 @@ const (
// even without receiving a STUN response.
// even without receiving a STUN response.
// Note: must remain higher than the derp package frameReceiveRecordRate
// Note: must remain higher than the derp package frameReceiveRecordRate
PreferredDERPFrameTime = 8 * time . Second
PreferredDERPFrameTime = 8 * time . Second
// PreferredDERPKeepAliveTimeout is 2x the DERP Keep Alive timeout. If there
// is no latency data to make judgements from, but we have heard from our
// current DERP region inside of 2x the KeepAlive window, don't switch DERP
// regions yet, keep the current region. This prevents region flapping /
// home DERP removal during short periods of packet loss where the DERP TCP
// connection may itself naturally recover.
// TODO(raggi): expose shared time bounds from the DERP package rather than
// duplicating them here.
PreferredDERPKeepAliveTimeout = 2 * derp . KeepAlive
)
)
// addReportHistoryAndSetPreferredDERP adds r to the set of recent Reports
// addReportHistoryAndSetPreferredDERP adds r to the set of recent Reports
@ -1455,13 +1465,10 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(rs *reportState, r *Report,
// the STUN probe) since we started the netcheck, or in the past 2s, as
// the STUN probe) since we started the netcheck, or in the past 2s, as
// another signal for "this region is still working".
// another signal for "this region is still working".
heardFromOldRegionRecently := false
heardFromOldRegionRecently := false
prevRegionLastHeard := rs . opts . getLastDERPActivity ( prevDERP )
if changingPreferred {
if changingPreferred {
if lastHeard := rs . opts . getLastDERPActivity ( prevDERP ) ; ! lastHeard . IsZero ( ) {
heardFromOldRegionRecently = prevRegionLastHeard . After ( rs . start )
now := c . timeNow ( )
heardFromOldRegionRecently = heardFromOldRegionRecently || prevRegionLastHeard . After ( now . Add ( - PreferredDERPFrameTime ) )
heardFromOldRegionRecently = lastHeard . After ( rs . start )
heardFromOldRegionRecently = heardFromOldRegionRecently || lastHeard . After ( now . Add ( - PreferredDERPFrameTime ) )
}
}
}
// The old region is accessible if we've heard from it via a non-STUN
// The old region is accessible if we've heard from it via a non-STUN
@ -1488,17 +1495,20 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(rs *reportState, r *Report,
// If the forced DERP region probed successfully, or has recent traffic,
// If the forced DERP region probed successfully, or has recent traffic,
// use it.
// use it.
_ , haveLatencySample := r . RegionLatency [ c . ForcePreferredDERP ]
_ , haveLatencySample := r . RegionLatency [ c . ForcePreferredDERP ]
var recentActivity bool
lastHeard := rs . opts . getLastDERPActivity ( c . ForcePreferredDERP )
if lastHeard := rs . opts . getLastDERPActivity ( c . ForcePreferredDERP ) ; ! lastHeard . IsZero ( ) {
recentActivity := lastHeard . After ( rs . start )
now := c . timeNow ( )
recentActivity = recentActivity || lastHeard . After ( now . Add ( - PreferredDERPFrameTime ) )
recentActivity = lastHeard . After ( rs . start )
recentActivity = recentActivity || lastHeard . After ( now . Add ( - PreferredDERPFrameTime ) )
}
if haveLatencySample || recentActivity {
if haveLatencySample || recentActivity {
r . PreferredDERP = c . ForcePreferredDERP
r . PreferredDERP = c . ForcePreferredDERP
}
}
}
}
// If there was no latency data to make judgements on, but there is an
// active DERP connection that has at least been doing KeepAlive recently,
// keep it, rather than dropping it.
if r . PreferredDERP == 0 && prevRegionLastHeard . After ( now . Add ( - PreferredDERPKeepAliveTimeout ) ) {
r . PreferredDERP = prevDERP
}
}
}
func updateLatency ( m map [ int ] time . Duration , regionID int , d time . Duration ) {
func updateLatency ( m map [ int ] time . Duration , regionID int , d time . Duration ) {