controlclient, ipn: add endpoints to manage sleep/wake

Updates #3363. Adds localapi + debug endpoints to set/unset sleep mode.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
Andrea Gottardo 2024-02-15 14:28:38 -08:00
parent 131f9094fd
commit 8a66006f37
7 changed files with 85 additions and 6 deletions

View File

@ -132,13 +132,23 @@ 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",
Exec: localAPIAction("derp-unset-homeless"),
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"),

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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()

View File

@ -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" {

View File

@ -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)