diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index c4a4b5c77..3947f4ff5 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -103,7 +103,16 @@ func Goroutines(ctx context.Context) ([]byte, error) { // Status returns the Tailscale daemon's status. func Status(ctx context.Context) (*ipnstate.Status, error) { - req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status", nil) + return status(ctx, "") +} + +// StatusWithPeers returns the Tailscale daemon's status, without the peer info. +func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) { + return status(ctx, "?peers=false") +} + +func status(ctx context.Context, queryString string) (*ipnstate.Status, error) { + req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status"+queryString, nil) if err != nil { return nil, err } diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index e9a39de2b..2c6f97a3b 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -11,7 +11,7 @@ import ( "log" "github.com/peterbourgon/ff/v2/ffcli" - "tailscale.com/ipn" + "tailscale.com/client/tailscale" "tailscale.com/version" ) @@ -42,29 +42,10 @@ func runVersion(ctx context.Context, args []string) error { fmt.Printf("Client: %s\n", version.String()) - c, bc, ctx, cancel := connect(ctx) - defer cancel() - - bc.AllowVersionSkew = true - - done := make(chan struct{}) - - bc.SetNotifyCallback(func(n ipn.Notify) { - if n.ErrMessage != nil { - log.Fatal(*n.ErrMessage) - } - if n.Engine != nil { - fmt.Printf("Daemon: %s\n", n.Version) - close(done) - } - }) - go pump(ctx, bc, c) - - bc.RequestEngineStatus() - select { - case <-done: - return nil - case <-ctx.Done(): - return ctx.Err() + st, err := tailscale.StatusWithoutPeers(ctx) + if err != nil { + return err } + fmt.Printf("Daemon: %s\n", st.Version) + return nil } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 377f838ce..3c4b75361 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -218,52 +218,76 @@ func (b *LocalBackend) Status() *ipnstate.Status { return sb.Status() } +// StatusWithoutPeers is like Status but omits any details +// of peers. +func (b *LocalBackend) StatusWithoutPeers() *ipnstate.Status { + sb := new(ipnstate.StatusBuilder) + b.updateStatus(sb, nil) + return sb.Status() +} + // UpdateStatus implements ipnstate.StatusUpdater. func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) { b.e.UpdateStatus(sb) + b.updateStatus(sb, b.populatePeerStatusLocked) +} +// updateStatus populates sb with status. +// +// extraLocked, if non-nil, is called while b.mu is still held. +func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) { b.mu.Lock() defer b.mu.Unlock() - + sb.SetVersion(version.Long) sb.SetBackendState(b.state.String()) sb.SetAuthURL(b.authURL) - // TODO: hostinfo, and its networkinfo // TODO: EngineStatus copy (and deprecate it?) + if b.netMap != nil { sb.SetMagicDNSSuffix(b.netMap.MagicDNSSuffix()) - for id, up := range b.netMap.UserProfiles { - sb.AddUser(id, up) + } + + if extraLocked != nil { + extraLocked(sb) + } +} + +func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) { + if b.netMap == nil { + return + } + for id, up := range b.netMap.UserProfiles { + sb.AddUser(id, up) + } + for _, p := range b.netMap.Peers { + var lastSeen time.Time + if p.LastSeen != nil { + lastSeen = *p.LastSeen } - for _, p := range b.netMap.Peers { - var lastSeen time.Time - if p.LastSeen != nil { - lastSeen = *p.LastSeen - } - var tailAddr string - for _, addr := range p.Addresses { - // The peer struct currently only allows a single - // Tailscale IP address. For compatibility with the - // old display, make sure it's the IPv4 address. - if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) { - tailAddr = addr.IP.String() - break - } + var tailAddr string + for _, addr := range p.Addresses { + // The peer struct currently only allows a single + // Tailscale IP address. For compatibility with the + // old display, make sure it's the IPv4 address. + if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) { + tailAddr = addr.IP.String() + break } - sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{ - InNetworkMap: true, - UserID: p.User, - TailAddr: tailAddr, - HostName: p.Hostinfo.Hostname, - DNSName: p.Name, - OS: p.Hostinfo.OS, - KeepAlive: p.KeepAlive, - Created: p.Created, - LastSeen: lastSeen, - ShareeNode: p.Hostinfo.ShareeNode, - ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID, - }) } + sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{ + InNetworkMap: true, + UserID: p.User, + TailAddr: tailAddr, + HostName: p.Hostinfo.Hostname, + DNSName: p.Name, + OS: p.Hostinfo.OS, + KeepAlive: p.KeepAlive, + Created: p.Created, + LastSeen: lastSeen, + ShareeNode: p.Hostinfo.ShareeNode, + ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID, + }) } } diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index b89f7bb08..a419067ad 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -26,7 +26,14 @@ import ( // Status represents the entire state of the IPN network. type Status struct { + // Version is the daemon's long version (see version.Long). + Version string + + // BackendState is an ipn.State string value: + // "NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped", + // "Starting", "Running". BackendState string + AuthURL string // current URL provided by control to authorize client TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node Self *PeerStatus @@ -105,6 +112,12 @@ type StatusBuilder struct { st Status } +func (sb *StatusBuilder) SetVersion(v string) { + sb.mu.Lock() + defer sb.mu.Unlock() + sb.st.Version = v +} + func (sb *StatusBuilder) SetBackendState(v string) { sb.mu.Lock() defer sb.mu.Unlock() diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index d9ad7bf13..169f18ba4 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -10,9 +10,11 @@ import ( "io" "net/http" "runtime" + "strconv" "inet.af/netaddr" "tailscale.com/ipn/ipnlocal" + "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" ) @@ -118,7 +120,24 @@ func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) { return } w.Header().Set("Content-Type", "application/json") + var st *ipnstate.Status + if defBool(r.FormValue("peers"), true) { + st = h.b.Status() + } else { + st = h.b.StatusWithoutPeers() + } e := json.NewEncoder(w) e.SetIndent("", "\t") - e.Encode(h.b.Status()) + e.Encode(st) +} + +func defBool(a string, def bool) bool { + if a == "" { + return def + } + v, err := strconv.ParseBool(a) + if err != nil { + return def + } + return v }