| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package magicsock
- import (
- "fmt"
- "html"
- "io"
- "net/http"
- "net/netip"
- "sort"
- "strings"
- "time"
- "tailscale.com/feature"
- "tailscale.com/feature/buildfeatures"
- "tailscale.com/tailcfg"
- "tailscale.com/tstime/mono"
- "tailscale.com/types/key"
- )
- // ServeHTTPDebug serves an HTML representation of the innards of c for debugging.
- //
- // It's accessible either from tailscaled's debug port (at
- // /debug/magicsock) or via peerapi to a peer that's owned by the same
- // user (so they can e.g. inspect their phones).
- func (c *Conn) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
- if !buildfeatures.HasDebug {
- http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
- return
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- now := time.Now()
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- fmt.Fprintf(w, "<h1>magicsock</h1>")
- fmt.Fprintf(w, "<h2 id=derp><a href=#derp>#</a> DERP</h2><ul>")
- if c.derpMap != nil {
- type D struct {
- regionID int
- lastWrite time.Time
- createTime time.Time
- }
- ent := make([]D, 0, len(c.activeDerp))
- for rid, ad := range c.activeDerp {
- ent = append(ent, D{
- regionID: rid,
- lastWrite: *ad.lastWrite,
- createTime: ad.createTime,
- })
- }
- sort.Slice(ent, func(i, j int) bool {
- return ent[i].regionID < ent[j].regionID
- })
- for _, e := range ent {
- r, ok := c.derpMap.Regions[e.regionID]
- if !ok {
- continue
- }
- home := ""
- if e.regionID == c.myDerp {
- home = "🏠"
- }
- fmt.Fprintf(w, "<li>%s %d - %v: created %v ago, write %v ago</li>\n",
- home, e.regionID, html.EscapeString(r.RegionCode),
- now.Sub(e.createTime).Round(time.Second),
- now.Sub(e.lastWrite).Round(time.Second),
- )
- }
- }
- fmt.Fprintf(w, "</ul>\n")
- fmt.Fprintf(w, "<h2 id=ipport><a href=#ipport>#</a> ip:port to endpoint</h2><ul>")
- {
- type kv struct {
- addr epAddr
- pi *peerInfo
- }
- ent := make([]kv, 0, len(c.peerMap.byEpAddr))
- for k, v := range c.peerMap.byEpAddr {
- ent = append(ent, kv{k, v})
- }
- sort.Slice(ent, func(i, j int) bool { return epAddrLess(ent[i].addr, ent[j].addr) })
- for _, e := range ent {
- ep := e.pi.ep
- shortStr := ep.publicKey.ShortString()
- fmt.Fprintf(w, "<li>%v: <a href='#%v'>%v</a></li>\n", e.addr, strings.Trim(shortStr, "[]"), shortStr)
- }
- }
- fmt.Fprintf(w, "</ul>\n")
- fmt.Fprintf(w, "<h2 id=bykey><a href=#bykey>#</a> endpoints by key</h2>")
- {
- type kv struct {
- pub key.NodePublic
- pi *peerInfo
- }
- ent := make([]kv, 0, len(c.peerMap.byNodeKey))
- for k, v := range c.peerMap.byNodeKey {
- ent = append(ent, kv{k, v})
- }
- sort.Slice(ent, func(i, j int) bool { return ent[i].pub.Less(ent[j].pub) })
- peers := map[key.NodePublic]tailcfg.NodeView{}
- for _, p := range c.peers.All() {
- peers[p.Key()] = p
- }
- for _, e := range ent {
- ep := e.pi.ep
- shortStr := e.pub.ShortString()
- name := peerDebugName(peers[e.pub])
- fmt.Fprintf(w, "<h3 id=%v><a href='#%v'>%v</a> - %s</h3>\n",
- strings.Trim(shortStr, "[]"),
- strings.Trim(shortStr, "[]"),
- shortStr,
- html.EscapeString(name))
- printEndpointHTML(w, ep)
- }
- }
- }
- func printEndpointHTML(w io.Writer, ep *endpoint) {
- lastRecv := ep.lastRecvWG.LoadAtomic()
- ep.mu.Lock()
- defer ep.mu.Unlock()
- if ep.lastSendExt == 0 && lastRecv == 0 {
- return // no activity ever
- }
- now := time.Now()
- mnow := mono.Now()
- fmtMono := func(m mono.Time) string {
- if m == 0 {
- return "-"
- }
- return mnow.Sub(m).Round(time.Millisecond).String()
- }
- fmt.Fprintf(w, "<p>Best: <b>%+v</b>, %v ago (for %v)</p>\n", ep.bestAddr, fmtMono(ep.bestAddrAt), ep.trustBestAddrUntil.Sub(mnow).Round(time.Millisecond))
- fmt.Fprintf(w, "<p>heartbeating: %v</p>\n", ep.heartBeatTimer != nil)
- fmt.Fprintf(w, "<p>lastSend: %v ago</p>\n", fmtMono(ep.lastSendExt))
- fmt.Fprintf(w, "<p>lastFullPing: %v ago</p>\n", fmtMono(ep.lastFullPing))
- eps := make([]netip.AddrPort, 0, len(ep.endpointState))
- for ipp := range ep.endpointState {
- eps = append(eps, ipp)
- }
- sort.Slice(eps, func(i, j int) bool { return addrPortLess(eps[i], eps[j]) })
- io.WriteString(w, "<p>Endpoints:</p><ul>")
- for _, ipp := range eps {
- s := ep.endpointState[ipp]
- if ipp == ep.bestAddr.ap && !ep.bestAddr.vni.IsSet() {
- fmt.Fprintf(w, "<li><b>%s</b>: (best)<ul>", ipp)
- } else {
- fmt.Fprintf(w, "<li>%s: ...<ul>", ipp)
- }
- fmt.Fprintf(w, "<li>lastPing: %v ago</li>\n", fmtMono(s.lastPing))
- if s.lastGotPing.IsZero() {
- fmt.Fprintf(w, "<li>disco-learned-at: -</li>\n")
- } else {
- fmt.Fprintf(w, "<li>disco-learned-at: %v ago</li>\n", now.Sub(s.lastGotPing).Round(time.Second))
- }
- fmt.Fprintf(w, "<li>callMeMaybeTime: %v</li>\n", s.callMeMaybeTime)
- for i := range s.recentPongs {
- if i == 5 {
- break
- }
- pos := (int(s.recentPong) - i) % len(s.recentPongs)
- // If s.recentPongs wraps around pos will be negative, so start
- // again from the end of the slice.
- if pos < 0 {
- pos += len(s.recentPongs)
- }
- pr := s.recentPongs[pos]
- fmt.Fprintf(w, "<li>pong %v ago: in %v, from %v src %v</li>\n",
- fmtMono(pr.pongAt), pr.latency.Round(time.Millisecond/10),
- pr.from, pr.pongSrc)
- }
- fmt.Fprintf(w, "</ul></li>\n")
- }
- io.WriteString(w, "</ul>")
- }
- func peerDebugName(p tailcfg.NodeView) string {
- if !p.Valid() {
- return ""
- }
- n := p.Name()
- if base, _, ok := strings.Cut(n, "."); ok {
- return base
- }
- return p.Hostinfo().Hostname()
- }
- func addrPortLess(a, b netip.AddrPort) bool {
- if v := a.Addr().Compare(b.Addr()); v != 0 {
- return v < 0
- }
- return a.Port() < b.Port()
- }
- func epAddrLess(a, b epAddr) bool {
- if v := a.ap.Addr().Compare(b.ap.Addr()); v != 0 {
- return v < 0
- }
- if a.ap.Port() == b.ap.Port() {
- return a.vni.Get() < b.vni.Get()
- }
- return a.ap.Port() < b.ap.Port()
- }
|