From 664f490bf1fcb55417b9e0b1eca8d46fb6272cd5 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 21 Apr 2021 14:17:21 -0700 Subject: [PATCH] control/controlclient, tailcfg: add Debug.SleepSeconds (mapver 19) Signed-off-by: Brad Fitzpatrick --- control/controlclient/direct.go | 39 +++++++++++++++++++++++++++++++++ tailcfg/tailcfg.go | 9 +++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 96bd268b4..726d86fd7 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -795,6 +795,45 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm } setControlAtomic(&controlUseDERPRoute, resp.Debug.DERPRoute) setControlAtomic(&controlTrimWGConfig, resp.Debug.TrimWGConfig) + if sleep := time.Duration(resp.Debug.SleepSeconds * float64(time.Second)); sleep > 0 { + const maxSleep = 5 * time.Minute + // SleepFor sleeps for d, but keeps the poll timeout from + // firing while we're sleeeping. + sleepFor := func(d time.Duration) { + ticker := time.NewTicker(pollTimeout / 2) + defer ticker.Stop() + timer := time.NewTimer(d) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return + case <-timer.C: + return + case <-ticker.C: + select { + case timeoutReset <- struct{}{}: + case <-timer.C: + return + case <-ctx.Done(): + return + } + } + } + } + if sleep > maxSleep { + c.logf("sleeping for %v, capped from server-requested %v ...", maxSleep, sleep) + sleepFor(maxSleep) + } else { + c.logf("sleeping for server-requested %v ...", sleep) + sleepFor(sleep) + } + select { + default: + case <-ctx.Done(): + return ctx.Err() + } + } } nm := sess.netmapForResponse(&resp) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 81419040a..1c49a19dd 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -41,7 +41,8 @@ import ( // 16: 2021-04-15: client understands Node.Online, MapResponse.OnlineChange // 17: 2021-04-18: MapResponse.Domain empty means unchanged // 18: 2021-04-19: MapResponse.Node nil means unchanged (all fields now omitempty) -const CurrentMapRequestVersion = 18 +// 19: 2021-04-21: MapResponse.Debug.SleepSeconds +const CurrentMapRequestVersion = 19 type StableID string @@ -1012,6 +1013,12 @@ type Debug struct { // GoroutineDumpURL, if non-empty, requests that the client do // a one-time dump of its active goroutines to the given URL. GoroutineDumpURL string `json:",omitempty"` + + // SleepSeconds requests that the client sleep for the + // provided number of seconds. + // The client can (and should) limit the value (such as 5 + // minutes). + SleepSeconds float64 `json:",omitempty"` } func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }