cmd/tailscale, net/portmapper: add --log-http option to "debug portmap"

This option allows logging the raw HTTP requests and responses that the
portmapper Client makes when using UPnP. This can be extremely helpful
when debugging strange UPnP issues with users' devices, and might allow
us to avoid having to instruct users to perform a packet capture.

Updates #8992

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I2c3cf6930b09717028deaff31738484cc9b008e4
This commit is contained in:
Andrew Dunham
2023-08-21 16:53:47 -04:00
parent 3451b89e5f
commit c86a610eb3
5 changed files with 142 additions and 14 deletions

View File

@@ -37,6 +37,7 @@ import (
"tailscale.com/tka"
"tailscale.com/types/key"
"tailscale.com/types/tkatype"
"tailscale.com/util/cmpx"
)
// defaultLocalClient is the default LocalClient when using the legacy
@@ -391,15 +392,51 @@ func (lc *LocalClient) DebugAction(ctx context.Context, action string) error {
return nil
}
// DebugPortmapOpts contains options for the DebugPortmap command.
type DebugPortmapOpts struct {
// Duration is how long the mapping should be created for. It defaults
// to 5 seconds if not set.
Duration time.Duration
// Type is the kind of portmap to debug. The empty string instructs the
// portmap client to perform all known types. Other valid options are
// "pmp", "pcp", and "upnp".
Type string
// GatewayAddr specifies the gateway address used during portmapping.
// If set, SelfAddr must also be set. If unset, it will be
// autodetected.
GatewayAddr netip.Addr
// SelfAddr specifies the gateway address used during portmapping. If
// set, GatewayAddr must also be set. If unset, it will be
// autodetected.
SelfAddr netip.Addr
// LogHTTP instructs the debug-portmap endpoint to print all HTTP
// requests and responses made to the logs.
LogHTTP bool
}
// DebugPortmap invokes the debug-portmap endpoint, and returns an
// io.ReadCloser that can be used to read the logs that are printed during this
// process.
func (lc *LocalClient) DebugPortmap(ctx context.Context, duration time.Duration, ty, gwSelf string) (io.ReadCloser, error) {
//
// opts can be nil; if so, default values will be used.
func (lc *LocalClient) DebugPortmap(ctx context.Context, opts *DebugPortmapOpts) (io.ReadCloser, error) {
vals := make(url.Values)
vals.Set("duration", duration.String())
vals.Set("type", ty)
if gwSelf != "" {
vals.Set("gateway_and_self", gwSelf)
if opts == nil {
opts = &DebugPortmapOpts{}
}
vals.Set("duration", cmpx.Or(opts.Duration, 5*time.Second).String())
vals.Set("type", opts.Type)
vals.Set("log_http", strconv.FormatBool(opts.LogHTTP))
if opts.GatewayAddr.IsValid() != opts.SelfAddr.IsValid() {
return nil, fmt.Errorf("both GatewayAddr and SelfAddr must be provided if one is")
} else if opts.GatewayAddr.IsValid() {
vals.Set("gateway_and_self", fmt.Sprintf("%s/%s", opts.GatewayAddr, opts.SelfAddr))
}
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+apitype.LocalAPIHost+"/localapi/v0/debug-portmap?"+vals.Encode(), nil)