diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 142fb9242..48d3faa06 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -29,7 +29,22 @@ var statusCmd = &ffcli.Command{ Name: "status", ShortUsage: "status [--active] [--web] [--json]", ShortHelp: "Show state of tailscaled and its connections", - Exec: runStatus, + LongHelp: strings.TrimSpace(` + +JSON FORMAT + +Warning: this format has changed between releases and might change more +in the future. + +For a description of the fields, see the "type Status" declaration at: + +https://github.com/tailscale/tailscale/blob/main/ipn/ipnstate/ipnstate.go + +(and be sure to select branch/tag that corresponds to the version + of Tailscale you're running) + +`), + Exec: runStatus, FlagSet: (func() *flag.FlagSet { fs := newFlagSet("status") fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)") @@ -145,13 +160,19 @@ func runStatus(ctx context.Context, args []string) error { ) relay := ps.Relay anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0 + var offline string + if !ps.Online { + offline = "; offline" + } if !ps.Active { if ps.ExitNode { - f("idle; exit node") + f("idle; exit node" + offline) } else if ps.ExitNodeOption { - f("idle; offers exit node") + f("idle; offers exit node" + offline) } else if anyTraffic { - f("idle") + f("idle" + offline) + } else if !ps.Online { + f("offline") } else { f("-") } @@ -167,6 +188,9 @@ func runStatus(ctx context.Context, args []string) error { } else if ps.CurAddr != "" { f("direct %s", ps.CurAddr) } + if !ps.Online { + f("; offline") + } } if anyTraffic { f(", tx %d rx %d", ps.TxBytes, ps.RxBytes) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 29d1dea5b..4742bfbce 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -445,6 +445,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) { KeepAlive: p.KeepAlive, Created: p.Created, LastSeen: lastSeen, + Online: p.Online != nil && *p.Online, ShareeNode: p.Hostinfo.ShareeNode, ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID, ExitNodeOption: exitNodeOption, diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 2347b86ca..96dbda755 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -100,8 +100,9 @@ type PeerStatus struct { TxBytes int64 Created time.Time // time registered with tailcontrol LastWrite time.Time // time last packet sent - LastSeen time.Time // last seen to tailcontrol + LastSeen time.Time // last seen to tailcontrol; only present if offline LastHandshake time.Time // with local wireguard + Online bool // whether node is connected to the control plane KeepAlive bool ExitNode bool // true if this is the currently selected exit node. ExitNodeOption bool // true if this node can be an exit node (offered && approved) @@ -276,6 +277,9 @@ func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) { if v := st.LastWrite; !v.IsZero() { e.LastWrite = v } + if st.Online { + e.Online = true + } if st.InNetworkMap { e.InNetworkMap = true }