diff --git a/cmd/tailscale/status.go b/cmd/tailscale/status.go index 9dc641064..237ff8a40 100644 --- a/cmd/tailscale/status.go +++ b/cmd/tailscale/status.go @@ -14,6 +14,7 @@ import ( "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(", ") diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index ac754e28f..e8f41c9ca 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -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 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; } //f("
opts: %s
Peer | Node | Owner | Rx | Tx | Handshake | Endpoints |
---|---|---|---|---|---|---|
Peer | Node | Owner | Rx | Tx | Activity | Endpoints | ")
+ // 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("🔗 derp-%v ", html.EscapeString(relay)) + } else { + f("derp-%v ", html.EscapeString(relay)) + } + } + match := false for _, addr := range ps.Addrs { if addr == ps.CurAddr { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 04ec092bc..0655a36d0 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -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() } } |