// 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/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) { c.mu.Lock() defer c.mu.Unlock() now := time.Now() w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintf(w, "

magicsock

") fmt.Fprintf(w, "

# DERP

\n") fmt.Fprintf(w, "

# ip:port to endpoint

\n") fmt.Fprintf(w, "

# endpoints by key

") { 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 i := range c.peers.LenIter() { p := c.peers.At(i) peers[p.Key()] = p } for _, e := range ent { ep := e.pi.ep shortStr := e.pub.ShortString() name := peerDebugName(peers[e.pub]) fmt.Fprintf(w, "

%v - %s

\n", strings.Trim(shortStr, "[]"), strings.Trim(shortStr, "[]"), shortStr, html.EscapeString(name)) printEndpointHTML(w, ep) } } } func printEndpointHTML(w io.Writer, ep *endpoint) { lastRecv := ep.lastRecv.LoadAtomic() ep.mu.Lock() defer ep.mu.Unlock() if ep.lastSend == 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, "

Best: %+v, %v ago (for %v)

\n", ep.bestAddr, fmtMono(ep.bestAddrAt), ep.trustBestAddrUntil.Sub(mnow).Round(time.Millisecond)) fmt.Fprintf(w, "

heartbeating: %v

\n", ep.heartBeatTimer != nil) fmt.Fprintf(w, "

lastSend: %v ago

\n", fmtMono(ep.lastSend)) fmt.Fprintf(w, "

lastFullPing: %v ago

\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 ipPortLess(eps[i], eps[j]) }) io.WriteString(w, "

Endpoints: