diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index 9ee2dcc50..fc6d28182 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -133,6 +133,7 @@ var debugCmd = &ffcli.Command{ FlagSet: (func() *flag.FlagSet { fs := newFlagSet("watch-ipn") fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages") + fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status") return fs })(), }, @@ -318,11 +319,16 @@ func runPrefs(ctx context.Context, args []string) error { } var watchIPNArgs struct { - netmap bool + netmap bool + initial bool } func runWatchIPN(ctx context.Context, args []string) error { - watcher, err := localClient.WatchIPNBus(ctx, 0) + var mask ipn.NotifyWatchOpt + if watchIPNArgs.initial { + mask = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap + } + watcher, err := localClient.WatchIPNBus(ctx, mask) if err != nil { return err } diff --git a/ipn/backend.go b/ipn/backend.go index 4aaf426d5..7b8dfff0d 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -61,6 +61,10 @@ const ( // client either regularly or when they change, without having to ask for // each one via RequestEngineStatus. NotifyWatchEngineUpdates NotifyWatchOpt = 1 << iota + + NotifyInitialState // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL + NotifyInitialPrefs // if set, the first Notify message (sent immediately) will contain the current Prefs + NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap ) // Notify is a communication from a backend (e.g. tailscaled) to a frontend diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 938a54b1a..55bbf7649 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -59,6 +59,7 @@ import ( "tailscale.com/types/netmap" "tailscale.com/types/persist" "tailscale.com/types/preftype" + "tailscale.com/types/ptr" "tailscale.com/types/views" "tailscale.com/util/deephash" "tailscale.com/util/dnsname" @@ -534,6 +535,10 @@ func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView { func (b *LocalBackend) Prefs() ipn.PrefsView { b.mu.Lock() defer b.mu.Unlock() + return b.prefsLocked() +} + +func (b *LocalBackend) prefsLocked() ipn.PrefsView { return stripKeysFromPrefs(b.pm.CurrentPrefs()) } @@ -1736,15 +1741,41 @@ func (b *LocalBackend) readPoller() { func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, fn func(roNotify *ipn.Notify) (keepGoing bool)) { ch := make(chan *ipn.Notify, 128) + var ini *ipn.Notify + b.mu.Lock() + const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap + if mask&initialBits != 0 { + ini = &ipn.Notify{} + if mask&ipn.NotifyInitialState != 0 { + ini.State = ptr.To(b.state) + if b.state == ipn.NeedsLogin { + ini.BrowseToURL = ptr.To(b.authURLSticky) + } + } + if mask&ipn.NotifyInitialPrefs != 0 { + ini.Prefs = ptr.To(b.prefsLocked()) + } + if mask&ipn.NotifyInitialNetMap != 0 { + ini.NetMap = b.netMap + } + } + handle := b.notifyWatchers.Add(ch) b.mu.Unlock() + defer func() { b.mu.Lock() delete(b.notifyWatchers, handle) b.mu.Unlock() }() + if ini != nil { + if !fn(ini) { + return + } + } + // The GUI clients want to know when peers become active or inactive. // They've historically got this information by polling for it, which is // wasteful. As a step towards making it efficient, they now set this