debughttp.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package magicsock
  4. import (
  5. "fmt"
  6. "html"
  7. "io"
  8. "net/http"
  9. "net/netip"
  10. "sort"
  11. "strings"
  12. "time"
  13. "tailscale.com/feature"
  14. "tailscale.com/feature/buildfeatures"
  15. "tailscale.com/tailcfg"
  16. "tailscale.com/tstime/mono"
  17. "tailscale.com/types/key"
  18. )
  19. // ServeHTTPDebug serves an HTML representation of the innards of c for debugging.
  20. //
  21. // It's accessible either from tailscaled's debug port (at
  22. // /debug/magicsock) or via peerapi to a peer that's owned by the same
  23. // user (so they can e.g. inspect their phones).
  24. func (c *Conn) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
  25. if !buildfeatures.HasDebug {
  26. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  27. return
  28. }
  29. c.mu.Lock()
  30. defer c.mu.Unlock()
  31. now := time.Now()
  32. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  33. fmt.Fprintf(w, "<h1>magicsock</h1>")
  34. fmt.Fprintf(w, "<h2 id=derp><a href=#derp>#</a> DERP</h2><ul>")
  35. if c.derpMap != nil {
  36. type D struct {
  37. regionID int
  38. lastWrite time.Time
  39. createTime time.Time
  40. }
  41. ent := make([]D, 0, len(c.activeDerp))
  42. for rid, ad := range c.activeDerp {
  43. ent = append(ent, D{
  44. regionID: rid,
  45. lastWrite: *ad.lastWrite,
  46. createTime: ad.createTime,
  47. })
  48. }
  49. sort.Slice(ent, func(i, j int) bool {
  50. return ent[i].regionID < ent[j].regionID
  51. })
  52. for _, e := range ent {
  53. r, ok := c.derpMap.Regions[e.regionID]
  54. if !ok {
  55. continue
  56. }
  57. home := ""
  58. if e.regionID == c.myDerp {
  59. home = "🏠"
  60. }
  61. fmt.Fprintf(w, "<li>%s %d - %v: created %v ago, write %v ago</li>\n",
  62. home, e.regionID, html.EscapeString(r.RegionCode),
  63. now.Sub(e.createTime).Round(time.Second),
  64. now.Sub(e.lastWrite).Round(time.Second),
  65. )
  66. }
  67. }
  68. fmt.Fprintf(w, "</ul>\n")
  69. fmt.Fprintf(w, "<h2 id=ipport><a href=#ipport>#</a> ip:port to endpoint</h2><ul>")
  70. {
  71. type kv struct {
  72. addr epAddr
  73. pi *peerInfo
  74. }
  75. ent := make([]kv, 0, len(c.peerMap.byEpAddr))
  76. for k, v := range c.peerMap.byEpAddr {
  77. ent = append(ent, kv{k, v})
  78. }
  79. sort.Slice(ent, func(i, j int) bool { return epAddrLess(ent[i].addr, ent[j].addr) })
  80. for _, e := range ent {
  81. ep := e.pi.ep
  82. shortStr := ep.publicKey.ShortString()
  83. fmt.Fprintf(w, "<li>%v: <a href='#%v'>%v</a></li>\n", e.addr, strings.Trim(shortStr, "[]"), shortStr)
  84. }
  85. }
  86. fmt.Fprintf(w, "</ul>\n")
  87. fmt.Fprintf(w, "<h2 id=bykey><a href=#bykey>#</a> endpoints by key</h2>")
  88. {
  89. type kv struct {
  90. pub key.NodePublic
  91. pi *peerInfo
  92. }
  93. ent := make([]kv, 0, len(c.peerMap.byNodeKey))
  94. for k, v := range c.peerMap.byNodeKey {
  95. ent = append(ent, kv{k, v})
  96. }
  97. sort.Slice(ent, func(i, j int) bool { return ent[i].pub.Less(ent[j].pub) })
  98. peers := map[key.NodePublic]tailcfg.NodeView{}
  99. for _, p := range c.peers.All() {
  100. peers[p.Key()] = p
  101. }
  102. for _, e := range ent {
  103. ep := e.pi.ep
  104. shortStr := e.pub.ShortString()
  105. name := peerDebugName(peers[e.pub])
  106. fmt.Fprintf(w, "<h3 id=%v><a href='#%v'>%v</a> - %s</h3>\n",
  107. strings.Trim(shortStr, "[]"),
  108. strings.Trim(shortStr, "[]"),
  109. shortStr,
  110. html.EscapeString(name))
  111. printEndpointHTML(w, ep)
  112. }
  113. }
  114. }
  115. func printEndpointHTML(w io.Writer, ep *endpoint) {
  116. lastRecv := ep.lastRecvWG.LoadAtomic()
  117. ep.mu.Lock()
  118. defer ep.mu.Unlock()
  119. if ep.lastSendExt == 0 && lastRecv == 0 {
  120. return // no activity ever
  121. }
  122. now := time.Now()
  123. mnow := mono.Now()
  124. fmtMono := func(m mono.Time) string {
  125. if m == 0 {
  126. return "-"
  127. }
  128. return mnow.Sub(m).Round(time.Millisecond).String()
  129. }
  130. 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))
  131. fmt.Fprintf(w, "<p>heartbeating: %v</p>\n", ep.heartBeatTimer != nil)
  132. fmt.Fprintf(w, "<p>lastSend: %v ago</p>\n", fmtMono(ep.lastSendExt))
  133. fmt.Fprintf(w, "<p>lastFullPing: %v ago</p>\n", fmtMono(ep.lastFullPing))
  134. eps := make([]netip.AddrPort, 0, len(ep.endpointState))
  135. for ipp := range ep.endpointState {
  136. eps = append(eps, ipp)
  137. }
  138. sort.Slice(eps, func(i, j int) bool { return addrPortLess(eps[i], eps[j]) })
  139. io.WriteString(w, "<p>Endpoints:</p><ul>")
  140. for _, ipp := range eps {
  141. s := ep.endpointState[ipp]
  142. if ipp == ep.bestAddr.ap && !ep.bestAddr.vni.IsSet() {
  143. fmt.Fprintf(w, "<li><b>%s</b>: (best)<ul>", ipp)
  144. } else {
  145. fmt.Fprintf(w, "<li>%s: ...<ul>", ipp)
  146. }
  147. fmt.Fprintf(w, "<li>lastPing: %v ago</li>\n", fmtMono(s.lastPing))
  148. if s.lastGotPing.IsZero() {
  149. fmt.Fprintf(w, "<li>disco-learned-at: -</li>\n")
  150. } else {
  151. fmt.Fprintf(w, "<li>disco-learned-at: %v ago</li>\n", now.Sub(s.lastGotPing).Round(time.Second))
  152. }
  153. fmt.Fprintf(w, "<li>callMeMaybeTime: %v</li>\n", s.callMeMaybeTime)
  154. for i := range s.recentPongs {
  155. if i == 5 {
  156. break
  157. }
  158. pos := (int(s.recentPong) - i) % len(s.recentPongs)
  159. // If s.recentPongs wraps around pos will be negative, so start
  160. // again from the end of the slice.
  161. if pos < 0 {
  162. pos += len(s.recentPongs)
  163. }
  164. pr := s.recentPongs[pos]
  165. fmt.Fprintf(w, "<li>pong %v ago: in %v, from %v src %v</li>\n",
  166. fmtMono(pr.pongAt), pr.latency.Round(time.Millisecond/10),
  167. pr.from, pr.pongSrc)
  168. }
  169. fmt.Fprintf(w, "</ul></li>\n")
  170. }
  171. io.WriteString(w, "</ul>")
  172. }
  173. func peerDebugName(p tailcfg.NodeView) string {
  174. if !p.Valid() {
  175. return ""
  176. }
  177. n := p.Name()
  178. if base, _, ok := strings.Cut(n, "."); ok {
  179. return base
  180. }
  181. return p.Hostinfo().Hostname()
  182. }
  183. func addrPortLess(a, b netip.AddrPort) bool {
  184. if v := a.Addr().Compare(b.Addr()); v != 0 {
  185. return v < 0
  186. }
  187. return a.Port() < b.Port()
  188. }
  189. func epAddrLess(a, b epAddr) bool {
  190. if v := a.ap.Addr().Compare(b.ap.Addr()); v != 0 {
  191. return v < 0
  192. }
  193. if a.ap.Port() == b.ap.Port() {
  194. return a.vni.Get() < b.vni.Get()
  195. }
  196. return a.ap.Port() < b.ap.Port()
  197. }