summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrea Gottardo <andrea@gottardo.me>2024-02-15 14:28:38 -0800
committerAndrea Gottardo <andrea@gottardo.me>2024-02-23 10:51:33 -0800
commit8a66006f371a408fe58a38eb4b1d1a755b6d17b5 (patch)
tree7452ca987589d6d1c5790b92394be47515ccdb89
parent131f9094fdeda05b0c949055d9b18a495f200769 (diff)
downloadtailscale-angott/sleep-debug-apis.tar.xz
tailscale-angott/sleep-debug-apis.zip
controlclient, ipn: add endpoints to manage sleep/wakeangott/sleep-debug-apis
Updates #3363. Adds localapi + debug endpoints to set/unset sleep mode. Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
-rw-r--r--cmd/tailscale/cli/debug.go12
-rw-r--r--control/controlclient/auto.go11
-rw-r--r--control/controlclient/client.go4
-rw-r--r--ipn/ipnlocal/local.go19
-rw-r--r--ipn/ipnlocal/state_test.go21
-rw-r--r--ipn/localapi/localapi.go19
-rw-r--r--logtail/logtail.go5
7 files changed, 85 insertions, 6 deletions
diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go
index 51e825803..374e7fd98 100644
--- a/cmd/tailscale/cli/debug.go
+++ b/cmd/tailscale/cli/debug.go
@@ -132,7 +132,7 @@ var debugCmd = &ffcli.Command{
{
Name: "derp-set-homeless",
Exec: localAPIAction("derp-set-homeless"),
- ShortHelp: "enable DERP homeless mode (breaks reachablility)",
+ ShortHelp: "enable DERP homeless mode (breaks reachability)",
},
{
Name: "derp-unset-homeless",
@@ -140,6 +140,16 @@ var debugCmd = &ffcli.Command{
ShortHelp: "disable DERP homeless mode",
},
{
+ Name: "sleep-set",
+ Exec: localAPIAction("sleep-set"),
+ ShortHelp: "asks the backend to enter sleep mode",
+ },
+ {
+ Name: "sleep-unset",
+ Exec: localAPIAction("sleep-unset"),
+ ShortHelp: "asks the backend to leave sleep mode and resume all features",
+ },
+ {
Name: "break-tcp-conns",
Exec: localAPIAction("break-tcp-conns"),
ShortHelp: "break any open TCP connections from the daemon",
diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index d0551cdab..f4d9a4f1a 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -139,6 +139,7 @@ type Auto struct {
loginGoal *LoginGoal // non-nil if some login activity is desired
inMapPoll bool // true once we get the first MapResponse in a stream; false when HTTP response ends
state State // TODO(bradfitz): delete this, make it computed by method from other state
+ isSleeping bool // whether we are ZZZing
authCtx context.Context // context used for auth requests
mapCtx context.Context // context used for netmap and update requests
@@ -200,6 +201,16 @@ func NewNoStart(opts Options) (_ *Auto, err error) {
}
+func (c *Auto) SetSleepMode(enabled bool) {
+ c.logf("setSleepMode(%v)", enabled)
+ c.isSleeping = enabled
+ c.SetPaused(enabled)
+}
+
+func (c *Auto) IsSleeping() bool {
+ return c.isSleeping
+}
+
// SetPaused controls whether HTTP activity should be paused.
//
// The client can be paused and unpaused repeatedly, unlike Start and Shutdown, which can only be used once.
diff --git a/control/controlclient/client.go b/control/controlclient/client.go
index ef5af68c6..2acd82ef8 100644
--- a/control/controlclient/client.go
+++ b/control/controlclient/client.go
@@ -54,6 +54,10 @@ type Client interface {
// TODO: It might be better to simply shutdown the controlclient and
// make a new one when it's time to unpause.
SetPaused(bool)
+ // SetSleepMode pauses the control client and prevents anybody else
+ // from unpausing it until SetSleepMode(false) is called again
+ SetSleepMode(bool)
+ IsSleeping() bool
// AuthCantContinue returns whether authentication is blocked. If it
// is, you either need to visit the auth URL (previously sent in a
// Status callback) or call the Login function appropriately.
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index b8aa769a1..3bc164124 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -53,6 +53,7 @@ import (
"tailscale.com/ipn/policy"
"tailscale.com/log/sockstatlog"
"tailscale.com/logpolicy"
+ "tailscale.com/logtail"
"tailscale.com/net/dns"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
@@ -580,7 +581,12 @@ func (b *LocalBackend) pauseOrResumeControlClientLocked() {
return
}
networkUp := b.prevIfState.AnyInterfaceUp()
- b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || (!networkUp && !testenv.InTest()))
+ shouldPause := (b.state == ipn.Stopped && b.netMap != nil) || (!networkUp && !testenv.InTest())
+ if b.cc.IsSleeping() && shouldPause == false {
+ // Leave things untouched if a request to un-pause comes in while we should be sleeping.
+ return
+ }
+ b.cc.SetPaused(shouldPause)
}
// linkChange is our network monitor callback, called whenever the network changes.
@@ -5327,6 +5333,17 @@ func peerCanProxyDNS(p tailcfg.NodeView) bool {
return false
}
+func (b *LocalBackend) SetSleep(enabled bool) {
+ b.logf("SetSleep: enabled = %v", enabled)
+ b.cc.SetSleepMode(enabled)
+ b.MagicConn().SetHomeless(enabled)
+ if enabled {
+ logtail.Disable()
+ } else {
+ logtail.Enable()
+ }
+}
+
func (b *LocalBackend) DebugRebind() error {
b.MagicConn().Rebind()
return nil
diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go
index 403233a11..395fc750a 100644
--- a/ipn/ipnlocal/state_test.go
+++ b/ipn/ipnlocal/state_test.go
@@ -91,10 +91,11 @@ func (nt *notifyThrottler) drain(count int) []ipn.Notify {
// in the controlclient.Client, so by controlling it, we can check that
// the state machine works as expected.
type mockControl struct {
- tb testing.TB
- logf logger.Logf
- opts controlclient.Options
- paused atomic.Bool
+ tb testing.TB
+ logf logger.Logf
+ opts controlclient.Options
+ paused atomic.Bool
+ isSleeping atomic.Bool
mu sync.Mutex
machineKey key.MachinePrivate
@@ -236,6 +237,18 @@ func (cc *mockControl) SetPaused(paused bool) {
}
}
+func (cc *mockControl) SetSleepMode(enabled bool) {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ cc.isSleeping.Store(enabled)
+}
+
+func (cc *mockControl) IsSleeping() bool {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ return cc.isSleeping.Load()
+}
+
func (cc *mockControl) AuthCantContinue() bool {
cc.mu.Lock()
defer cc.mu.Unlock()
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index a1b7da46f..cd5ebb811 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -110,6 +110,7 @@ var handler = map[string]localAPIHandler{
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
+ "set-sleep": (*Handler).serveSetSleep,
"tailfs/fileserver-address": (*Handler).serveTailFSFileServerAddr,
"tailfs/shares": (*Handler).serveShares,
"start": (*Handler).serveStart,
@@ -573,6 +574,10 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) {
h.b.MagicConn().SetHomeless(true)
case "derp-unset-homeless":
h.b.MagicConn().SetHomeless(false)
+ case "sleep-set":
+ h.b.SetSleep(true)
+ case "sleep-unset":
+ h.b.SetSleep(false)
case "rebind":
err = h.b.DebugRebind()
case "restun":
@@ -1695,6 +1700,20 @@ func (h *Handler) serveSetExpirySooner(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "done\n")
}
+func (h *Handler) serveSetSleep(w http.ResponseWriter, r *http.Request) {
+ if !h.PermitWrite {
+ http.Error(w, "access denied", http.StatusForbidden)
+ return
+ }
+ if r.Method != "POST" {
+ http.Error(w, "want POST", http.StatusBadRequest)
+ return
+ }
+ h.b.SetSleep(r.FormValue("sleep") == "true")
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(struct{}{})
+}
+
func (h *Handler) servePing(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != "POST" {
diff --git a/logtail/logtail.go b/logtail/logtail.go
index fcaf80e41..90be1b515 100644
--- a/logtail/logtail.go
+++ b/logtail/logtail.go
@@ -510,6 +510,11 @@ func (l *Logger) StartFlush() {
// logtailDisabled is whether logtail uploads to logcatcher are disabled.
var logtailDisabled atomic.Bool
+// Enable enables logtail uploads for the lifetime of the process.
+func Enable() {
+ logtailDisabled.Store(false)
+}
+
// Disable disables logtail uploads for the lifetime of the process.
func Disable() {
logtailDisabled.Store(true)