mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
cmd/tailscale: add tailscale status region name, last write, consistently star
There's a lot of confusion around what tailscale status shows, so make it better: show region names, last write time, and put stars around DERP too if active. Now stars are always present if activity, and always somewhere.
This commit is contained in:
parent
0ea51872c9
commit
630379a1d0
@ -14,6 +14,7 @@
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/peterbourgon/ff/v2/ffcli"
|
||||
"github.com/toqueteos/webbrowser"
|
||||
@ -127,6 +128,15 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
ps.TxBytes,
|
||||
ps.RxBytes,
|
||||
)
|
||||
// TODO: let server report this active bool instead
|
||||
active := !ps.LastWrite.IsZero() && time.Since(ps.LastWrite) < 2*time.Minute
|
||||
relay := ps.Relay
|
||||
if active && relay != "" && ps.CurAddr == "" {
|
||||
relay = "*" + relay + "*"
|
||||
} else {
|
||||
relay = " " + relay
|
||||
}
|
||||
f("%-6s", relay)
|
||||
for i, addr := range ps.Addrs {
|
||||
if i != 0 {
|
||||
f(", ")
|
||||
|
@ -49,10 +49,12 @@ type PeerStatus struct {
|
||||
// Endpoints:
|
||||
Addrs []string
|
||||
CurAddr string // one of Addrs, or unique if roaming
|
||||
Relay string // DERP region
|
||||
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Created time.Time // time registered with tailcontrol
|
||||
LastWrite time.Time // time last packet sent
|
||||
LastSeen time.Time // last seen to tailcontrol
|
||||
LastHandshake time.Time // with local wireguard
|
||||
KeepAlive bool
|
||||
@ -135,6 +137,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
|
||||
if v := st.HostName; v != "" {
|
||||
e.HostName = v
|
||||
}
|
||||
if v := st.Relay; v != "" {
|
||||
e.Relay = v
|
||||
}
|
||||
if v := st.UserID; v != 0 {
|
||||
e.UserID = v
|
||||
}
|
||||
@ -165,6 +170,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
|
||||
if v := st.LastSeen; !v.IsZero() {
|
||||
e.LastSeen = v
|
||||
}
|
||||
if v := st.LastWrite; !v.IsZero() {
|
||||
e.LastWrite = v
|
||||
}
|
||||
if st.InNetworkMap {
|
||||
e.InNetworkMap = true
|
||||
}
|
||||
@ -211,28 +219,19 @@ func (st *Status) WriteHTML(w io.Writer) {
|
||||
//f("<p><b>opts:</b> <code>%s</code></p>\n", html.EscapeString(fmt.Sprintf("%+v", opts)))
|
||||
|
||||
f("<table>\n<thead>\n")
|
||||
f("<tr><th>Peer</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Handshake</th><th>Endpoints</th></tr>\n")
|
||||
f("<tr><th>Peer</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Endpoints</th></tr>\n")
|
||||
f("</thead>\n<tbody>\n")
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// The tailcontrol server rounds LastSeen to 10 minutes. So we
|
||||
// declare that a longAgo seen time of 15 minutes means
|
||||
// they're not connected.
|
||||
longAgo := now.Add(-15 * time.Minute)
|
||||
|
||||
for _, peer := range st.Peers() {
|
||||
ps := st.Peer[peer]
|
||||
var hsAgo string
|
||||
if !ps.LastHandshake.IsZero() {
|
||||
hsAgo = now.Sub(ps.LastHandshake).Round(time.Second).String() + " ago"
|
||||
} else {
|
||||
if ps.LastSeen.Before(longAgo) {
|
||||
hsAgo = "<i>offline</i>"
|
||||
} else if !ps.KeepAlive {
|
||||
hsAgo = "on demand"
|
||||
} else {
|
||||
hsAgo = "<b>pending</b>"
|
||||
var actAgo string
|
||||
if !ps.LastWrite.IsZero() {
|
||||
ago := now.Sub(ps.LastWrite)
|
||||
actAgo = ago.Round(time.Second).String() + " ago"
|
||||
if ago < 5*time.Minute {
|
||||
actAgo = "<b>" + actAgo + "</b>"
|
||||
}
|
||||
}
|
||||
var owner string
|
||||
@ -250,9 +249,20 @@ func (st *Status) WriteHTML(w io.Writer) {
|
||||
html.EscapeString(owner),
|
||||
ps.RxBytes,
|
||||
ps.TxBytes,
|
||||
hsAgo,
|
||||
actAgo,
|
||||
)
|
||||
f("<td class=\"aright\">")
|
||||
// TODO: let server report this active bool instead
|
||||
active := !ps.LastWrite.IsZero() && time.Since(ps.LastWrite) < 2*time.Minute
|
||||
relay := ps.Relay
|
||||
if relay != "" {
|
||||
if active && ps.CurAddr == "" {
|
||||
f("🔗 <b>derp-%v</b><br>", html.EscapeString(relay))
|
||||
} else {
|
||||
f("derp-%v<br>", html.EscapeString(relay))
|
||||
}
|
||||
}
|
||||
|
||||
match := false
|
||||
for _, addr := range ps.Addrs {
|
||||
if addr == ps.CurAddr {
|
||||
|
@ -686,6 +686,8 @@ func (as *AddrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
as.lastSend = now
|
||||
|
||||
// Some internal invariant checks.
|
||||
if len(as.addrs) != len(as.ipPorts) {
|
||||
panic(fmt.Sprintf("lena %d != leni %d", len(as.addrs), len(as.ipPorts)))
|
||||
@ -2094,6 +2096,8 @@ type AddrSet struct {
|
||||
|
||||
mu sync.Mutex // guards following fields
|
||||
|
||||
lastSend time.Time
|
||||
|
||||
// roamAddr is non-nil if/when we receive a correctly signed
|
||||
// WireGuard packet from an unexpected address. If so, we
|
||||
// remember it and send responses there in the future, but
|
||||
@ -2308,6 +2312,26 @@ func (a *AddrSet) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (as *AddrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
ps.LastWrite = as.lastSend
|
||||
for i, ua := range as.addrs {
|
||||
if ua.IP.Equal(derpMagicIP) {
|
||||
continue
|
||||
}
|
||||
uaStr := ua.String()
|
||||
ps.Addrs = append(ps.Addrs, uaStr)
|
||||
if as.curAddr == i {
|
||||
ps.CurAddr = uaStr
|
||||
}
|
||||
}
|
||||
if as.roamAddr != nil {
|
||||
ps.CurAddr = udpAddrDebugString(*as.roamAddrStd)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AddrSet) Addrs() []wgcfg.Endpoint {
|
||||
var eps []wgcfg.Endpoint
|
||||
for _, addr := range a.addrs {
|
||||
@ -2566,6 +2590,28 @@ func sbPrintAddr(sb *strings.Builder, a net.UDPAddr) {
|
||||
fmt.Fprintf(sb, ":%d", a.Port)
|
||||
}
|
||||
|
||||
func (c *Conn) derpRegionCodeOfAddrLocked(ipPort string) string {
|
||||
_, portStr, err := net.SplitHostPort(ipPort)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
regionID, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return c.derpRegionCodeOfIDLocked(regionID)
|
||||
}
|
||||
|
||||
func (c *Conn) derpRegionCodeOfIDLocked(regionID int) string {
|
||||
if c.derpMap == nil {
|
||||
return ""
|
||||
}
|
||||
if r, ok := c.derpMap.Regions[regionID]; ok {
|
||||
return r.RegionCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@ -2574,6 +2620,7 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||
ps := &ipnstate.PeerStatus{InMagicSock: true}
|
||||
if node, ok := c.nodeOfDisco[dk]; ok {
|
||||
ps.Addrs = append(ps.Addrs, node.Endpoints...)
|
||||
ps.Relay = c.derpRegionCodeOfAddrLocked(node.DERP)
|
||||
}
|
||||
de.populatePeerStatus(ps)
|
||||
sb.AddPeer(de.publicKey, ps)
|
||||
@ -2582,17 +2629,9 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||
for k, as := range c.addrsByKey {
|
||||
ps := &ipnstate.PeerStatus{
|
||||
InMagicSock: true,
|
||||
Relay: c.derpRegionCodeOfIDLocked(as.derpID()),
|
||||
}
|
||||
for i, ua := range as.addrs {
|
||||
uaStr := udpAddrDebugString(ua)
|
||||
ps.Addrs = append(ps.Addrs, uaStr)
|
||||
if as.curAddr == i {
|
||||
ps.CurAddr = uaStr
|
||||
}
|
||||
}
|
||||
if as.roamAddr != nil {
|
||||
ps.CurAddr = udpAddrDebugString(*as.roamAddrStd)
|
||||
}
|
||||
as.populatePeerStatus(ps)
|
||||
sb.AddPeer(k, ps)
|
||||
}
|
||||
|
||||
@ -3078,8 +3117,10 @@ func (de *discoEndpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
return
|
||||
}
|
||||
|
||||
ps.LastWrite = de.lastSend
|
||||
|
||||
now := time.Now()
|
||||
if udpAddr, _ := de.addrForSendLocked(now); !udpAddr.IsZero() {
|
||||
if udpAddr, derpAddr := de.addrForSendLocked(now); !udpAddr.IsZero() && derpAddr.IsZero() {
|
||||
ps.CurAddr = udpAddr.String()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user