mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-09 08:01:31 +00:00
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:
@@ -36,6 +36,11 @@ type DebugKnobs struct {
|
||||
// to its logger.
|
||||
VerboseLogs bool
|
||||
|
||||
// LogHTTP tells the Client to print the raw HTTP logs (from UPnP) to
|
||||
// its logger. This is useful when debugging buggy UPnP
|
||||
// implementations.
|
||||
LogHTTP bool
|
||||
|
||||
// Disable* disables a specific service from mapping.
|
||||
DisableUPnP bool
|
||||
DisablePMP bool
|
||||
|
@@ -12,12 +12,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
@@ -261,6 +263,9 @@ func (c *Client) upnpHTTPClientLocked() *http.Client {
|
||||
IdleConnTimeout: 2 * time.Second, // LAN is cheap
|
||||
},
|
||||
}
|
||||
if c.debug.LogHTTP {
|
||||
c.uPnPHTTPClient = requestLogger(c.logf, c.uPnPHTTPClient)
|
||||
}
|
||||
}
|
||||
return c.uPnPHTTPClient
|
||||
}
|
||||
@@ -369,3 +374,60 @@ func parseUPnPDiscoResponse(body []byte) (uPnPDiscoResponse, error) {
|
||||
r.USN = res.Header.Get("Usn")
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (r roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return r(req)
|
||||
}
|
||||
|
||||
func requestLogger(logf logger.Logf, client *http.Client) *http.Client {
|
||||
// Clone the HTTP client, and override the Transport to log to the
|
||||
// provided logger.
|
||||
ret := *client
|
||||
oldTransport := ret.Transport
|
||||
|
||||
var requestCounter atomic.Uint64
|
||||
loggingTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
ctr := requestCounter.Add(1)
|
||||
|
||||
// Read the body and re-set it.
|
||||
var (
|
||||
body []byte
|
||||
err error
|
||||
)
|
||||
if req.Body != nil {
|
||||
body, err = io.ReadAll(req.Body)
|
||||
req.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
logf("request[%d]: %s %q body=%q", ctr, req.Method, req.URL, body)
|
||||
|
||||
resp, err := oldTransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
logf("response[%d]: err=%v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read the response body
|
||||
if resp.Body != nil {
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
logf("response[%d]: %d bodyErr=%v", resp.StatusCode, err)
|
||||
return nil, err
|
||||
}
|
||||
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
|
||||
logf("response[%d]: %d body=%q", ctr, resp.StatusCode, body)
|
||||
return resp, nil
|
||||
})
|
||||
ret.Transport = loggingTransport
|
||||
|
||||
return &ret
|
||||
}
|
||||
|
Reference in New Issue
Block a user