diff --git a/cmd/tailscaled/depaware-minlinux.txt b/cmd/tailscaled/depaware-minlinux.txt index 301cdde84..f19db955a 100644 --- a/cmd/tailscaled/depaware-minlinux.txt +++ b/cmd/tailscaled/depaware-minlinux.txt @@ -87,7 +87,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/packet/checksum from tailscale.com/net/tstun tailscale.com/net/ping from tailscale.com/net/netcheck+ tailscale.com/net/sockstats from tailscale.com/control/controlclient+ - tailscale.com/net/stun from tailscale.com/ipn/localapi+ + tailscale.com/net/stun from tailscale.com/net/netcheck+ L tailscale.com/net/tcpinfo from tailscale.com/derp tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsaddr from tailscale.com/ipn+ @@ -115,7 +115,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ tailscale.com/types/logid from tailscale.com/ipn/ipnlocal+ tailscale.com/types/netmap from tailscale.com/control/controlclient+ - tailscale.com/types/nettype from tailscale.com/ipn/localapi+ + tailscale.com/types/nettype from tailscale.com/net/netcheck+ tailscale.com/types/opt from tailscale.com/control/controlknobs+ tailscale.com/types/persist from tailscale.com/control/controlclient+ tailscale.com/types/preftype from tailscale.com/ipn+ @@ -251,7 +251,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de hash from crypto+ hash/crc32 from compress/gzip+ hash/maphash from go4.org/mem - html from net/http/pprof+ + html from tailscale.com/ipn/ipnstate+ io from bufio+ io/fs from crypto/x509+ L io/ioutil from github.com/mitchellh/go-ps+ @@ -273,7 +273,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de net/http/httptrace from golang.org/x/net/http2+ net/http/httputil from tailscale.com/cmd/tailscaled net/http/internal from net/http+ - net/http/pprof from tailscale.com/cmd/tailscaled+ net/netip from github.com/gaissmai/bart+ net/textproto from golang.org/x/net/http/httpguts+ net/url from crypto/x509+ @@ -287,8 +286,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de regexp from github.com/coreos/go-iptables/iptables+ regexp/syntax from regexp runtime/debug from github.com/klauspost/compress/zstd+ - runtime/pprof from net/http/pprof+ - runtime/trace from net/http/pprof slices from crypto/tls+ sort from compress/flate+ strconv from compress/flate+ @@ -296,7 +293,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de sync from compress/flate+ sync/atomic from context+ syscall from crypto/rand+ - text/tabwriter from runtime/pprof time from compress/gzip+ unicode from bytes+ unicode/utf16 from crypto/x509+ diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 11b00dec8..d64524d7e 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -17,7 +17,6 @@ import ( "fmt" "log" "net/http" - "net/http/pprof" "os" "os/signal" "path/filepath" @@ -106,7 +105,6 @@ var args struct { cleanUp bool confFile string // empty, file path, or "vm:user-data" - debug string port uint16 statepath string statedir string @@ -131,7 +129,6 @@ var ( var subCommands = map[string]*func([]string) error{ "install-system-daemon": &installSystemDaemon, "uninstall-system-daemon": &uninstallSystemDaemon, - "debug": &debugModeFunc, "be-child": &beChildFunc, } @@ -146,7 +143,6 @@ func main() { printVersion := false flag.IntVar(&args.verbose, "verbose", defaultVerbosity(), "log verbosity level; 0 is default, 1 or higher are increasingly verbose") flag.BoolVar(&args.cleanUp, "cleanup", false, "clean up system state and exit") - flag.StringVar(&args.debug, "debug", "", "listen address ([ip]:port) of optional debug server") flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`) flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`) flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`) @@ -314,8 +310,6 @@ func ipnServerOpts() (o serverOptions) { return o } -var debugMux *http.ServeMux - func run() (err error) { var logf logger.Logf = log.Printf @@ -378,10 +372,6 @@ func run() (err error) { log.Printf("error in synology migration: %v", err) } - if args.debug != "" { - debugMux = newDebugMux() - } - if app := envknob.App(); app != "" { hostinfo.SetApp(app) } @@ -433,9 +423,6 @@ func startIPNServer(ctx context.Context, logf logger.Logf, sys *tsd.System) erro }() srv := ipnserver.New(logf, sys.NetMon.Get()) - if debugMux != nil { - debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) - } var lbErr syncs.AtomicValue[error] go func() { @@ -493,12 +480,6 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, sys *tsd.System) (_ if err != nil { return nil, fmt.Errorf("createEngine: %w", err) } - if debugMux != nil { - if ms, ok := sys.MagicSock.GetOK(); ok { - debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug) - } - go runDebugServer(debugMux, args.debug) - } startNetstack, err := newNetstack(logf, sys, onlyNetstack, handleSubnetsInNetstack()) if err != nil { @@ -642,17 +623,6 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo return onlyNetstack, nil } -func newDebugMux() *http.ServeMux { - mux := http.NewServeMux() - mux.HandleFunc("/debug/metrics", servePrometheusMetrics) - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - return mux -} - func servePrometheusMetrics(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") varz.Handler(w, r) diff --git a/ipn/ipnlocal/c2n_pprof.go b/ipn/ipnlocal/c2n_pprof.go index b4bc35790..6bfdd783b 100644 --- a/ipn/ipnlocal/c2n_pprof.go +++ b/ipn/ipnlocal/c2n_pprof.go @@ -4,42 +4,3 @@ //go:build !js && !wasm package ipnlocal - -import ( - "fmt" - "net/http" - "runtime" - "runtime/pprof" - "strconv" -) - -func init() { - c2nLogHeap = func(w http.ResponseWriter, r *http.Request) { - // Support same optional gc parameter as net/http/pprof: - if gc, _ := strconv.Atoi(r.FormValue("gc")); gc > 0 { - runtime.GC() - } - pprof.WriteHeapProfile(w) - } - - c2nPprof = func(w http.ResponseWriter, r *http.Request, profile string) { - w.Header().Set("X-Content-Type-Options", "nosniff") - p := pprof.Lookup(string(profile)) - if p == nil { - http.Error(w, "Unknown profile", http.StatusNotFound) - return - } - gc, _ := strconv.Atoi(r.FormValue("gc")) - if profile == "heap" && gc > 0 { - runtime.GC() - } - debug, _ := strconv.Atoi(r.FormValue("debug")) - if debug != 0 { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - } else { - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, profile)) - } - p.WriteTo(w, debug) - } -} diff --git a/ipn/localapi/debugderp.go b/ipn/localapi/debugderp.go index 85eb031e6..71d0b896e 100644 --- a/ipn/localapi/debugderp.go +++ b/ipn/localapi/debugderp.go @@ -2,303 +2,3 @@ // SPDX-License-Identifier: BSD-3-Clause package localapi - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "net" - "net/http" - "net/netip" - "strconv" - "time" - - "tailscale.com/derp/derphttp" - "tailscale.com/ipn/ipnstate" - "tailscale.com/net/netaddr" - "tailscale.com/net/netns" - "tailscale.com/net/stun" - "tailscale.com/tailcfg" - "tailscale.com/types/key" - "tailscale.com/types/nettype" -) - -func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - if r.Method != "POST" { - http.Error(w, "POST required", http.StatusMethodNotAllowed) - return - } - var st ipnstate.DebugDERPRegionReport - defer func() { - j, _ := json.Marshal(st) - w.Header().Set("Content-Type", "application/json") - w.Write(j) - }() - - dm := h.b.DERPMap() - if dm == nil { - st.Errors = append(st.Errors, "no DERP map (not connected?)") - return - } - regStr := r.FormValue("region") - var reg *tailcfg.DERPRegion - if id, err := strconv.Atoi(regStr); err == nil { - reg = dm.Regions[id] - } else { - for _, r := range dm.Regions { - if r.RegionCode == regStr { - reg = r - break - } - } - } - if reg == nil { - st.Errors = append(st.Errors, fmt.Sprintf("no such region %q in DERP map", regStr)) - return - } - st.Info = append(st.Info, fmt.Sprintf("Region %v == %q", reg.RegionID, reg.RegionCode)) - if len(dm.Regions) == 1 { - st.Warnings = append(st.Warnings, "Having only a single DERP region (i.e. removing the default Tailscale-provided regions) is a single point of failure and could hamper connectivity") - } - - if reg.Avoid { - st.Warnings = append(st.Warnings, "Region is marked with Avoid bit") - } - if len(reg.Nodes) == 0 { - st.Errors = append(st.Errors, "Region has no nodes defined") - return - } - - ctx := r.Context() - - var ( - dialer net.Dialer - client *http.Client = http.DefaultClient - ) - checkConn := func(derpNode *tailcfg.DERPNode) bool { - port := firstNonzero(derpNode.DERPPort, 443) - - var ( - hasIPv4 bool - hasIPv6 bool - ) - - // Check IPv4 first - addr := net.JoinHostPort(firstNonzero(derpNode.IPv4, derpNode.HostName), strconv.Itoa(port)) - conn, err := dialer.DialContext(ctx, "tcp4", addr) - if err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error connecting to node %q @ %q over IPv4: %v", derpNode.HostName, addr, err)) - } else { - defer conn.Close() - - // Upgrade to TLS and verify that works properly. - tlsConn := tls.Client(conn, &tls.Config{ - ServerName: firstNonzero(derpNode.CertName, derpNode.HostName), - }) - if err := tlsConn.HandshakeContext(ctx); err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error upgrading connection to node %q @ %q to TLS over IPv4: %v", derpNode.HostName, addr, err)) - } else { - hasIPv4 = true - } - } - - // Check IPv6 - addr = net.JoinHostPort(firstNonzero(derpNode.IPv6, derpNode.HostName), strconv.Itoa(port)) - conn, err = dialer.DialContext(ctx, "tcp6", addr) - if err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error connecting to node %q @ %q over IPv6: %v", derpNode.HostName, addr, err)) - } else { - defer conn.Close() - - // Upgrade to TLS and verify that works properly. - tlsConn := tls.Client(conn, &tls.Config{ - ServerName: firstNonzero(derpNode.CertName, derpNode.HostName), - // TODO(andrew-d): we should print more - // detailed failure information on if/why TLS - // verification fails - }) - if err := tlsConn.HandshakeContext(ctx); err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error upgrading connection to node %q @ %q to TLS over IPv6: %v", derpNode.HostName, addr, err)) - } else { - hasIPv6 = true - } - } - - // If we only have an IPv6 conn, then warn; we want both. - if hasIPv6 && !hasIPv4 { - st.Warnings = append(st.Warnings, fmt.Sprintf("Node %q only has IPv6 connectivity, not IPv4", derpNode.HostName)) - } else if hasIPv6 && hasIPv4 { - st.Info = append(st.Info, fmt.Sprintf("Node %q has working IPv4 and IPv6 connectivity", derpNode.HostName)) - } - - return hasIPv4 || hasIPv6 - } - - checkSTUN4 := func(derpNode *tailcfg.DERPNode) { - u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf, h.b.NetMon())).ListenPacket(ctx, "udp4", ":0") - if err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err)) - return - } - defer u4.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var addr netip.Addr - if derpNode.IPv4 != "" { - addr, err = netip.ParseAddr(derpNode.IPv4) - if err != nil { - // Error printed elsewhere - return - } - } else { - addrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", derpNode.HostName) - if err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error resolving node %q IPv4 addresses: %v", derpNode.HostName, err)) - return - } - addr = addrs[0] - } - - addrPort := netip.AddrPortFrom(addr, uint16(firstNonzero(derpNode.STUNPort, 3478))) - - txID := stun.NewTxID() - req := stun.Request(txID) - - done := make(chan struct{}) - defer close(done) - - go func() { - select { - case <-ctx.Done(): - case <-done: - } - u4.Close() - }() - - gotResponse := make(chan netip.AddrPort, 1) - go func() { - defer u4.Close() - - var buf [64 << 10]byte - for { - n, addr, err := u4.ReadFromUDPAddrPort(buf[:]) - if err != nil { - return - } - pkt := buf[:n] - if !stun.Is(pkt) { - continue - } - ap := netaddr.Unmap(addr) - if !ap.IsValid() { - continue - } - tx, addrPort, err := stun.ParseResponse(pkt) - if err != nil { - continue - } - if tx == txID { - gotResponse <- addrPort - return - } - } - }() - - _, err = u4.WriteToUDPAddrPort(req, addrPort) - if err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error sending IPv4 STUN packet to %v (%q): %v", addrPort, derpNode.HostName, err)) - return - } - - select { - case resp := <-gotResponse: - st.Info = append(st.Info, fmt.Sprintf("Node %q returned IPv4 STUN response: %v", derpNode.HostName, resp)) - case <-ctx.Done(): - st.Warnings = append(st.Warnings, fmt.Sprintf("Node %q did not return a IPv4 STUN response", derpNode.HostName)) - } - } - - // Start by checking whether we can establish a HTTP connection - for _, derpNode := range reg.Nodes { - connSuccess := checkConn(derpNode) - - // Verify that the /generate_204 endpoint works - captivePortalURL := "http://" + derpNode.HostName + "/generate_204" - resp, err := client.Get(captivePortalURL) - if err != nil { - st.Warnings = append(st.Warnings, fmt.Sprintf("Error making request to the captive portal check %q; is port 80 blocked?", captivePortalURL)) - } else { - resp.Body.Close() - } - - if !connSuccess { - continue - } - - fakePrivKey := key.NewNode() - - // Next, repeatedly get the server key to see if the node is - // behind a load balancer (incorrectly). - serverPubKeys := make(map[key.NodePublic]bool) - for i := range 5 { - func() { - rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.b.NetMon(), func() *tailcfg.DERPRegion { - return &tailcfg.DERPRegion{ - RegionID: reg.RegionID, - RegionCode: reg.RegionCode, - RegionName: reg.RegionName, - Nodes: []*tailcfg.DERPNode{derpNode}, - } - }) - if err := rc.Connect(ctx); err != nil { - st.Errors = append(st.Errors, fmt.Sprintf("Error connecting to node %q @ try %d: %v", derpNode.HostName, i, err)) - return - } - - if len(serverPubKeys) == 0 { - st.Info = append(st.Info, fmt.Sprintf("Successfully established a DERP connection with node %q", derpNode.HostName)) - } - serverPubKeys[rc.ServerPublicKey()] = true - }() - } - if len(serverPubKeys) > 1 { - st.Errors = append(st.Errors, fmt.Sprintf("Received multiple server public keys (%d); is the DERP server behind a load balancer?", len(serverPubKeys))) - } - - // Send a STUN query to this node to verify whether or not it - // correctly returns an IP address. - checkSTUN4(derpNode) - } - - // TODO(bradfitz): finish: - // * try to DERP auth with new public key. - // * if rejected, add Info that it's likely the DERP server authz is on, - // try with LocalBackend's node key instead. - // * if they have more then one node, try to relay a packet between them - // and see if it works (like cmd/derpprobe). But if server authz is on, - // we won't be able to, so just warn. Say to turn that off, try again, - // then turn it back on. TODO(bradfitz): maybe add a debug frame to DERP - // protocol to say how many peers it's meshed with. Should match count - // in DERPRegion. Or maybe even list all their server pub keys that it's peered - // with. - // * If their certificate is bad, either expired or just wrongly - // issued in the first place, tell them specifically that the - // cert is bad not just that the connection failed. -} - -func firstNonzero[T comparable](items ...T) T { - var zero T - for _, item := range items { - if item != zero { - return item - } - } - return zero -} diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 6ec0c772a..52bbdd086 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -7,7 +7,6 @@ package localapi import ( "bytes" "cmp" - "context" "encoding/json" "errors" "fmt" @@ -59,44 +58,34 @@ var handler = map[string]localAPIHandler{ // The other /localapi/v0/NAME handlers are exact matches and contain only NAME // without a trailing slash: - "alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690 - "bugreport": (*Handler).serveBugReport, - "check-ip-forwarding": (*Handler).serveCheckIPForwarding, - "check-prefs": (*Handler).serveCheckPrefs, - "component-debug-logging": (*Handler).serveComponentDebugLogging, - "debug": (*Handler).serveDebug, - "debug-derp-region": (*Handler).serveDebugDERPRegion, - "debug-dial-types": (*Handler).serveDebugDialTypes, - "debug-log": (*Handler).serveDebugLog, - "debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches, - "debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules, - "derpmap": (*Handler).serveDERPMap, - "dev-set-state-store": (*Handler).serveDevSetStateStore, - "dial": (*Handler).serveDial, - "disconnect-control": (*Handler).disconnectControl, - "goroutines": (*Handler).serveGoroutines, - "handle-push-message": (*Handler).serveHandlePushMessage, - "id-token": (*Handler).serveIDToken, - "login-interactive": (*Handler).serveLoginInteractive, - "logout": (*Handler).serveLogout, - "metrics": (*Handler).serveMetrics, - "ping": (*Handler).servePing, - "pprof": (*Handler).servePprof, - "prefs": (*Handler).servePrefs, - "query-feature": (*Handler).serveQueryFeature, - "reload-config": (*Handler).reloadConfig, - "reset-auth": (*Handler).serveResetAuth, - "set-expiry-sooner": (*Handler).serveSetExpirySooner, - "set-gui-visible": (*Handler).serveSetGUIVisible, - "set-push-device-token": (*Handler).serveSetPushDeviceToken, - "set-use-exit-node-enabled": (*Handler).serveSetUseExitNodeEnabled, - "start": (*Handler).serveStart, - "status": (*Handler).serveStatus, - "suggest-exit-node": (*Handler).serveSuggestExitNode, - "upload-client-metrics": (*Handler).serveUploadClientMetrics, - "usermetrics": (*Handler).serveUserMetrics, - "watch-ipn-bus": (*Handler).serveWatchIPNBus, - "whois": (*Handler).serveWhoIs, + "alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690 + "bugreport": (*Handler).serveBugReport, + "check-ip-forwarding": (*Handler).serveCheckIPForwarding, + "check-prefs": (*Handler).serveCheckPrefs, + "dial": (*Handler).serveDial, + "disconnect-control": (*Handler).disconnectControl, + "goroutines": (*Handler).serveGoroutines, + "handle-push-message": (*Handler).serveHandlePushMessage, + "id-token": (*Handler).serveIDToken, + "login-interactive": (*Handler).serveLoginInteractive, + "logout": (*Handler).serveLogout, + "metrics": (*Handler).serveMetrics, + "ping": (*Handler).servePing, + "prefs": (*Handler).servePrefs, + "query-feature": (*Handler).serveQueryFeature, + "reload-config": (*Handler).reloadConfig, + "reset-auth": (*Handler).serveResetAuth, + "set-expiry-sooner": (*Handler).serveSetExpirySooner, + "set-gui-visible": (*Handler).serveSetGUIVisible, + "set-push-device-token": (*Handler).serveSetPushDeviceToken, + "set-use-exit-node-enabled": (*Handler).serveSetUseExitNodeEnabled, + "start": (*Handler).serveStart, + "status": (*Handler).serveStatus, + "suggest-exit-node": (*Handler).serveSuggestExitNode, + "upload-client-metrics": (*Handler).serveUploadClientMetrics, + "usermetrics": (*Handler).serveUserMetrics, + "watch-ipn-bus": (*Handler).serveWatchIPNBus, + "whois": (*Handler).serveWhoIs, } var ( @@ -512,236 +501,6 @@ func (h *Handler) serveUserMetrics(w http.ResponseWriter, r *http.Request) { h.b.UserMetricsRegistry().Handler(w, r) } -func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - if r.Method != "POST" { - http.Error(w, "POST required", http.StatusMethodNotAllowed) - return - } - // The action is normally in a POST form parameter, but - // some actions (like "notify") want a full JSON body, so - // permit some to have their action in a header. - var action string - switch v := r.Header.Get("Debug-Action"); v { - case "notify": - action = v - default: - action = r.FormValue("action") - } - var err error - switch action { - case "derp-set-homeless": - h.b.MagicConn().SetHomeless(true) - case "derp-unset-homeless": - h.b.MagicConn().SetHomeless(false) - case "rebind": - err = h.b.DebugRebind() - case "restun": - err = h.b.DebugReSTUN() - case "notify": - var n ipn.Notify - err = json.NewDecoder(r.Body).Decode(&n) - if err != nil { - break - } - h.b.DebugNotify(n) - case "notify-last-netmap": - h.b.DebugNotifyLastNetMap() - case "break-tcp-conns": - err = h.b.DebugBreakTCPConns() - case "break-derp-conns": - err = h.b.DebugBreakDERPConns() - case "force-netmap-update": - h.b.DebugForceNetmapUpdate() - case "control-knobs": - k := h.b.ControlKnobs() - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(k.AsDebugJSON()) - if err == nil { - return - } - case "pick-new-derp": - err = h.b.DebugPickNewDERP() - case "force-prefer-derp": - var n int - err = json.NewDecoder(r.Body).Decode(&n) - if err != nil { - break - } - h.b.DebugForcePreferDERP(n) - case "": - err = fmt.Errorf("missing parameter 'action'") - default: - err = fmt.Errorf("unknown action %q", action) - } - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - w.Header().Set("Content-Type", "text/plain") - io.WriteString(w, "done\n") -} - -func (h *Handler) serveDevSetStateStore(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - if r.Method != "POST" { - http.Error(w, "POST required", http.StatusMethodNotAllowed) - return - } - if err := h.b.SetDevStateStore(r.FormValue("key"), r.FormValue("value")); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "text/plain") - io.WriteString(w, "done\n") -} - -func (h *Handler) serveDebugPacketFilterRules(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - nm := h.b.NetMap() - if nm == nil { - http.Error(w, "no netmap", http.StatusNotFound) - return - } - w.Header().Set("Content-Type", "application/json") - - enc := json.NewEncoder(w) - enc.SetIndent("", "\t") - enc.Encode(nm.PacketFilterRules) -} - -func (h *Handler) serveDebugPacketFilterMatches(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - nm := h.b.NetMap() - if nm == nil { - http.Error(w, "no netmap", http.StatusNotFound) - return - } - w.Header().Set("Content-Type", "application/json") - - enc := json.NewEncoder(w) - enc.SetIndent("", "\t") - enc.Encode(nm.PacketFilter) -} - -func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug access denied", http.StatusForbidden) - return - } - component := r.FormValue("component") - secs, _ := strconv.Atoi(r.FormValue("secs")) - err := h.b.SetComponentDebugLogging(component, h.clock.Now().Add(time.Duration(secs)*time.Second)) - var res struct { - Error string - } - if err != nil { - res.Error = err.Error() - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(res) -} - -func (h *Handler) serveDebugDialTypes(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "debug-dial-types access denied", http.StatusForbidden) - return - } - if r.Method != httpm.POST { - http.Error(w, "only POST allowed", http.StatusMethodNotAllowed) - return - } - - ip := r.FormValue("ip") - port := r.FormValue("port") - network := r.FormValue("network") - - addr := ip + ":" + port - if _, err := netip.ParseAddrPort(addr); err != nil { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "invalid address %q: %v", addr, err) - return - } - - ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) - defer cancel() - - var bareDialer net.Dialer - - dialer := h.b.Dialer() - - var peerDialer net.Dialer - peerDialer.Control = dialer.PeerDialControlFunc() - - // Kick off a dial with each available dialer in parallel. - dialers := []struct { - name string - dial func(context.Context, string, string) (net.Conn, error) - }{ - {"SystemDial", dialer.SystemDial}, - {"UserDial", dialer.UserDial}, - {"PeerDial", peerDialer.DialContext}, - {"BareDial", bareDialer.DialContext}, - } - type result struct { - name string - conn net.Conn - err error - } - results := make(chan result, len(dialers)) - - var wg sync.WaitGroup - for _, dialer := range dialers { - dialer := dialer // loop capture - - wg.Add(1) - go func() { - defer wg.Done() - conn, err := dialer.dial(ctx, network, addr) - results <- result{dialer.name, conn, err} - }() - } - - wg.Wait() - for range len(dialers) { - res := <-results - fmt.Fprintf(w, "[%s] connected=%v err=%v\n", res.name, res.conn != nil, res.err) - if res.conn != nil { - res.conn.Close() - } - } -} - -// servePprofFunc is the implementation of Handler.servePprof, after auth, -// for platforms where we want to link it in. -var servePprofFunc func(http.ResponseWriter, *http.Request) - -func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) { - // Require write access out of paranoia that the profile dump - // might contain something sensitive. - if !h.PermitWrite { - http.Error(w, "profile access denied", http.StatusForbidden) - return - } - if servePprofFunc == nil { - http.Error(w, "not implemented on this platform", http.StatusServiceUnavailable) - return - } - servePprofFunc(w, r) -} - // disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that // node no longer communicates with control. Doing this makes control consider this node inactive. This can be used // before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the diff --git a/ipn/localapi/pprof.go b/ipn/localapi/pprof.go index 8c9429b31..d45e58810 100644 --- a/ipn/localapi/pprof.go +++ b/ipn/localapi/pprof.go @@ -7,22 +7,3 @@ // there's no CLI to get at the results anyway. package localapi - -import ( - "net/http" - "net/http/pprof" -) - -func init() { - servePprofFunc = servePprof -} - -func servePprof(w http.ResponseWriter, r *http.Request) { - name := r.FormValue("name") - switch name { - case "profile": - pprof.Profile(w, r) - default: - pprof.Handler(name).ServeHTTP(w, r) - } -} diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index a3cbb81b0..e1cd6f1b6 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -9,7 +9,6 @@ import ( "fmt" "log" "net/netip" - "runtime/pprof" "strings" "sync" "time" @@ -88,7 +87,6 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { return err case <-t.C: buf := new(strings.Builder) - pprof.Lookup("goroutine").WriteTo(buf, 1) e.logf("wgengine watchdog stacks:\n%s", buf.String()) // Collect the list of in-flight operations for debugging.