|
|
|
@ -117,13 +117,15 @@ type Client struct {
|
|
|
|
|
mu sync.Mutex // mutex guards the following fields
|
|
|
|
|
statusFunc func(Status) // called to update Client status
|
|
|
|
|
|
|
|
|
|
loggedIn bool // true if currently logged in
|
|
|
|
|
loginGoal *LoginGoal // non-nil if some login activity is desired
|
|
|
|
|
synced bool // true if our netmap is up-to-date
|
|
|
|
|
hostinfo *tailcfg.Hostinfo
|
|
|
|
|
inPollNetMap bool // true if currently running a PollNetMap
|
|
|
|
|
inSendStatus int // number of sendStatus calls currently in progress
|
|
|
|
|
state State
|
|
|
|
|
paused bool // whether we should stop making HTTP requests
|
|
|
|
|
unpauseWaiters []chan struct{}
|
|
|
|
|
loggedIn bool // true if currently logged in
|
|
|
|
|
loginGoal *LoginGoal // non-nil if some login activity is desired
|
|
|
|
|
synced bool // true if our netmap is up-to-date
|
|
|
|
|
hostinfo *tailcfg.Hostinfo
|
|
|
|
|
inPollNetMap bool // true if currently running a PollNetMap
|
|
|
|
|
inSendStatus int // number of sendStatus calls currently in progress
|
|
|
|
|
state State
|
|
|
|
|
|
|
|
|
|
authCtx context.Context // context used for auth requests
|
|
|
|
|
mapCtx context.Context // context used for netmap requests
|
|
|
|
@ -169,6 +171,27 @@ func NewNoStart(opts Options) (*Client, error) {
|
|
|
|
|
return c, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
func (c *Client) SetPaused(paused bool) {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
if paused == c.paused {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
c.paused = paused
|
|
|
|
|
if paused {
|
|
|
|
|
// Just cancel the map routine. The auth routine isn't expensive.
|
|
|
|
|
c.cancelMapLocked()
|
|
|
|
|
} else {
|
|
|
|
|
for _, ch := range c.unpauseWaiters {
|
|
|
|
|
close(ch)
|
|
|
|
|
}
|
|
|
|
|
c.unpauseWaiters = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start starts the client's goroutines.
|
|
|
|
|
//
|
|
|
|
|
// It should only be called for clients created by NewNoStart.
|
|
|
|
@ -272,6 +295,7 @@ func (c *Client) authRoutine() {
|
|
|
|
|
if goal == nil {
|
|
|
|
|
// Wait for something interesting to happen
|
|
|
|
|
var exp <-chan time.Time
|
|
|
|
|
var expTimer *time.Timer
|
|
|
|
|
if expiry != nil && !expiry.IsZero() {
|
|
|
|
|
// if expiry is in the future, don't delay
|
|
|
|
|
// past that time.
|
|
|
|
@ -284,11 +308,15 @@ func (c *Client) authRoutine() {
|
|
|
|
|
if delay > 5*time.Second {
|
|
|
|
|
delay = time.Second
|
|
|
|
|
}
|
|
|
|
|
exp = time.After(delay)
|
|
|
|
|
expTimer = time.NewTimer(delay)
|
|
|
|
|
exp = expTimer.C
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
if expTimer != nil {
|
|
|
|
|
expTimer.Stop()
|
|
|
|
|
}
|
|
|
|
|
c.logf("authRoutine: context done.")
|
|
|
|
|
case <-exp:
|
|
|
|
|
// Unfortunately the key expiry isn't provided
|
|
|
|
@ -310,7 +338,7 @@ func (c *Client) authRoutine() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if !goal.wantLoggedIn {
|
|
|
|
|
err := c.direct.TryLogout(c.authCtx)
|
|
|
|
|
err := c.direct.TryLogout(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
report(err, "TryLogout")
|
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
@ -399,12 +427,35 @@ func (c *Client) Direct() *Direct {
|
|
|
|
|
return c.direct
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unpausedChanLocked returns a new channel that is closed when the
|
|
|
|
|
// current Client pause is unpaused.
|
|
|
|
|
//
|
|
|
|
|
// c.mu must be held
|
|
|
|
|
func (c *Client) unpausedChanLocked() <-chan struct{} {
|
|
|
|
|
unpaused := make(chan struct{})
|
|
|
|
|
c.unpauseWaiters = append(c.unpauseWaiters, unpaused)
|
|
|
|
|
return unpaused
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) mapRoutine() {
|
|
|
|
|
defer close(c.mapDone)
|
|
|
|
|
bo := backoff.NewBackoff("mapRoutine", c.logf, 30*time.Second)
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
if c.paused {
|
|
|
|
|
unpaused := c.unpausedChanLocked()
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
c.logf("mapRoutine: awaiting unpause")
|
|
|
|
|
select {
|
|
|
|
|
case <-unpaused:
|
|
|
|
|
c.logf("mapRoutine: unpaused")
|
|
|
|
|
case <-c.quit:
|
|
|
|
|
c.logf("mapRoutine: quit")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
c.logf("mapRoutine: %s", c.state)
|
|
|
|
|
loggedIn := c.loggedIn
|
|
|
|
|
ctx := c.mapCtx
|
|
|
|
@ -487,8 +538,14 @@ func (c *Client) mapRoutine() {
|
|
|
|
|
if c.state == StateSynchronized {
|
|
|
|
|
c.state = StateAuthenticated
|
|
|
|
|
}
|
|
|
|
|
paused := c.paused
|
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if paused {
|
|
|
|
|
c.logf("mapRoutine: paused")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
report(err, "PollNetMap")
|
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
|