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:
parent
131f9094fd
commit
8a66006f37
|
@ -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"),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue