debug.go 6.3 KB

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