debugportmapper.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !ts_omit_debugportmapper
  4. package local
  5. import (
  6. "cmp"
  7. "context"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "net/netip"
  12. "net/url"
  13. "strconv"
  14. "time"
  15. "tailscale.com/client/tailscale/apitype"
  16. )
  17. // DebugPortmapOpts contains options for the [Client.DebugPortmap] command.
  18. type DebugPortmapOpts struct {
  19. // Duration is how long the mapping should be created for. It defaults
  20. // to 5 seconds if not set.
  21. Duration time.Duration
  22. // Type is the kind of portmap to debug. The empty string instructs the
  23. // portmap client to perform all known types. Other valid options are
  24. // "pmp", "pcp", and "upnp".
  25. Type string
  26. // GatewayAddr specifies the gateway address used during portmapping.
  27. // If set, SelfAddr must also be set. If unset, it will be
  28. // autodetected.
  29. GatewayAddr netip.Addr
  30. // SelfAddr specifies the gateway address used during portmapping. If
  31. // set, GatewayAddr must also be set. If unset, it will be
  32. // autodetected.
  33. SelfAddr netip.Addr
  34. // LogHTTP instructs the debug-portmap endpoint to print all HTTP
  35. // requests and responses made to the logs.
  36. LogHTTP bool
  37. }
  38. // DebugPortmap invokes the debug-portmap endpoint, and returns an
  39. // io.ReadCloser that can be used to read the logs that are printed during this
  40. // process.
  41. //
  42. // opts can be nil; if so, default values will be used.
  43. func (lc *Client) DebugPortmap(ctx context.Context, opts *DebugPortmapOpts) (io.ReadCloser, error) {
  44. vals := make(url.Values)
  45. if opts == nil {
  46. opts = &DebugPortmapOpts{}
  47. }
  48. vals.Set("duration", cmp.Or(opts.Duration, 5*time.Second).String())
  49. vals.Set("type", opts.Type)
  50. vals.Set("log_http", strconv.FormatBool(opts.LogHTTP))
  51. if opts.GatewayAddr.IsValid() != opts.SelfAddr.IsValid() {
  52. return nil, fmt.Errorf("both GatewayAddr and SelfAddr must be provided if one is")
  53. } else if opts.GatewayAddr.IsValid() {
  54. vals.Set("gateway_and_self", fmt.Sprintf("%s/%s", opts.GatewayAddr, opts.SelfAddr))
  55. }
  56. req, err := http.NewRequestWithContext(ctx, "GET", "http://"+apitype.LocalAPIHost+"/localapi/v0/debug-portmap?"+vals.Encode(), nil)
  57. if err != nil {
  58. return nil, err
  59. }
  60. res, err := lc.doLocalRequestNiceError(req)
  61. if err != nil {
  62. return nil, err
  63. }
  64. if res.StatusCode != 200 {
  65. body, _ := io.ReadAll(res.Body)
  66. res.Body.Close()
  67. return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
  68. }
  69. return res.Body, nil
  70. }