mirror of
https://github.com/tailscale/tailscale.git
synced 2025-05-05 07:01:01 +00:00
cmd/tailscaled, wgengine, ipn: add /debug/ipn handler with world state
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
dbca186a64
commit
322499473e
@ -62,8 +62,10 @@ func main() {
|
|||||||
log.Fatalf("--socket is required")
|
log.Fatalf("--socket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var debugMux *http.ServeMux
|
||||||
if *debug != "" {
|
if *debug != "" {
|
||||||
go runDebugServer(*debug)
|
debugMux = newDebugMux()
|
||||||
|
go runDebugServer(debugMux, *debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
var e wgengine.Engine
|
var e wgengine.Engine
|
||||||
@ -84,6 +86,7 @@ func main() {
|
|||||||
AutostartStateKey: globalStateKey,
|
AutostartStateKey: globalStateKey,
|
||||||
LegacyConfigPath: paths.LegacyConfigPath,
|
LegacyConfigPath: paths.LegacyConfigPath,
|
||||||
SurviveDisconnects: true,
|
SurviveDisconnects: true,
|
||||||
|
DebugMux: debugMux,
|
||||||
}
|
}
|
||||||
err = ipnserver.Run(context.Background(), logf, pol.PublicID.String(), opts, e)
|
err = ipnserver.Run(context.Background(), logf, pol.PublicID.String(), opts, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,14 +101,18 @@ func main() {
|
|||||||
pol.Shutdown(ctx)
|
pol.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDebugServer(addr string) {
|
func newDebugMux() *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
srv := http.Server{
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDebugServer(mux *http.ServeMux, addr string) {
|
||||||
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -56,6 +58,10 @@ type Options struct {
|
|||||||
// its existing state, and accepts new frontend connections. If
|
// its existing state, and accepts new frontend connections. If
|
||||||
// false, the server dumps its state and becomes idle.
|
// false, the server dumps its state and becomes idle.
|
||||||
SurviveDisconnects bool
|
SurviveDisconnects bool
|
||||||
|
|
||||||
|
// DebugMux, if non-nil, specifies an HTTP ServeMux in which
|
||||||
|
// to register a debug handler.
|
||||||
|
DebugMux *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
func pump(logf logger.Logf, ctx context.Context, bs *ipn.BackendServer, s net.Conn) {
|
func pump(logf logger.Logf, ctx context.Context, bs *ipn.BackendServer, s net.Conn) {
|
||||||
@ -112,6 +118,12 @@ func Run(rctx context.Context, logf logger.Logf, logid string, opts Options, e w
|
|||||||
return zstd.NewReader(nil)
|
return zstd.NewReader(nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if opts.DebugMux != nil {
|
||||||
|
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serveDebugHandler(w, r, logid, opts, b, e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var s net.Conn
|
var s net.Conn
|
||||||
serverToClient := func(b []byte) {
|
serverToClient := func(b []byte) {
|
||||||
if s != nil { // TODO: racy access to s?
|
if s != nil { // TODO: racy access to s?
|
||||||
@ -299,3 +311,101 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveDebugHandler(w http.ResponseWriter, r *http.Request, logid string, opts Options, b *ipn.LocalBackend, e wgengine.Engine) {
|
||||||
|
f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
|
f(`<html><head><style>
|
||||||
|
.owner { font-size: 80%%; color: #444; }
|
||||||
|
.tailaddr { font-size: 80%%; font-family: monospace: }
|
||||||
|
</style></head>`)
|
||||||
|
f("<body><h1>IPN state</h1><h2>Run args</h2>")
|
||||||
|
f("<p><b>logid:</b> %s</p>\n", logid)
|
||||||
|
f("<p><b>opts:</b> <code>%s</code></p>\n", html.EscapeString(fmt.Sprintf("%+v", opts)))
|
||||||
|
|
||||||
|
st := b.Status()
|
||||||
|
f("<table border=1 cellpadding=5><tr><th>Peer</th><th>Node</th><th>Rx</th><th>Tx</th><th>Handshake</th><th>Endpoints</th></tr>")
|
||||||
|
|
||||||
|
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 owner string
|
||||||
|
if up, ok := st.User[ps.UserID]; ok {
|
||||||
|
owner = up.LoginName
|
||||||
|
if i := strings.Index(owner, "@"); i != -1 {
|
||||||
|
owner = owner[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f("<tr><td>%s</td><td>%s<div class=owner>%s</div><div class=tailaddr>%s</div></td><td>%v</td><td>%v</td><td>%v</td>",
|
||||||
|
peer.ShortString(),
|
||||||
|
osEmoji(ps.OS)+" "+html.EscapeString(simplifyHostname(ps.HostName)),
|
||||||
|
html.EscapeString(owner),
|
||||||
|
ps.TailAddr,
|
||||||
|
ps.RxBytes,
|
||||||
|
ps.TxBytes,
|
||||||
|
hsAgo,
|
||||||
|
)
|
||||||
|
f("<td>")
|
||||||
|
match := false
|
||||||
|
for _, addr := range ps.Addrs {
|
||||||
|
if addr == ps.CurAddr {
|
||||||
|
match = true
|
||||||
|
f("<b>%s</b> 🔗<br>\n", addr)
|
||||||
|
} else {
|
||||||
|
f("%s<br>\n", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ps.CurAddr != "" && !match {
|
||||||
|
f("<b>%s</b> \xf0\x9f\xa7\xb3<br>\n", ps.CurAddr)
|
||||||
|
}
|
||||||
|
f("</tr>") // end Addrs
|
||||||
|
|
||||||
|
f("</tr>\n")
|
||||||
|
}
|
||||||
|
f("</table>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func osEmoji(os string) string {
|
||||||
|
switch os {
|
||||||
|
case "linux":
|
||||||
|
return "🐧"
|
||||||
|
case "macOS":
|
||||||
|
return "🍎"
|
||||||
|
case "windows":
|
||||||
|
return "🖥️"
|
||||||
|
case "iOS":
|
||||||
|
return "📱"
|
||||||
|
case "android":
|
||||||
|
return "🤖"
|
||||||
|
case "freebsd":
|
||||||
|
return "👿"
|
||||||
|
case "openbsd":
|
||||||
|
return "🐡"
|
||||||
|
}
|
||||||
|
return "👽"
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyHostname(s string) string {
|
||||||
|
s = strings.TrimSuffix(s, ".local")
|
||||||
|
s = strings.TrimSuffix(s, ".localdomain")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
172
ipn/ipnstate/ipnstate.go
Normal file
172
ipn/ipnstate/ipnstate.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package ipnstate captures the entire state of the Tailscale network.
|
||||||
|
//
|
||||||
|
// It's a leaf package so ipn, wgengine, and magicsock can all depend on it.
|
||||||
|
package ipnstate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status represents the entire state of the IPN network.
|
||||||
|
type Status struct {
|
||||||
|
BackendState string
|
||||||
|
Peer map[key.Public]*PeerStatus
|
||||||
|
User map[tailcfg.UserID]tailcfg.UserProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) Peers() []key.Public {
|
||||||
|
kk := make([]key.Public, 0, len(s.Peer))
|
||||||
|
for k := range s.Peer {
|
||||||
|
kk = append(kk, k)
|
||||||
|
}
|
||||||
|
sort.Slice(kk, func(i, j int) bool { return bytes.Compare(kk[i][:], kk[j][:]) < 0 })
|
||||||
|
return kk
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeerStatus struct {
|
||||||
|
PublicKey key.Public
|
||||||
|
HostName string // HostInfo's Hostname (not a DNS name or necessarily unique)
|
||||||
|
OS string // HostInfo.OS
|
||||||
|
UserID tailcfg.UserID
|
||||||
|
|
||||||
|
TailAddr string // Tailscale IP
|
||||||
|
|
||||||
|
// Endpoints:
|
||||||
|
Addrs []string
|
||||||
|
CurAddr string // one of Addrs, or unique if roaming
|
||||||
|
|
||||||
|
RxBytes int64
|
||||||
|
TxBytes int64
|
||||||
|
Created time.Time // time registered with tailcontrol
|
||||||
|
LastSeen time.Time // last seen to tailcontrol
|
||||||
|
LastHandshake time.Time // with local wireguard
|
||||||
|
KeepAlive bool
|
||||||
|
|
||||||
|
// InNetworkMap means that this peer was seen in our latest network map.
|
||||||
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
||||||
|
InNetworkMap bool
|
||||||
|
|
||||||
|
// InMagicSock means that this peer is being tracked by magicsock.
|
||||||
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
||||||
|
InMagicSock bool
|
||||||
|
|
||||||
|
// InEngine means that this peer is tracked by the wireguard engine.
|
||||||
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
||||||
|
InEngine bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusBuilder struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
locked bool
|
||||||
|
st Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *StatusBuilder) Status() *Status {
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
sb.locked = true
|
||||||
|
return &sb.st
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUser adds a user profile to the status.
|
||||||
|
func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
if sb.locked {
|
||||||
|
log.Printf("[unexpected] ipnstate: AddUser after Locked")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.st.User == nil {
|
||||||
|
sb.st.User = make(map[tailcfg.UserID]tailcfg.UserProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.st.User[id] = up
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPeer adds a peer node to the status.
|
||||||
|
//
|
||||||
|
// Its PeerStatus is mixed with any previous status already added.
|
||||||
|
func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
|
||||||
|
if st == nil {
|
||||||
|
panic("nil PeerStatus")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.mu.Lock()
|
||||||
|
defer sb.mu.Unlock()
|
||||||
|
if sb.locked {
|
||||||
|
log.Printf("[unexpected] ipnstate: AddPeer after Locked")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.st.Peer == nil {
|
||||||
|
sb.st.Peer = make(map[key.Public]*PeerStatus)
|
||||||
|
}
|
||||||
|
e, ok := sb.st.Peer[peer]
|
||||||
|
if !ok {
|
||||||
|
sb.st.Peer[peer] = st
|
||||||
|
st.PublicKey = peer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := st.HostName; v != "" {
|
||||||
|
e.HostName = v
|
||||||
|
}
|
||||||
|
if v := st.UserID; v != 0 {
|
||||||
|
e.UserID = v
|
||||||
|
}
|
||||||
|
if v := st.TailAddr; v != "" {
|
||||||
|
e.TailAddr = v
|
||||||
|
}
|
||||||
|
if v := st.OS; v != "" {
|
||||||
|
e.OS = st.OS
|
||||||
|
}
|
||||||
|
if v := st.Addrs; v != nil {
|
||||||
|
e.Addrs = v
|
||||||
|
}
|
||||||
|
if v := st.CurAddr; v != "" {
|
||||||
|
e.CurAddr = v
|
||||||
|
}
|
||||||
|
if v := st.RxBytes; v != 0 {
|
||||||
|
e.RxBytes = v
|
||||||
|
}
|
||||||
|
if v := st.TxBytes; v != 0 {
|
||||||
|
e.TxBytes = v
|
||||||
|
}
|
||||||
|
if v := st.LastHandshake; !v.IsZero() {
|
||||||
|
e.LastHandshake = v
|
||||||
|
}
|
||||||
|
if v := st.Created; !v.IsZero() {
|
||||||
|
e.Created = v
|
||||||
|
}
|
||||||
|
if v := st.LastSeen; !v.IsZero() {
|
||||||
|
e.LastSeen = v
|
||||||
|
}
|
||||||
|
if st.InNetworkMap {
|
||||||
|
e.InNetworkMap = true
|
||||||
|
}
|
||||||
|
if st.InMagicSock {
|
||||||
|
e.InMagicSock = true
|
||||||
|
}
|
||||||
|
if st.InEngine {
|
||||||
|
e.InEngine = true
|
||||||
|
}
|
||||||
|
if st.KeepAlive {
|
||||||
|
e.KeepAlive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusUpdater interface {
|
||||||
|
UpdateStatus(*StatusBuilder)
|
||||||
|
}
|
45
ipn/local.go
45
ipn/local.go
@ -15,9 +15,11 @@ import (
|
|||||||
|
|
||||||
"github.com/tailscale/wireguard-go/wgcfg"
|
"github.com/tailscale/wireguard-go/wgcfg"
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/portlist"
|
"tailscale.com/portlist"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/empty"
|
"tailscale.com/types/empty"
|
||||||
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/wgengine"
|
"tailscale.com/wgengine"
|
||||||
@ -103,6 +105,49 @@ func (b *LocalBackend) Shutdown() {
|
|||||||
b.e.Wait()
|
b.e.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status returns the latest status of the Tailscale network from all the various components.
|
||||||
|
func (b *LocalBackend) Status() *ipnstate.Status {
|
||||||
|
sb := new(ipnstate.StatusBuilder)
|
||||||
|
b.UpdateStatus(sb)
|
||||||
|
return sb.Status()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||||
|
b.e.UpdateStatus(sb)
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
// TODO: hostinfo, and its networkinfo
|
||||||
|
// TODO: EngineStatus copy (and deprecate it?)
|
||||||
|
if b.netMapCache != nil {
|
||||||
|
for id, up := range b.netMapCache.UserProfiles {
|
||||||
|
sb.AddUser(id, up)
|
||||||
|
}
|
||||||
|
for _, p := range b.netMapCache.Peers {
|
||||||
|
var lastSeen time.Time
|
||||||
|
if p.LastSeen != nil {
|
||||||
|
lastSeen = *p.LastSeen
|
||||||
|
}
|
||||||
|
var tailAddr string
|
||||||
|
if len(p.Addresses) > 0 {
|
||||||
|
tailAddr = strings.TrimSuffix(p.Addresses[0].String(), "/32")
|
||||||
|
}
|
||||||
|
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
|
||||||
|
InNetworkMap: true,
|
||||||
|
UserID: p.User,
|
||||||
|
TailAddr: tailAddr,
|
||||||
|
HostName: p.Hostinfo.Hostname,
|
||||||
|
OS: p.Hostinfo.OS,
|
||||||
|
KeepAlive: p.KeepAlive,
|
||||||
|
Created: p.Created,
|
||||||
|
LastSeen: lastSeen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// SetDecompressor sets a decompression function, which must be a zstd
|
// SetDecompressor sets a decompression function, which must be a zstd
|
||||||
// reader.
|
// reader.
|
||||||
//
|
//
|
||||||
|
@ -5,7 +5,11 @@
|
|||||||
// Package key defines some types related to curve25519 keys.
|
// Package key defines some types related to curve25519 keys.
|
||||||
package key
|
package key
|
||||||
|
|
||||||
import "golang.org/x/crypto/curve25519"
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
)
|
||||||
|
|
||||||
// Private represents a curve25519 private key.
|
// Private represents a curve25519 private key.
|
||||||
type Private [32]byte
|
type Private [32]byte
|
||||||
@ -24,6 +28,13 @@ type Public [32]byte
|
|||||||
// Public reports whether p is the zero value.
|
// Public reports whether p is the zero value.
|
||||||
func (p Public) IsZero() bool { return p == Public{} }
|
func (p Public) IsZero() bool { return p == Public{} }
|
||||||
|
|
||||||
|
// ShortString returns the Tailscale conventional debug representation
|
||||||
|
// of a public key: the first five base64 digits of the key, in square
|
||||||
|
// brackets.
|
||||||
|
func (p Public) ShortString() string {
|
||||||
|
return "[" + base64.StdEncoding.EncodeToString(p[:])[:5] + "]"
|
||||||
|
}
|
||||||
|
|
||||||
// B32 returns k as the *[32]byte type that's used by the
|
// B32 returns k as the *[32]byte type that's used by the
|
||||||
// golang.org/x/crypto packages. This allocates; it might
|
// golang.org/x/crypto packages. This allocates; it might
|
||||||
// not be appropriate for performance-sensitive paths.
|
// not be appropriate for performance-sensitive paths.
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
"tailscale.com/derp"
|
"tailscale.com/derp"
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/derp/derpmap"
|
"tailscale.com/derp/derpmap"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/netcheck"
|
"tailscale.com/netcheck"
|
||||||
@ -1898,3 +1899,37 @@ func sbPrintAddr(sb *strings.Builder, a net.UDPAddr) {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(sb, ":%d", a.Port)
|
fmt.Fprintf(sb, ":%d", a.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
for k, as := range c.addrsByKey {
|
||||||
|
ps := &ipnstate.PeerStatus{
|
||||||
|
InMagicSock: true,
|
||||||
|
}
|
||||||
|
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.roamAddr)
|
||||||
|
}
|
||||||
|
sb.AddPeer(k, ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.foreachActiveDerpSortedLocked(func(node int, ad activeDerp) {
|
||||||
|
// TODO(bradfitz): add to ipnstate.StatusBuilder
|
||||||
|
//f("<li><b>derp-%v</b>: cr%v,wr%v</li>", node, simpleDur(now.Sub(ad.createTime)), simpleDur(now.Sub(*ad.lastWrite)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func udpAddrDebugString(ua net.UDPAddr) string {
|
||||||
|
if ua.IP.Equal(derpMagicIP) {
|
||||||
|
return fmt.Sprintf("derp-%d", ua.Port)
|
||||||
|
}
|
||||||
|
return ua.String()
|
||||||
|
}
|
||||||
|
@ -18,8 +18,10 @@ import (
|
|||||||
"github.com/tailscale/wireguard-go/device"
|
"github.com/tailscale/wireguard-go/device"
|
||||||
"github.com/tailscale/wireguard-go/tun"
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"github.com/tailscale/wireguard-go/wgcfg"
|
"github.com/tailscale/wireguard-go/wgcfg"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/magicsock"
|
"tailscale.com/wgengine/magicsock"
|
||||||
@ -666,3 +668,21 @@ func (e *userspaceEngine) SetNetInfoCallback(cb NetInfoCallback) {
|
|||||||
func (e *userspaceEngine) SetDERPEnabled(v bool) {
|
func (e *userspaceEngine) SetDERPEnabled(v bool) {
|
||||||
e.magicConn.SetDERPEnabled(v)
|
e.magicConn.SetDERPEnabled(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||||
|
st, err := e.getStatus()
|
||||||
|
if err != nil {
|
||||||
|
e.logf("wgengine: getStatus: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, ps := range st.Peers {
|
||||||
|
sb.AddPeer(key.Public(ps.NodeKey), &ipnstate.PeerStatus{
|
||||||
|
RxBytes: int64(ps.RxBytes),
|
||||||
|
TxBytes: int64(ps.TxBytes),
|
||||||
|
LastHandshake: ps.LastHandshake,
|
||||||
|
InEngine: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
e.magicConn.UpdateStatus(sb)
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tailscale/wireguard-go/wgcfg"
|
"github.com/tailscale/wireguard-go/wgcfg"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,6 +75,9 @@ func (e *watchdogEngine) SetFilter(filt *filter.Filter) {
|
|||||||
func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) {
|
func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) {
|
||||||
e.watchdog("SetStatusCallback", func() { e.wrap.SetStatusCallback(cb) })
|
e.watchdog("SetStatusCallback", func() { e.wrap.SetStatusCallback(cb) })
|
||||||
}
|
}
|
||||||
|
func (e *watchdogEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||||
|
e.watchdog("UpdateStatus", func() { e.wrap.UpdateStatus(sb) })
|
||||||
|
}
|
||||||
func (e *watchdogEngine) SetNetInfoCallback(cb NetInfoCallback) {
|
func (e *watchdogEngine) SetNetInfoCallback(cb NetInfoCallback) {
|
||||||
e.watchdog("SetNetInfoCallback", func() { e.wrap.SetNetInfoCallback(cb) })
|
e.watchdog("SetNetInfoCallback", func() { e.wrap.SetNetInfoCallback(cb) })
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/tailscale/wireguard-go/device"
|
"github.com/tailscale/wireguard-go/device"
|
||||||
"github.com/tailscale/wireguard-go/tun"
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"github.com/tailscale/wireguard-go/wgcfg"
|
"github.com/tailscale/wireguard-go/wgcfg"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
@ -29,6 +30,8 @@ type PeerStatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status is the Engine status.
|
// Status is the Engine status.
|
||||||
|
//
|
||||||
|
// TODO(bradfitz): remove this, subset of ipnstate? Need to migrate users.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Peers []PeerStatus
|
Peers []PeerStatus
|
||||||
LocalAddrs []string // TODO(crawshaw): []wgcfg.Endpoint?
|
LocalAddrs []string // TODO(crawshaw): []wgcfg.Endpoint?
|
||||||
@ -144,4 +147,8 @@ type Engine interface {
|
|||||||
// SetNetInfoCallback sets the function to call when a
|
// SetNetInfoCallback sets the function to call when a
|
||||||
// new NetInfo summary is available.
|
// new NetInfo summary is available.
|
||||||
SetNetInfoCallback(NetInfoCallback)
|
SetNetInfoCallback(NetInfoCallback)
|
||||||
|
|
||||||
|
// UpdateStatus populates the network state using the provided
|
||||||
|
// status builder.
|
||||||
|
UpdateStatus(*ipnstate.StatusBuilder)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user