cmd/tailscaled: add whois/identd-ish debug handler
parent
c7fc4a06da
commit
c611d8480b
|
@ -7,6 +7,7 @@ package ipnserver
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -32,6 +33,7 @@ import (
|
||||||
"tailscale.com/net/netstat"
|
"tailscale.com/net/netstat"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/smallzstd"
|
"tailscale.com/smallzstd"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/pidowner"
|
"tailscale.com/util/pidowner"
|
||||||
"tailscale.com/util/systemd"
|
"tailscale.com/util/systemd"
|
||||||
|
@ -620,6 +622,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
||||||
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
|
opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) {
|
||||||
serveHTMLStatus(w, b)
|
serveHTMLStatus(w, b)
|
||||||
})
|
})
|
||||||
|
opts.DebugMux.Handle("/whois", whoIsHandler{b})
|
||||||
}
|
}
|
||||||
|
|
||||||
server.b = b
|
server.b = b
|
||||||
|
@ -883,3 +886,40 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// whoIsHandler is the debug server's /debug?ip=$IP HTTP handler.
|
||||||
|
type whoIsHandler struct {
|
||||||
|
b *ipn.LocalBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h whoIsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
b := h.b
|
||||||
|
var ip netaddr.IP
|
||||||
|
if v := r.FormValue("ip"); v != "" {
|
||||||
|
var err error
|
||||||
|
ip, err = netaddr.ParseIP(r.FormValue("ip"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "invalid 'ip' parameter", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, "missing 'ip' parameter", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, u, ok := b.WhoIs(ip)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "no match for IP", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := &tailcfg.WhoIsResponse{
|
||||||
|
Node: n,
|
||||||
|
UserProfile: &u,
|
||||||
|
}
|
||||||
|
j, err := json.MarshalIndent(res, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "JSON encoding error", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
|
49
ipn/local.go
49
ipn/local.go
|
@ -90,6 +90,7 @@ type LocalBackend struct {
|
||||||
hostinfo *tailcfg.Hostinfo
|
hostinfo *tailcfg.Hostinfo
|
||||||
// netMap is not mutated in-place once set.
|
// netMap is not mutated in-place once set.
|
||||||
netMap *controlclient.NetworkMap
|
netMap *controlclient.NetworkMap
|
||||||
|
nodeByAddr map[netaddr.IP]*tailcfg.Node
|
||||||
activeLogin string // last logged LoginName from netMap
|
activeLogin string // last logged LoginName from netMap
|
||||||
engineStatus EngineStatus
|
engineStatus EngineStatus
|
||||||
endpoints []string
|
endpoints []string
|
||||||
|
@ -234,7 +235,22 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhoIs reports the node and user who owns the node with the given IP.
|
||||||
|
// If ok == true, n and u are valid.
|
||||||
|
func (b *LocalBackend) WhoIs(ip netaddr.IP) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
n, ok = b.nodeByAddr[ip]
|
||||||
|
if !ok {
|
||||||
|
return nil, u, false
|
||||||
|
}
|
||||||
|
u, ok = b.netMap.UserProfiles[n.User]
|
||||||
|
if !ok {
|
||||||
|
return nil, u, false
|
||||||
|
}
|
||||||
|
return n, u, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDecompressor sets a decompression function, which must be a zstd
|
// SetDecompressor sets a decompression function, which must be a zstd
|
||||||
|
@ -1507,6 +1523,39 @@ func (b *LocalBackend) setNetMapLocked(nm *controlclient.NetworkMap) {
|
||||||
b.logf("active login: %v", login)
|
b.logf("active login: %v", login)
|
||||||
b.activeLogin = login
|
b.activeLogin = login
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nm == nil {
|
||||||
|
b.nodeByAddr = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the nodeByAddr index.
|
||||||
|
if b.nodeByAddr == nil {
|
||||||
|
b.nodeByAddr = map[netaddr.IP]*tailcfg.Node{}
|
||||||
|
}
|
||||||
|
// First pass, mark everything unwanted.
|
||||||
|
for k := range b.nodeByAddr {
|
||||||
|
b.nodeByAddr[k] = nil
|
||||||
|
}
|
||||||
|
addNode := func(n *tailcfg.Node) {
|
||||||
|
for _, ipp := range n.Addresses {
|
||||||
|
if ipp.IsSingleIP() {
|
||||||
|
b.nodeByAddr[ipp.IP] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nm.SelfNode != nil {
|
||||||
|
addNode(nm.SelfNode)
|
||||||
|
}
|
||||||
|
for _, p := range nm.Peers {
|
||||||
|
addNode(p)
|
||||||
|
}
|
||||||
|
// Third pass, actually delete the unwanted items.
|
||||||
|
for k, v := range b.nodeByAddr {
|
||||||
|
if v == nil {
|
||||||
|
delete(b.nodeByAddr, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOnlyPublicKeys returns the current machine and node public
|
// TestOnlyPublicKeys returns the current machine and node public
|
||||||
|
|
|
@ -935,3 +935,9 @@ func eqCIDRs(a, b []netaddr.IPPrefix) bool {
|
||||||
func eqTimePtr(a, b *time.Time) bool {
|
func eqTimePtr(a, b *time.Time) bool {
|
||||||
return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b))
|
return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.
|
||||||
|
type WhoIsResponse struct {
|
||||||
|
Node *Node
|
||||||
|
UserProfile *UserProfile
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue