2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
package controlclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-06-20 01:14:45 +00:00
|
|
|
"errors"
|
2020-02-05 22:16:58 +00:00
|
|
|
"fmt"
|
2022-03-10 18:28:42 +00:00
|
|
|
"net/http"
|
2020-02-05 22:16:58 +00:00
|
|
|
"sync"
|
2023-08-10 02:56:43 +00:00
|
|
|
"sync/atomic"
|
2020-02-05 22:16:58 +00:00
|
|
|
"time"
|
|
|
|
|
2021-02-18 16:58:13 +00:00
|
|
|
"tailscale.com/health"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/logtail/backoff"
|
2023-02-03 20:07:58 +00:00
|
|
|
"tailscale.com/net/sockstats"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/tailcfg"
|
2023-08-04 23:29:44 +00:00
|
|
|
"tailscale.com/tstime"
|
2020-02-14 21:09:19 +00:00
|
|
|
"tailscale.com/types/empty"
|
2021-10-28 21:22:51 +00:00
|
|
|
"tailscale.com/types/key"
|
2020-02-15 03:23:16 +00:00
|
|
|
"tailscale.com/types/logger"
|
2021-02-05 23:44:46 +00:00
|
|
|
"tailscale.com/types/netmap"
|
2021-02-05 23:23:01 +00:00
|
|
|
"tailscale.com/types/persist"
|
2023-08-04 05:27:02 +00:00
|
|
|
"tailscale.com/types/ptr"
|
2020-05-03 20:58:39 +00:00
|
|
|
"tailscale.com/types/structs"
|
2020-02-05 22:16:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type LoginGoal struct {
|
2021-04-08 04:06:31 +00:00
|
|
|
_ structs.Incomparable
|
|
|
|
wantLoggedIn bool // true if we *want* to be logged in
|
|
|
|
token *tailcfg.Oauth2Token // oauth token to use when logging in
|
|
|
|
flags LoginFlags // flags to use when logging in
|
|
|
|
url string // auth url that needs to be visited
|
|
|
|
loggedOutResult chan<- error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *LoginGoal) sendLogoutError(err error) {
|
|
|
|
if g.loggedOutResult == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case g.loggedOutResult <- err:
|
|
|
|
default:
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2022-06-19 22:06:33 +00:00
|
|
|
var _ Client = (*Auto)(nil)
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// waitUnpause waits until the client is unpaused then returns. It only
|
|
|
|
// returns an error if the client is closed.
|
|
|
|
func (c *Auto) waitUnpause(routineLogName string) error {
|
|
|
|
c.mu.Lock()
|
|
|
|
if !c.paused {
|
|
|
|
c.mu.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
unpaused := c.unpausedChanLocked()
|
|
|
|
c.mu.Unlock()
|
|
|
|
c.logf("%s: awaiting unpause", routineLogName)
|
|
|
|
select {
|
|
|
|
case <-unpaused:
|
|
|
|
c.logf("%s: unpaused", routineLogName)
|
|
|
|
return nil
|
|
|
|
case <-c.quit:
|
|
|
|
return errors.New("quit")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateRoutine is responsible for informing the server of worthy changes to
|
|
|
|
// our local state. It runs in its own goroutine.
|
|
|
|
func (c *Auto) updateRoutine() {
|
|
|
|
defer close(c.updateDone)
|
|
|
|
bo := backoff.NewBackoff("updateRoutine", c.logf, 30*time.Second)
|
2023-08-13 15:31:15 +00:00
|
|
|
|
|
|
|
// lastUpdateGenInformed is the value of lastUpdateAt that we've successfully
|
|
|
|
// informed the server of.
|
|
|
|
var lastUpdateGenInformed updateGen
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
for {
|
|
|
|
if err := c.waitUnpause("updateRoutine"); err != nil {
|
|
|
|
c.logf("updateRoutine: exiting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
gen := c.lastUpdateGen
|
|
|
|
ctx := c.mapCtx
|
2023-08-13 15:31:15 +00:00
|
|
|
needUpdate := gen > 0 && gen != lastUpdateGenInformed && c.loggedIn
|
2023-08-10 02:56:43 +00:00
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
if needUpdate {
|
|
|
|
select {
|
|
|
|
case <-c.quit:
|
|
|
|
c.logf("updateRoutine: exiting")
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Nothing to do, wait for a signal.
|
|
|
|
select {
|
|
|
|
case <-c.quit:
|
|
|
|
c.logf("updateRoutine: exiting")
|
|
|
|
return
|
|
|
|
case <-c.updateCh:
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t0 := c.clock.Now()
|
|
|
|
err := c.direct.SendUpdate(ctx)
|
|
|
|
d := time.Since(t0).Round(time.Millisecond)
|
|
|
|
if err != nil {
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
c.direct.logf("lite map update error after %v: %v", d, err)
|
|
|
|
}
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
c.direct.logf("[v1] successful lite map update in %v", d)
|
|
|
|
|
2023-08-13 15:31:15 +00:00
|
|
|
lastUpdateGenInformed = gen
|
2023-08-10 02:56:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// atomicGen is an atomic int64 generator. It is used to generate monotonically
|
|
|
|
// increasing numbers for updateGen.
|
|
|
|
var atomicGen atomic.Int64
|
|
|
|
|
|
|
|
func nextUpdateGen() updateGen {
|
|
|
|
return updateGen(atomicGen.Add(1))
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateGen is a monotonically increasing number that represents a particular
|
|
|
|
// update to the local state.
|
|
|
|
type updateGen int64
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
// Auto connects to a tailcontrol server for a node.
|
|
|
|
// It's a concrete implementation of the Client interface.
|
|
|
|
type Auto struct {
|
2023-08-28 22:27:39 +00:00
|
|
|
direct *Direct // our interface to the server APIs
|
|
|
|
clock tstime.Clock
|
|
|
|
logf logger.Logf
|
|
|
|
closed bool
|
|
|
|
updateCh chan struct{} // readable when we should inform the server of a change
|
|
|
|
newMapCh chan struct{} // readable when we must restart a map request
|
|
|
|
observer Observer // called to update Client status; always non-nil
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2021-02-18 16:58:13 +00:00
|
|
|
unregisterHealthWatch func()
|
|
|
|
|
2022-06-20 01:14:45 +00:00
|
|
|
mu sync.Mutex // mutex guards the following fields
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2023-08-29 05:19:23 +00:00
|
|
|
expiry time.Time
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// lastUpdateGen is the gen of last update we had an update worth sending to
|
|
|
|
// the server.
|
|
|
|
lastUpdateGen updateGen
|
|
|
|
|
|
|
|
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
|
|
|
|
inSendStatus int // number of sendStatus calls currently in progress
|
|
|
|
state State
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
authCtx context.Context // context used for auth requests
|
2023-08-10 02:56:43 +00:00
|
|
|
mapCtx context.Context // context used for netmap and update requests
|
|
|
|
authCancel func() // cancel authCtx
|
|
|
|
mapCancel func() // cancel mapCtx
|
2020-02-05 22:16:58 +00:00
|
|
|
quit chan struct{} // when closed, goroutines should all exit
|
2023-08-10 02:56:43 +00:00
|
|
|
authDone chan struct{} // when closed, authRoutine is done
|
|
|
|
mapDone chan struct{} // when closed, mapRoutine is done
|
|
|
|
updateDone chan struct{} // when closed, updateRoutine is done
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
// New creates and starts a new Auto.
|
|
|
|
func New(opts Options) (*Auto, error) {
|
2020-02-05 22:16:58 +00:00
|
|
|
c, err := NewNoStart(opts)
|
|
|
|
if c != nil {
|
|
|
|
c.Start()
|
|
|
|
}
|
|
|
|
return c, err
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
// NewNoStart creates a new Auto, but without calling Start on it.
|
2022-09-26 22:37:27 +00:00
|
|
|
func NewNoStart(opts Options) (_ *Auto, err error) {
|
2020-02-05 22:16:58 +00:00
|
|
|
direct, err := NewDirect(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-26 22:37:27 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
direct.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2023-08-28 22:27:39 +00:00
|
|
|
if opts.Observer == nil {
|
|
|
|
return nil, errors.New("missing required Options.Observer")
|
2022-06-20 01:14:45 +00:00
|
|
|
}
|
2020-02-11 20:08:07 +00:00
|
|
|
if opts.Logf == nil {
|
2022-03-16 23:27:57 +00:00
|
|
|
opts.Logf = func(fmt string, args ...any) {}
|
2020-02-11 20:08:07 +00:00
|
|
|
}
|
2023-08-04 23:29:44 +00:00
|
|
|
if opts.Clock == nil {
|
|
|
|
opts.Clock = tstime.StdClock{}
|
2020-03-08 12:40:56 +00:00
|
|
|
}
|
2021-04-30 03:27:00 +00:00
|
|
|
c := &Auto{
|
2022-06-20 01:14:45 +00:00
|
|
|
direct: direct,
|
2023-08-04 23:29:44 +00:00
|
|
|
clock: opts.Clock,
|
2022-06-20 01:14:45 +00:00
|
|
|
logf: opts.Logf,
|
2023-08-10 02:56:43 +00:00
|
|
|
updateCh: make(chan struct{}, 1),
|
2022-06-20 01:14:45 +00:00
|
|
|
newMapCh: make(chan struct{}, 1),
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
authDone: make(chan struct{}),
|
|
|
|
mapDone: make(chan struct{}),
|
2023-08-10 02:56:43 +00:00
|
|
|
updateDone: make(chan struct{}),
|
2023-08-28 22:27:39 +00:00
|
|
|
observer: opts.Observer,
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
c.authCtx, c.authCancel = context.WithCancel(context.Background())
|
2023-04-13 01:23:22 +00:00
|
|
|
c.authCtx = sockstats.WithSockStats(c.authCtx, sockstats.LabelControlClientAuto, opts.Logf)
|
2023-02-03 20:07:58 +00:00
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
|
2023-04-13 01:23:22 +00:00
|
|
|
c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto, opts.Logf)
|
2023-02-03 20:07:58 +00:00
|
|
|
|
2022-06-21 14:44:11 +00:00
|
|
|
c.unregisterHealthWatch = health.RegisterWatcher(direct.ReportHealthChange)
|
2020-02-05 22:16:58 +00:00
|
|
|
return c, nil
|
2021-02-18 16:58:13 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-07-31 16:39:45 +00:00
|
|
|
// 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.
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) SetPaused(paused bool) {
|
2020-07-31 16:39:45 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
if paused == c.paused {
|
|
|
|
return
|
|
|
|
}
|
2021-04-30 10:08:26 +00:00
|
|
|
c.logf("setPaused(%v)", paused)
|
2020-07-31 16:39:45 +00:00
|
|
|
c.paused = paused
|
|
|
|
if paused {
|
2020-12-21 16:33:05 +00:00
|
|
|
// Only cancel the map routine. (The auth routine isn't expensive
|
|
|
|
// so it's fine to keep it running.)
|
2020-07-31 16:39:45 +00:00
|
|
|
c.cancelMapLocked()
|
|
|
|
} else {
|
|
|
|
for _, ch := range c.unpauseWaiters {
|
|
|
|
close(ch)
|
|
|
|
}
|
|
|
|
c.unpauseWaiters = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
// Start starts the client's goroutines.
|
|
|
|
//
|
|
|
|
// It should only be called for clients created by NewNoStart.
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) Start() {
|
2020-02-05 22:16:58 +00:00
|
|
|
go c.authRoutine()
|
|
|
|
go c.mapRoutine()
|
2023-08-10 02:56:43 +00:00
|
|
|
go c.updateRoutine()
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// updateControl sends a new OmitPeers, non-streaming map request (to just send
|
|
|
|
// Hostinfo/Netinfo/Endpoints info, while keeping an existing streaming response
|
|
|
|
// open).
|
2020-12-23 21:03:16 +00:00
|
|
|
//
|
|
|
|
// It should be called whenever there's something new to tell the server.
|
2023-08-10 02:56:43 +00:00
|
|
|
func (c *Auto) updateControl() {
|
|
|
|
gen := nextUpdateGen()
|
2020-12-23 21:03:16 +00:00
|
|
|
c.mu.Lock()
|
2023-08-10 02:56:43 +00:00
|
|
|
if gen < c.lastUpdateGen {
|
|
|
|
// This update is out of date.
|
2020-12-23 21:03:16 +00:00
|
|
|
c.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
2023-08-10 02:56:43 +00:00
|
|
|
c.lastUpdateGen = gen
|
|
|
|
c.mu.Unlock()
|
2020-12-23 21:03:16 +00:00
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
select {
|
|
|
|
case c.updateCh <- struct{}{}:
|
|
|
|
default:
|
2023-03-09 01:15:47 +00:00
|
|
|
}
|
2020-12-23 21:03:16 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) cancelAuth() {
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
2023-08-10 02:56:43 +00:00
|
|
|
defer c.mu.Unlock()
|
2020-02-05 22:16:58 +00:00
|
|
|
if c.authCancel != nil {
|
|
|
|
c.authCancel()
|
|
|
|
}
|
|
|
|
if !c.closed {
|
|
|
|
c.authCtx, c.authCancel = context.WithCancel(context.Background())
|
2023-04-13 01:23:22 +00:00
|
|
|
c.authCtx = sockstats.WithSockStats(c.authCtx, sockstats.LabelControlClientAuto, c.logf)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// cancelMapLocked is like cancelMap, but assumes the caller holds c.mu.
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) cancelMapLocked() {
|
2020-02-05 22:16:58 +00:00
|
|
|
if c.mapCancel != nil {
|
|
|
|
c.mapCancel()
|
|
|
|
}
|
|
|
|
if !c.closed {
|
|
|
|
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
|
2023-04-13 01:23:22 +00:00
|
|
|
c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto, c.logf)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// cancelMap cancels the existing mapPoll and liteUpdates.
|
|
|
|
func (c *Auto) cancelMap() {
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
2023-08-10 02:56:43 +00:00
|
|
|
defer c.mu.Unlock()
|
2020-02-05 22:16:58 +00:00
|
|
|
c.cancelMapLocked()
|
|
|
|
}
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// restartMap cancels the existing mapPoll and liteUpdates, and then starts a
|
|
|
|
// new one.
|
|
|
|
func (c *Auto) restartMap() {
|
2023-08-12 01:49:40 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
c.cancelMapLocked()
|
|
|
|
synced := c.synced
|
|
|
|
c.mu.Unlock()
|
2023-03-09 01:15:47 +00:00
|
|
|
|
2023-08-12 01:49:40 +00:00
|
|
|
c.logf("[v1] restartMap: synced=%v", synced)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
select {
|
|
|
|
case c.newMapCh <- struct{}{}:
|
|
|
|
c.logf("[v1] restartMap: wrote to channel")
|
|
|
|
default:
|
|
|
|
// if channel write failed, then there was already
|
|
|
|
// an outstanding newMapCh request. One is enough,
|
|
|
|
// since it'll always use the latest endpoints.
|
|
|
|
c.logf("[v1] restartMap: channel was full")
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2023-08-10 02:56:43 +00:00
|
|
|
c.updateControl()
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) authRoutine() {
|
2020-02-05 22:16:58 +00:00
|
|
|
defer close(c.authDone)
|
2020-08-09 04:03:20 +00:00
|
|
|
bo := backoff.NewBackoff("authRoutine", c.logf, 30*time.Second)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
c.mu.Lock()
|
|
|
|
goal := c.loginGoal
|
|
|
|
ctx := c.authCtx
|
2020-12-14 18:10:01 +00:00
|
|
|
if goal != nil {
|
2022-02-12 16:05:24 +00:00
|
|
|
c.logf("[v1] authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn)
|
2020-12-14 18:10:01 +00:00
|
|
|
} else {
|
2022-02-12 16:05:24 +00:00
|
|
|
c.logf("[v1] authRoutine: %s; goal=nil paused=%v", c.state, c.paused)
|
2020-12-14 18:10:01 +00:00
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-c.quit:
|
2020-12-21 18:58:06 +00:00
|
|
|
c.logf("[v1] authRoutine: quit")
|
2020-02-05 22:16:58 +00:00
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
report := func(err error, msg string) {
|
2020-12-21 18:58:06 +00:00
|
|
|
c.logf("[v1] %s: %v", msg, err)
|
2020-02-05 22:16:58 +00:00
|
|
|
// don't send status updates for context errors,
|
|
|
|
// since context cancelation is always on purpose.
|
|
|
|
if ctx.Err() == nil {
|
2020-10-13 22:03:56 +00:00
|
|
|
c.sendStatus("authRoutine-report", err, "", nil)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if goal == nil {
|
2022-06-03 17:52:07 +00:00
|
|
|
health.SetAuthRoutineInError(nil)
|
2020-12-14 18:10:01 +00:00
|
|
|
// Wait for user to Login or Logout.
|
|
|
|
<-ctx.Done()
|
2020-12-21 18:58:06 +00:00
|
|
|
c.logf("[v1] authRoutine: context done.")
|
2020-12-14 18:10:01 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !goal.wantLoggedIn {
|
2022-06-03 17:52:07 +00:00
|
|
|
health.SetAuthRoutineInError(nil)
|
2020-07-31 16:39:45 +00:00
|
|
|
err := c.direct.TryLogout(ctx)
|
2021-04-08 04:06:31 +00:00
|
|
|
goal.sendLogoutError(err)
|
2020-02-05 22:16:58 +00:00
|
|
|
if err != nil {
|
|
|
|
report(err, "TryLogout")
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// success
|
|
|
|
c.mu.Lock()
|
|
|
|
c.loggedIn = false
|
|
|
|
c.loginGoal = nil
|
2020-05-27 18:46:09 +00:00
|
|
|
c.state = StateNotAuthenticated
|
2020-02-05 22:16:58 +00:00
|
|
|
c.synced = false
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-10-13 22:03:56 +00:00
|
|
|
c.sendStatus("authRoutine-wantout", nil, "", nil)
|
2020-02-05 22:16:58 +00:00
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
} else { // ie. goal.wantLoggedIn
|
|
|
|
c.mu.Lock()
|
|
|
|
if goal.url != "" {
|
2020-05-27 18:46:09 +00:00
|
|
|
c.state = StateURLVisitRequired
|
2020-02-05 22:16:58 +00:00
|
|
|
} else {
|
2020-05-27 18:46:09 +00:00
|
|
|
c.state = StateAuthenticating
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
var url string
|
|
|
|
var err error
|
|
|
|
var f string
|
|
|
|
if goal.url != "" {
|
|
|
|
url, err = c.direct.WaitLoginURL(ctx, goal.url)
|
|
|
|
f = "WaitLoginURL"
|
|
|
|
} else {
|
|
|
|
url, err = c.direct.TryLogin(ctx, goal.token, goal.flags)
|
|
|
|
f = "TryLogin"
|
|
|
|
}
|
|
|
|
if err != nil {
|
2022-06-03 17:52:07 +00:00
|
|
|
health.SetAuthRoutineInError(err)
|
2020-02-05 22:16:58 +00:00
|
|
|
report(err, f)
|
|
|
|
bo.BackOff(ctx, err)
|
|
|
|
continue
|
2021-04-08 04:06:31 +00:00
|
|
|
}
|
|
|
|
if url != "" {
|
2021-11-23 22:18:03 +00:00
|
|
|
// goal.url ought to be empty here.
|
|
|
|
// However, not all control servers get this right,
|
|
|
|
// and logging about it here just generates noise.
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
2020-07-15 17:00:20 +00:00
|
|
|
c.loginGoal = &LoginGoal{
|
|
|
|
wantLoggedIn: true,
|
|
|
|
flags: LoginDefault,
|
|
|
|
url: url,
|
|
|
|
}
|
2020-05-27 18:46:09 +00:00
|
|
|
c.state = StateURLVisitRequired
|
2020-02-05 22:16:58 +00:00
|
|
|
c.synced = false
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-10-13 22:03:56 +00:00
|
|
|
c.sendStatus("authRoutine-url", err, url, nil)
|
2023-03-14 23:45:04 +00:00
|
|
|
if goal.url == url {
|
|
|
|
// The server sent us the same URL we already tried,
|
|
|
|
// backoff to avoid a busy loop.
|
|
|
|
bo.BackOff(ctx, errors.New("login URL not changing"))
|
|
|
|
} else {
|
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// success
|
2022-06-03 17:52:07 +00:00
|
|
|
health.SetAuthRoutineInError(nil)
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
c.loggedIn = true
|
|
|
|
c.loginGoal = nil
|
2020-05-27 18:46:09 +00:00
|
|
|
c.state = StateAuthenticated
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-10-13 22:03:56 +00:00
|
|
|
c.sendStatus("authRoutine-success", nil, "", nil)
|
2023-08-10 02:56:43 +00:00
|
|
|
c.restartMap()
|
2020-02-05 22:16:58 +00:00
|
|
|
bo.BackOff(ctx, nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-29 05:19:23 +00:00
|
|
|
// ExpiryForTests returns the credential expiration time, or the zero value if
|
|
|
|
// the expiration time isn't known. It's used in tests only.
|
|
|
|
func (c *Auto) ExpiryForTests() time.Time {
|
2020-05-27 18:46:09 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
return c.expiry
|
|
|
|
}
|
|
|
|
|
2023-08-29 05:19:23 +00:00
|
|
|
// DirectForTest returns the underlying direct client object.
|
|
|
|
// It's used in tests only.
|
|
|
|
func (c *Auto) DirectForTest() *Direct {
|
2020-05-27 18:46:09 +00:00
|
|
|
return c.direct
|
|
|
|
}
|
|
|
|
|
2020-07-31 16:39:45 +00:00
|
|
|
// unpausedChanLocked returns a new channel that is closed when the
|
2021-04-30 03:27:00 +00:00
|
|
|
// current Auto pause is unpaused.
|
2020-07-31 16:39:45 +00:00
|
|
|
//
|
|
|
|
// c.mu must be held
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) unpausedChanLocked() <-chan struct{} {
|
2020-07-31 16:39:45 +00:00
|
|
|
unpaused := make(chan struct{})
|
|
|
|
c.unpauseWaiters = append(c.unpauseWaiters, unpaused)
|
|
|
|
return unpaused
|
|
|
|
}
|
|
|
|
|
2023-08-12 15:18:10 +00:00
|
|
|
// mapRoutineState is the state of Auto.mapRoutine while it's running.
|
|
|
|
type mapRoutineState struct {
|
|
|
|
c *Auto
|
|
|
|
bo *backoff.Backoff
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mrs mapRoutineState) UpdateFullNetmap(nm *netmap.NetworkMap) {
|
|
|
|
c := mrs.c
|
|
|
|
health.SetInPollNetMap(true)
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
ctx := c.mapCtx
|
|
|
|
c.synced = true
|
|
|
|
if c.loggedIn {
|
|
|
|
c.state = StateSynchronized
|
|
|
|
}
|
2023-08-29 05:19:23 +00:00
|
|
|
c.expiry = nm.Expiry
|
2023-08-12 15:18:10 +00:00
|
|
|
stillAuthed := c.loggedIn
|
|
|
|
c.logf("[v1] mapRoutine: netmap received: %s", c.state)
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
if stillAuthed {
|
|
|
|
c.sendStatus("mapRoutine-got-netmap", nil, "", nm)
|
|
|
|
}
|
|
|
|
// Reset the backoff timer if we got a netmap.
|
|
|
|
mrs.bo.BackOff(ctx, nil)
|
|
|
|
}
|
|
|
|
|
2023-08-10 02:56:43 +00:00
|
|
|
// mapRoutine is responsible for keeping a read-only streaming connection to the
|
|
|
|
// control server, and keeping the netmap up to date.
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) mapRoutine() {
|
2020-02-05 22:16:58 +00:00
|
|
|
defer close(c.mapDone)
|
2023-08-12 15:18:10 +00:00
|
|
|
mrs := &mapRoutineState{
|
|
|
|
c: c,
|
|
|
|
bo: backoff.NewBackoff("mapRoutine", c.logf, 30*time.Second),
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
for {
|
2023-08-10 02:56:43 +00:00
|
|
|
if err := c.waitUnpause("mapRoutine"); err != nil {
|
|
|
|
c.logf("mapRoutine: exiting")
|
|
|
|
return
|
2020-07-31 16:39:45 +00:00
|
|
|
}
|
2023-08-10 02:56:43 +00:00
|
|
|
|
|
|
|
c.mu.Lock()
|
2022-02-12 16:05:24 +00:00
|
|
|
c.logf("[v1] mapRoutine: %s", c.state)
|
2020-02-05 22:16:58 +00:00
|
|
|
loggedIn := c.loggedIn
|
|
|
|
ctx := c.mapCtx
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-c.quit:
|
2020-04-11 15:35:34 +00:00
|
|
|
c.logf("mapRoutine: quit")
|
2020-02-05 22:16:58 +00:00
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
report := func(err error, msg string) {
|
2020-12-21 18:58:06 +00:00
|
|
|
c.logf("[v1] %s: %v", msg, err)
|
2021-10-26 17:19:35 +00:00
|
|
|
err = fmt.Errorf("%s: %w", msg, err)
|
2020-02-05 22:16:58 +00:00
|
|
|
// don't send status updates for context errors,
|
|
|
|
// since context cancelation is always on purpose.
|
|
|
|
if ctx.Err() == nil {
|
|
|
|
c.sendStatus("mapRoutine1", err, "", nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !loggedIn {
|
|
|
|
// Wait for something interesting to happen
|
|
|
|
c.mu.Lock()
|
|
|
|
c.synced = false
|
|
|
|
// c.state is set by authRoutine()
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2022-02-12 16:05:24 +00:00
|
|
|
c.logf("[v1] mapRoutine: context done.")
|
2020-02-05 22:16:58 +00:00
|
|
|
case <-c.newMapCh:
|
2022-02-12 16:05:24 +00:00
|
|
|
c.logf("[v1] mapRoutine: new map needed while idle.")
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-02-25 05:29:51 +00:00
|
|
|
health.SetInPollNetMap(false)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2023-08-12 15:18:10 +00:00
|
|
|
err := c.direct.PollNetMap(ctx, mrs)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2021-02-25 05:29:51 +00:00
|
|
|
health.SetInPollNetMap(false)
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
c.synced = false
|
2020-05-27 18:46:09 +00:00
|
|
|
if c.state == StateSynchronized {
|
|
|
|
c.state = StateAuthenticated
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2020-07-31 16:39:45 +00:00
|
|
|
paused := c.paused
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-07-31 16:39:45 +00:00
|
|
|
if paused {
|
2023-08-12 15:18:10 +00:00
|
|
|
mrs.bo.BackOff(ctx, nil)
|
2020-07-31 16:39:45 +00:00
|
|
|
c.logf("mapRoutine: paused")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-08-12 15:18:10 +00:00
|
|
|
report(err, "PollNetMap")
|
|
|
|
mrs.bo.BackOff(ctx, err)
|
|
|
|
continue
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) AuthCantContinue() bool {
|
2021-04-21 19:57:48 +00:00
|
|
|
if c == nil {
|
|
|
|
return true
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
return !c.loggedIn && (c.loginGoal == nil || c.loginGoal.url != "")
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) SetHostinfo(hi *tailcfg.Hostinfo) {
|
2020-02-25 18:04:20 +00:00
|
|
|
if hi == nil {
|
|
|
|
panic("nil Hostinfo")
|
|
|
|
}
|
2020-04-02 00:18:39 +00:00
|
|
|
if !c.direct.SetHostinfo(hi) {
|
2020-07-28 22:13:34 +00:00
|
|
|
// No changes. Don't log.
|
2020-04-02 00:18:39 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
// Send new Hostinfo to server
|
2023-08-10 02:56:43 +00:00
|
|
|
c.updateControl()
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) SetNetInfo(ni *tailcfg.NetInfo) {
|
2020-03-04 06:21:56 +00:00
|
|
|
if ni == nil {
|
|
|
|
panic("nil NetInfo")
|
|
|
|
}
|
2020-04-02 00:18:39 +00:00
|
|
|
if !c.direct.SetNetInfo(ni) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-03 22:07:30 +00:00
|
|
|
// Send new NetInfo to server
|
2023-08-10 02:56:43 +00:00
|
|
|
c.updateControl()
|
2020-03-04 06:21:56 +00:00
|
|
|
}
|
|
|
|
|
2022-10-27 20:40:31 +00:00
|
|
|
// SetTKAHead updates the TKA head hash that map-request infrastructure sends.
|
|
|
|
func (c *Auto) SetTKAHead(headHash string) {
|
2023-08-10 02:56:43 +00:00
|
|
|
if !c.direct.SetTKAHead(headHash) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send new TKAHead to server
|
|
|
|
c.updateControl()
|
2022-10-27 20:40:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkMap) {
|
2020-02-05 22:16:58 +00:00
|
|
|
c.mu.Lock()
|
2022-06-20 01:14:45 +00:00
|
|
|
if c.closed {
|
|
|
|
c.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
state := c.state
|
|
|
|
loggedIn := c.loggedIn
|
|
|
|
synced := c.synced
|
|
|
|
c.inSendStatus++
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-12-21 18:58:06 +00:00
|
|
|
c.logf("[v1] sendStatus: %s: %v", who, state)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2022-11-10 14:43:59 +00:00
|
|
|
var p *persist.PersistView
|
ipnlocal: don't assume NeedsLogin immediately after StartLogout().
Previously, there was no server round trip required to log out, so when
you asked ipnlocal to Logout(), it could clear the netmap immediately
and switch to NeedsLogin state.
In v1.8, we added a true Logout operation. ipn.Logout() would trigger
an async cc.StartLogout() and *also* immediately switch to NeedsLogin.
Unfortunately, some frontends would see NeedsLogin and immediately
trigger a new StartInteractiveLogin() operation, before the
controlclient auth state machine actually acted on the Logout command,
thus accidentally invalidating the entire logout operation, retaining
the netmap, and violating the user's expectations.
Instead, add a new LogoutFinished signal from controlclient
(paralleling LoginFinished) and, upon starting a logout, don't update
the ipn state machine until it's received.
Updates: #1918 (BUG-2)
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2021-05-20 06:46:57 +00:00
|
|
|
var loginFin, logoutFin *empty.Message
|
2020-05-27 18:46:09 +00:00
|
|
|
if state == StateAuthenticated {
|
ipnlocal: don't assume NeedsLogin immediately after StartLogout().
Previously, there was no server round trip required to log out, so when
you asked ipnlocal to Logout(), it could clear the netmap immediately
and switch to NeedsLogin state.
In v1.8, we added a true Logout operation. ipn.Logout() would trigger
an async cc.StartLogout() and *also* immediately switch to NeedsLogin.
Unfortunately, some frontends would see NeedsLogin and immediately
trigger a new StartInteractiveLogin() operation, before the
controlclient auth state machine actually acted on the Logout command,
thus accidentally invalidating the entire logout operation, retaining
the netmap, and violating the user's expectations.
Instead, add a new LogoutFinished signal from controlclient
(paralleling LoginFinished) and, upon starting a logout, don't update
the ipn state machine until it's received.
Updates: #1918 (BUG-2)
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2021-05-20 06:46:57 +00:00
|
|
|
loginFin = new(empty.Message)
|
|
|
|
}
|
|
|
|
if state == StateNotAuthenticated {
|
|
|
|
logoutFin = new(empty.Message)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
if nm != nil && loggedIn && synced {
|
2023-08-04 05:27:02 +00:00
|
|
|
p = ptr.To(c.direct.GetPersist())
|
2020-02-05 22:16:58 +00:00
|
|
|
} else {
|
|
|
|
// don't send netmap status, as it's misleading when we're
|
|
|
|
// not logged in.
|
|
|
|
nm = nil
|
|
|
|
}
|
|
|
|
new := Status{
|
ipnlocal: don't assume NeedsLogin immediately after StartLogout().
Previously, there was no server round trip required to log out, so when
you asked ipnlocal to Logout(), it could clear the netmap immediately
and switch to NeedsLogin state.
In v1.8, we added a true Logout operation. ipn.Logout() would trigger
an async cc.StartLogout() and *also* immediately switch to NeedsLogin.
Unfortunately, some frontends would see NeedsLogin and immediately
trigger a new StartInteractiveLogin() operation, before the
controlclient auth state machine actually acted on the Logout command,
thus accidentally invalidating the entire logout operation, retaining
the netmap, and violating the user's expectations.
Instead, add a new LogoutFinished signal from controlclient
(paralleling LoginFinished) and, upon starting a logout, don't update
the ipn state machine until it's received.
Updates: #1918 (BUG-2)
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2021-05-20 06:46:57 +00:00
|
|
|
LoginFinished: loginFin,
|
|
|
|
LogoutFinished: logoutFin,
|
|
|
|
URL: url,
|
|
|
|
Persist: p,
|
|
|
|
NetMap: nm,
|
|
|
|
State: state,
|
2021-10-26 17:19:35 +00:00
|
|
|
Err: err,
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2023-08-28 22:27:39 +00:00
|
|
|
c.observer.SetControlClientStatus(new)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
c.inSendStatus--
|
|
|
|
c.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) Login(t *tailcfg.Oauth2Token, flags LoginFlags) {
|
2020-04-11 15:35:34 +00:00
|
|
|
c.logf("client.Login(%v, %v)", t != nil, flags)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
c.loginGoal = &LoginGoal{
|
|
|
|
wantLoggedIn: true,
|
|
|
|
token: t,
|
|
|
|
flags: flags,
|
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
c.cancelAuth()
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) StartLogout() {
|
2021-04-08 04:06:31 +00:00
|
|
|
c.logf("client.StartLogout()")
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
c.loginGoal = &LoginGoal{
|
|
|
|
wantLoggedIn: false,
|
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
2021-04-08 04:06:31 +00:00
|
|
|
c.cancelAuth()
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) Logout(ctx context.Context) error {
|
2021-04-08 04:06:31 +00:00
|
|
|
c.logf("client.Logout()")
|
|
|
|
|
|
|
|
errc := make(chan error, 1)
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
c.loginGoal = &LoginGoal{
|
|
|
|
wantLoggedIn: false,
|
|
|
|
loggedOutResult: errc,
|
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
2020-02-05 22:16:58 +00:00
|
|
|
c.cancelAuth()
|
2021-04-08 04:06:31 +00:00
|
|
|
|
2023-08-04 23:29:44 +00:00
|
|
|
timer, timerChannel := c.clock.NewTimer(10 * time.Second)
|
2021-04-08 04:06:31 +00:00
|
|
|
defer timer.Stop()
|
|
|
|
select {
|
|
|
|
case err := <-errc:
|
|
|
|
return err
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
2023-08-04 23:29:44 +00:00
|
|
|
case <-timerChannel:
|
2021-04-08 04:06:31 +00:00
|
|
|
return context.DeadlineExceeded
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2022-03-09 22:42:42 +00:00
|
|
|
func (c *Auto) SetExpirySooner(ctx context.Context, expiry time.Time) error {
|
|
|
|
return c.direct.SetExpirySooner(ctx, expiry)
|
|
|
|
}
|
|
|
|
|
2021-03-31 15:25:39 +00:00
|
|
|
// UpdateEndpoints sets the client's discovered endpoints and sends
|
|
|
|
// them to the control server if they've changed.
|
|
|
|
//
|
|
|
|
// It does not retain the provided slice.
|
2022-06-19 23:31:54 +00:00
|
|
|
func (c *Auto) UpdateEndpoints(endpoints []tailcfg.Endpoint) {
|
|
|
|
changed := c.direct.SetEndpoints(endpoints)
|
2020-02-14 17:28:29 +00:00
|
|
|
if changed {
|
2023-08-10 02:56:43 +00:00
|
|
|
c.updateControl()
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) Shutdown() {
|
2020-04-11 15:35:34 +00:00
|
|
|
c.logf("client.Shutdown()")
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
inSendStatus := c.inSendStatus
|
|
|
|
closed := c.closed
|
2022-03-08 19:45:48 +00:00
|
|
|
direct := c.direct
|
2020-02-05 22:16:58 +00:00
|
|
|
if !closed {
|
|
|
|
c.closed = true
|
|
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
2020-04-11 15:35:34 +00:00
|
|
|
c.logf("client.Shutdown: inSendStatus=%v", inSendStatus)
|
2020-02-05 22:16:58 +00:00
|
|
|
if !closed {
|
2021-02-18 16:58:13 +00:00
|
|
|
c.unregisterHealthWatch()
|
2020-02-05 22:16:58 +00:00
|
|
|
close(c.quit)
|
|
|
|
c.cancelAuth()
|
|
|
|
<-c.authDone
|
2023-08-10 02:56:43 +00:00
|
|
|
c.cancelMap()
|
2020-02-05 22:16:58 +00:00
|
|
|
<-c.mapDone
|
2023-08-10 02:56:43 +00:00
|
|
|
<-c.updateDone
|
2022-03-09 05:04:42 +00:00
|
|
|
if direct != nil {
|
|
|
|
direct.Close()
|
|
|
|
}
|
2020-04-11 15:35:34 +00:00
|
|
|
c.logf("Client.Shutdown done.")
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-27 18:46:09 +00:00
|
|
|
|
|
|
|
// NodePublicKey returns the node public key currently in use. This is
|
|
|
|
// used exclusively in tests.
|
2021-10-28 21:22:51 +00:00
|
|
|
func (c *Auto) TestOnlyNodePublicKey() key.NodePublic {
|
2020-05-27 18:46:09 +00:00
|
|
|
priv := c.direct.GetPersist()
|
2022-11-09 05:58:10 +00:00
|
|
|
return priv.PrivateNodeKey().Public()
|
2020-05-27 18:46:09 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) TestOnlySetAuthKey(authkey string) {
|
2020-05-27 18:46:09 +00:00
|
|
|
c.direct.mu.Lock()
|
|
|
|
defer c.direct.mu.Unlock()
|
|
|
|
c.direct.authKey = authkey
|
|
|
|
}
|
|
|
|
|
2021-04-30 03:27:00 +00:00
|
|
|
func (c *Auto) TestOnlyTimeNow() time.Time {
|
2023-08-04 23:29:44 +00:00
|
|
|
return c.clock.Now()
|
2020-05-27 18:46:09 +00:00
|
|
|
}
|
2021-06-07 23:03:16 +00:00
|
|
|
|
|
|
|
// SetDNS sends the SetDNSRequest request to the control plane server,
|
|
|
|
// requesting a DNS record be created or updated.
|
|
|
|
func (c *Auto) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
|
|
|
|
return c.direct.SetDNS(ctx, req)
|
|
|
|
}
|
2022-03-10 18:28:42 +00:00
|
|
|
|
|
|
|
func (c *Auto) DoNoiseRequest(req *http.Request) (*http.Response, error) {
|
|
|
|
return c.direct.DoNoiseRequest(req)
|
|
|
|
}
|
2022-11-02 03:37:13 +00:00
|
|
|
|
|
|
|
// GetSingleUseNoiseRoundTripper returns a RoundTripper that can be only be used
|
|
|
|
// once (and must be used once) to make a single HTTP request over the noise
|
|
|
|
// channel to the coordination server.
|
|
|
|
//
|
|
|
|
// In addition to the RoundTripper, it returns the HTTP/2 channel's early noise
|
|
|
|
// payload, if any.
|
|
|
|
func (c *Auto) GetSingleUseNoiseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error) {
|
|
|
|
return c.direct.GetSingleUseNoiseRoundTripper(ctx)
|
|
|
|
}
|