diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 141c3814a..7a674a32a 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -14,6 +14,7 @@ import ( "io" "io/ioutil" "log" + "net" "net/http" "net/http/httptrace" "net/url" @@ -23,9 +24,11 @@ import ( "tailscale.com/derp/derphttp" "tailscale.com/ipn" "tailscale.com/net/interfaces" + "tailscale.com/net/portmapper" "tailscale.com/net/tshttpproxy" "tailscale.com/tailcfg" "tailscale.com/types/key" + "tailscale.com/types/logger" "tailscale.com/wgengine/monitor" ) @@ -33,6 +36,7 @@ var debugArgs struct { monitor bool getURL string derpCheck string + portmap bool } var debugModeFunc = debugMode // so it can be addressable @@ -40,6 +44,7 @@ var debugModeFunc = debugMode // so it can be addressable func debugMode(args []string) error { fs := flag.NewFlagSet("debug", flag.ExitOnError) fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.") + fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.") fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.") fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code") if err := fs.Parse(args); err != nil { @@ -55,6 +60,9 @@ func debugMode(args []string) error { if debugArgs.monitor { return runMonitor(ctx) } + if debugArgs.portmap { + return debugPortmap(ctx) + } if debugArgs.getURL != "" { return getURL(ctx, debugArgs.getURL) } @@ -191,3 +199,63 @@ func checkDerp(ctx context.Context, derpRegion string) error { log.Printf("ok") return err } + +func debugPortmap(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + + done := make(chan bool, 1) + + var c *portmapper.Client + logf := log.Printf + c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), func() { + logf("portmapping changed.") + logf("have mapping: %v", c.HaveMapping()) + + if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok { + logf("cb: mapping: %v", ext) + select { + case done <- true: + default: + } + return + } + logf("cb: no mapping") + }) + linkMon, err := monitor.New(logger.WithPrefix(logf, "monitor: ")) + if err != nil { + return err + } + c.SetGatewayLookupFunc(linkMon.GatewayAndSelfIP) + + res, err := c.Probe(ctx) + if err != nil { + return fmt.Errorf("Probe: %v", err) + } + logf("Probe: %+v", res) + + if !res.PCP && !res.PMP && !res.UPnP { + logf("no portmapping services available") + return nil + } + + uc, err := net.ListenPacket("udp", "0.0.0.0:0") + if err != nil { + return err + } + defer uc.Close() + c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port)) + + if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok { + logf("mapping: %v", ext) + } else { + logf("no mapping") + } + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +}