debug.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build go1.19
  4. package main
  5. import (
  6. "context"
  7. "crypto/tls"
  8. "encoding/json"
  9. "errors"
  10. "flag"
  11. "fmt"
  12. "io"
  13. "log"
  14. "net/http"
  15. "net/http/httptrace"
  16. "net/url"
  17. "os"
  18. "time"
  19. "tailscale.com/derp/derphttp"
  20. "tailscale.com/health"
  21. "tailscale.com/ipn"
  22. "tailscale.com/net/netmon"
  23. "tailscale.com/net/tshttpproxy"
  24. "tailscale.com/tailcfg"
  25. "tailscale.com/types/key"
  26. "tailscale.com/util/eventbus"
  27. )
  28. var debugArgs struct {
  29. ifconfig bool // print network state once and exit
  30. monitor bool
  31. getURL string
  32. derpCheck string
  33. portmap bool
  34. }
  35. var debugModeFunc = debugMode // so it can be addressable
  36. func debugMode(args []string) error {
  37. fs := flag.NewFlagSet("debug", flag.ExitOnError)
  38. fs.BoolVar(&debugArgs.ifconfig, "ifconfig", false, "If true, print network interface state")
  39. fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run network monitor forever. Precludes all other options.")
  40. fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.")
  41. fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
  42. fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
  43. if err := fs.Parse(args); err != nil {
  44. return err
  45. }
  46. if len(fs.Args()) > 0 {
  47. return errors.New("unknown non-flag debug subcommand arguments")
  48. }
  49. ctx := context.Background()
  50. if debugArgs.derpCheck != "" {
  51. return checkDerp(ctx, debugArgs.derpCheck)
  52. }
  53. if debugArgs.ifconfig {
  54. return runMonitor(ctx, false)
  55. }
  56. if debugArgs.monitor {
  57. return runMonitor(ctx, true)
  58. }
  59. if debugArgs.portmap {
  60. return debugPortmap(ctx)
  61. }
  62. if debugArgs.getURL != "" {
  63. return getURL(ctx, debugArgs.getURL)
  64. }
  65. return errors.New("only --monitor is available at the moment")
  66. }
  67. func runMonitor(ctx context.Context, loop bool) error {
  68. b := eventbus.New()
  69. defer b.Close()
  70. dump := func(st *netmon.State) {
  71. j, _ := json.MarshalIndent(st, "", " ")
  72. os.Stderr.Write(j)
  73. }
  74. mon, err := netmon.New(b, log.Printf)
  75. if err != nil {
  76. return err
  77. }
  78. defer mon.Close()
  79. mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
  80. if !delta.Major {
  81. log.Printf("Network monitor fired; not a major change")
  82. return
  83. }
  84. log.Printf("Network monitor fired. New state:")
  85. dump(delta.New)
  86. })
  87. if loop {
  88. log.Printf("Starting link change monitor; initial state:")
  89. }
  90. dump(mon.InterfaceState())
  91. if !loop {
  92. return nil
  93. }
  94. mon.Start()
  95. log.Printf("Started link change monitor; waiting...")
  96. select {}
  97. }
  98. func getURL(ctx context.Context, urlStr string) error {
  99. if urlStr == "login" {
  100. urlStr = "https://login.tailscale.com"
  101. }
  102. log.SetOutput(os.Stdout)
  103. ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
  104. GetConn: func(hostPort string) { log.Printf("GetConn(%q)", hostPort) },
  105. GotConn: func(info httptrace.GotConnInfo) { log.Printf("GotConn: %+v", info) },
  106. DNSStart: func(info httptrace.DNSStartInfo) { log.Printf("DNSStart: %+v", info) },
  107. DNSDone: func(info httptrace.DNSDoneInfo) { log.Printf("DNSDoneInfo: %+v", info) },
  108. TLSHandshakeStart: func() { log.Printf("TLSHandshakeStart") },
  109. TLSHandshakeDone: func(cs tls.ConnectionState, err error) { log.Printf("TLSHandshakeDone: %+v, %v", cs, err) },
  110. WroteRequest: func(info httptrace.WroteRequestInfo) { log.Printf("WroteRequest: %+v", info) },
  111. })
  112. req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
  113. if err != nil {
  114. return fmt.Errorf("http.NewRequestWithContext: %v", err)
  115. }
  116. proxyURL, err := tshttpproxy.ProxyFromEnvironment(req)
  117. if err != nil {
  118. return fmt.Errorf("tshttpproxy.ProxyFromEnvironment: %v", err)
  119. }
  120. log.Printf("proxy: %v", proxyURL)
  121. tr := &http.Transport{
  122. Proxy: func(*http.Request) (*url.URL, error) { return proxyURL, nil },
  123. ProxyConnectHeader: http.Header{},
  124. DisableKeepAlives: true,
  125. }
  126. if proxyURL != nil {
  127. auth, err := tshttpproxy.GetAuthHeader(proxyURL)
  128. if err == nil && auth != "" {
  129. tr.ProxyConnectHeader.Set("Proxy-Authorization", auth)
  130. }
  131. log.Printf("tshttpproxy.GetAuthHeader(%v) got: auth of %d bytes, err=%v", proxyURL, len(auth), err)
  132. const truncLen = 20
  133. if len(auth) > truncLen {
  134. auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
  135. }
  136. if auth != "" {
  137. // We used log.Printf above (for timestamps).
  138. // Use fmt.Printf here instead just to appease
  139. // a security scanner, despite log.Printf only
  140. // going to stdout.
  141. fmt.Printf("... Proxy-Authorization = %q\n", auth)
  142. }
  143. }
  144. res, err := tr.RoundTrip(req)
  145. if err != nil {
  146. return fmt.Errorf("Transport.RoundTrip: %v", err)
  147. }
  148. defer res.Body.Close()
  149. return res.Write(os.Stdout)
  150. }
  151. func checkDerp(ctx context.Context, derpRegion string) (err error) {
  152. ht := new(health.Tracker)
  153. req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil)
  154. if err != nil {
  155. return fmt.Errorf("create derp map request: %w", err)
  156. }
  157. res, err := http.DefaultClient.Do(req)
  158. if err != nil {
  159. return fmt.Errorf("fetch derp map failed: %w", err)
  160. }
  161. defer res.Body.Close()
  162. b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
  163. if err != nil {
  164. return fmt.Errorf("fetch derp map failed: %w", err)
  165. }
  166. if res.StatusCode != 200 {
  167. return fmt.Errorf("fetch derp map: %v: %s", res.Status, b)
  168. }
  169. var dmap tailcfg.DERPMap
  170. if err = json.Unmarshal(b, &dmap); err != nil {
  171. return fmt.Errorf("fetch DERP map: %w", err)
  172. }
  173. getRegion := func() *tailcfg.DERPRegion {
  174. for _, r := range dmap.Regions {
  175. if r.RegionCode == derpRegion {
  176. return r
  177. }
  178. }
  179. for _, r := range dmap.Regions {
  180. log.Printf("Known region: %q", r.RegionCode)
  181. }
  182. log.Fatalf("unknown region %q", derpRegion)
  183. panic("unreachable")
  184. }
  185. priv1 := key.NewNode()
  186. priv2 := key.NewNode()
  187. c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
  188. c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
  189. c1.HealthTracker = ht
  190. c2.HealthTracker = ht
  191. defer func() {
  192. if err != nil {
  193. c1.Close()
  194. c2.Close()
  195. }
  196. }()
  197. c2.NotePreferred(true) // just to open it
  198. m, err := c2.Recv()
  199. log.Printf("c2 got %T, %v", m, err)
  200. t0 := time.Now()
  201. if err := c1.Send(priv2.Public(), []byte("hello")); err != nil {
  202. return err
  203. }
  204. fmt.Println(time.Since(t0))
  205. m, err = c2.Recv()
  206. log.Printf("c2 got %T, %v", m, err)
  207. if err != nil {
  208. return err
  209. }
  210. log.Printf("ok")
  211. return err
  212. }
  213. func debugPortmap(ctx context.Context) error {
  214. return fmt.Errorf("this flag has been deprecated in favour of 'tailscale debug portmap'")
  215. }