|
|
@@ -230,7 +230,8 @@ type LocalBackend struct {
|
|
|
ccGen clientGen // function for producing controlclient; lazily populated
|
|
|
sshServer SSHServer // or nil, initialized lazily.
|
|
|
appConnector *appc.AppConnector // or nil, initialized when configured.
|
|
|
- notify func(ipn.Notify)
|
|
|
+ // notifyCancel cancels notifications to the current SetNotifyCallback.
|
|
|
+ notifyCancel context.CancelFunc
|
|
|
cc controlclient.Client
|
|
|
ccAuto *controlclient.Auto // if cc is of type *controlclient.Auto
|
|
|
machinePrivKey key.MachinePrivate
|
|
|
@@ -710,6 +711,9 @@ func (b *LocalBackend) Shutdown() {
|
|
|
b.debugSink.Close()
|
|
|
b.debugSink = nil
|
|
|
}
|
|
|
+ if b.notifyCancel != nil {
|
|
|
+ b.notifyCancel()
|
|
|
+ }
|
|
|
b.mu.Unlock()
|
|
|
b.webClientShutdown()
|
|
|
|
|
|
@@ -1557,10 +1561,26 @@ func endpointsEqual(x, y []tailcfg.Endpoint) bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
+// SetNotifyCallback sets the function to call when the backend has something to
|
|
|
+// notify the frontend about. Only one callback can be set at a time, so calling
|
|
|
+// this function will replace the previous callback.
|
|
|
func (b *LocalBackend) SetNotifyCallback(notify func(ipn.Notify)) {
|
|
|
+ ctx, cancel := context.WithCancel(b.ctx)
|
|
|
b.mu.Lock()
|
|
|
- defer b.mu.Unlock()
|
|
|
- b.notify = notify
|
|
|
+ prevCancel := b.notifyCancel
|
|
|
+ b.notifyCancel = cancel
|
|
|
+ b.mu.Unlock()
|
|
|
+ if prevCancel != nil {
|
|
|
+ prevCancel()
|
|
|
+ }
|
|
|
+
|
|
|
+ var wg sync.WaitGroup
|
|
|
+ wg.Add(1)
|
|
|
+ go b.WatchNotifications(ctx, 0, wg.Done, func(n *ipn.Notify) bool {
|
|
|
+ notify(*n)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ wg.Wait()
|
|
|
}
|
|
|
|
|
|
// SetHTTPTestClient sets an alternate HTTP client to use with
|
|
|
@@ -1806,7 +1826,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|
|
tkaHead = string(head)
|
|
|
}
|
|
|
confWantRunning := b.conf != nil && wantRunning
|
|
|
- unlock.UnlockEarly()
|
|
|
|
|
|
if endpoints != nil {
|
|
|
cc.UpdateEndpoints(endpoints)
|
|
|
@@ -1815,16 +1834,23 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|
|
|
|
|
blid := b.backendLogID.String()
|
|
|
b.logf("Backend: logs: be:%v fe:%v", blid, opts.FrontendLogID)
|
|
|
- b.send(ipn.Notify{BackendLogID: &blid})
|
|
|
- b.send(ipn.Notify{Prefs: &prefs})
|
|
|
+ b.sendLocked(ipn.Notify{
|
|
|
+ BackendLogID: &blid,
|
|
|
+ Prefs: &prefs,
|
|
|
+ })
|
|
|
|
|
|
- if !loggedOut && (b.hasNodeKey() || confWantRunning) {
|
|
|
- // Even if !WantRunning, we should verify our key, if there
|
|
|
- // is one. If you want tailscaled to be completely idle,
|
|
|
- // use logout instead.
|
|
|
+ if !loggedOut && (b.hasNodeKeyLocked() || confWantRunning) {
|
|
|
+ // If we know that we're either logged in or meant to be
|
|
|
+ // running, tell the controlclient that it should also assume
|
|
|
+ // that we need to be logged in.
|
|
|
+ //
|
|
|
+ // Without this, the state machine transitions to "NeedsLogin" implying
|
|
|
+ // that user interaction is required, which is not the case and can
|
|
|
+ // regress tsnet.Server restarts.
|
|
|
cc.Login(nil, controlclient.LoginDefault)
|
|
|
}
|
|
|
- b.stateMachine()
|
|
|
+ b.stateMachineLockedOnEntry(unlock)
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -2390,6 +2416,13 @@ func (b *LocalBackend) DebugPickNewDERP() error {
|
|
|
//
|
|
|
// b.mu must not be held.
|
|
|
func (b *LocalBackend) send(n ipn.Notify) {
|
|
|
+ b.mu.Lock()
|
|
|
+ defer b.mu.Unlock()
|
|
|
+ b.sendLocked(n)
|
|
|
+}
|
|
|
+
|
|
|
+// sendLocked is like send, but assumes b.mu is already held.
|
|
|
+func (b *LocalBackend) sendLocked(n ipn.Notify) {
|
|
|
if n.Prefs != nil {
|
|
|
n.Prefs = ptr.To(stripKeysFromPrefs(*n.Prefs))
|
|
|
}
|
|
|
@@ -2397,8 +2430,6 @@ func (b *LocalBackend) send(n ipn.Notify) {
|
|
|
n.Version = version.Long()
|
|
|
}
|
|
|
|
|
|
- b.mu.Lock()
|
|
|
- notifyFunc := b.notify
|
|
|
apiSrv := b.peerAPIServer
|
|
|
if mayDeref(apiSrv).taildrop.HasFilesWaiting() {
|
|
|
n.FilesWaiting = &empty.Message{}
|
|
|
@@ -2411,12 +2442,6 @@ func (b *LocalBackend) send(n ipn.Notify) {
|
|
|
// Drop the notification if the channel is full.
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- b.mu.Unlock()
|
|
|
-
|
|
|
- if notifyFunc != nil {
|
|
|
- notifyFunc(n)
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
func (b *LocalBackend) sendFileNotify() {
|
|
|
@@ -2426,9 +2451,8 @@ func (b *LocalBackend) sendFileNotify() {
|
|
|
for _, wakeWaiter := range b.fileWaiters {
|
|
|
wakeWaiter()
|
|
|
}
|
|
|
- notifyFunc := b.notify
|
|
|
apiSrv := b.peerAPIServer
|
|
|
- if notifyFunc == nil || apiSrv == nil {
|
|
|
+ if apiSrv == nil {
|
|
|
b.mu.Unlock()
|
|
|
return
|
|
|
}
|
|
|
@@ -4376,14 +4400,6 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State, unlock unlock
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// hasNodeKey reports whether a non-zero node key is present in the current
|
|
|
-// prefs.
|
|
|
-func (b *LocalBackend) hasNodeKey() bool {
|
|
|
- b.mu.Lock()
|
|
|
- defer b.mu.Unlock()
|
|
|
- return b.hasNodeKeyLocked()
|
|
|
-}
|
|
|
-
|
|
|
func (b *LocalBackend) hasNodeKeyLocked() bool {
|
|
|
// we can't use b.Prefs(), because it strips the keys, oops!
|
|
|
p := b.pm.CurrentPrefs()
|
|
|
@@ -4481,6 +4497,12 @@ func (b *LocalBackend) nextStateLocked() ipn.State {
|
|
|
// Or maybe just call the state machine from fewer places.
|
|
|
func (b *LocalBackend) stateMachine() {
|
|
|
unlock := b.lockAndGetUnlock()
|
|
|
+ b.stateMachineLockedOnEntry(unlock)
|
|
|
+}
|
|
|
+
|
|
|
+// stateMachineLockedOnEntry is like stateMachine but requires b.mu be held to
|
|
|
+// call it, but it unlocks b.mu when done (via unlock, a once func).
|
|
|
+func (b *LocalBackend) stateMachineLockedOnEntry(unlock unlockOnce) {
|
|
|
b.enterStateLockedOnEntry(b.nextStateLocked(), unlock)
|
|
|
}
|
|
|
|