Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
// Package debugportmapper registers support for debugging Tailscale's// portmapping support.package debugportmapper
import ( "context" "fmt" "net" "net/http" "net/netip" "strings" "sync" "time"
"tailscale.com/feature" "tailscale.com/ipn/localapi" "tailscale.com/net/netmon" "tailscale.com/net/portmapper" "tailscale.com/types/logger" "tailscale.com/util/def" "tailscale.com/util/eventbus")
func init() { if !feature.Register("debugportmapper") { return } localapi.Register("debug-portmap", serveDebugPortmap)}
func serveDebugPortmap(h *localapi.Handler, w http.ResponseWriter, r *http.Request) { if !h.PermitWrite { http.Error(w, "debug access denied", http.StatusForbidden) return } w.Header().Set("Content-Type", "text/plain")
dur, err := time.ParseDuration(r.FormValue("duration")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }
gwSelf := r.FormValue("gateway_and_self")
trueFunc := func() bool { return true } // Update portmapper debug flags debugKnobs := &portmapper.DebugKnobs{VerboseLogs: true} switch r.FormValue("type") { case "": case "pmp": debugKnobs.DisablePCPFunc = trueFunc debugKnobs.DisableUPnPFunc = trueFunc case "pcp": debugKnobs.DisablePMPFunc = trueFunc debugKnobs.DisableUPnPFunc = trueFunc case "upnp": debugKnobs.DisablePCPFunc = trueFunc debugKnobs.DisablePMPFunc = trueFunc default: http.Error(w, "unknown portmap debug type", http.StatusBadRequest) return } if k := h.LocalBackend().ControlKnobs(); k != nil { if k.DisableUPnP.Load() { debugKnobs.DisableUPnPFunc = trueFunc } }
if def.Bool(r.FormValue("log_http"), false) { debugKnobs.LogHTTP = true }
var ( logLock sync.Mutex handlerDone bool ) logf := func(format string, args ...any) { if !strings.HasSuffix(format, "\n") { format = format + "\n" }
logLock.Lock() defer logLock.Unlock()
// The portmapper can call this log function after the HTTP // handler returns, which is not allowed and can cause a panic. // If this happens, ignore the log lines since this typically // occurs due to a client disconnect. if handlerDone { return }
// Write and flush each line to the client so that output is streamed fmt.Fprintf(w, format, args...) if f, ok := w.(http.Flusher); ok { f.Flush() } } defer func() { logLock.Lock() handlerDone = true logLock.Unlock() }()
ctx, cancel := context.WithTimeout(r.Context(), dur) defer cancel()
done := make(chan bool, 1)
var c *portmapper.Client c = portmapper.NewClient(portmapper.Config{ Logf: logger.WithPrefix(logf, "portmapper: "), NetMon: h.LocalBackend().NetMon(), DebugKnobs: debugKnobs, EventBus: h.LocalBackend().EventBus(), OnChange: 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") }, }) defer c.Close()
bus := eventbus.New() defer bus.Close() netMon, err := netmon.New(bus, logger.WithPrefix(logf, "monitor: ")) if err != nil { logf("error creating monitor: %v", err) return }
gatewayAndSelfIP := func() (gw, self netip.Addr, ok bool) { if a, b, ok := strings.Cut(gwSelf, "/"); ok { gw = netip.MustParseAddr(a) self = netip.MustParseAddr(b) return gw, self, true } return netMon.GatewayAndSelfIP() }
c.SetGatewayLookupFunc(gatewayAndSelfIP)
gw, selfIP, ok := gatewayAndSelfIP() if !ok { logf("no gateway or self IP; %v", netMon.InterfaceState()) return } logf("gw=%v; self=%v", gw, selfIP)
uc, err := net.ListenPacket("udp", "0.0.0.0:0") if err != nil { return } defer uc.Close() c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port))
res, err := c.Probe(ctx) if err != nil { logf("error in Probe: %v", err) return } logf("Probe: %+v", res)
if !res.PCP && !res.PMP && !res.UPnP { logf("no portmapping services available") return }
if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok { logf("mapping: %v", ext) } else { logf("no mapping") }
select { case <-done: case <-ctx.Done(): if r.Context().Err() == nil { logf("serveDebugPortmap: context done: %v", ctx.Err()) } else { h.Logf("serveDebugPortmap: context done: %v", ctx.Err()) } }}