diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index b17a3a4fd..48d342998 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -55,6 +55,7 @@ "tailscale.com/version/distro" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg/nmcfg" @@ -3138,3 +3139,33 @@ func exitNodeCanProxyDNS(nm *netmap.NetworkMap, exitNodeID tailcfg.StableNodeID) } return "", false } + +func (b *LocalBackend) DebugRebind() error { + mc, err := b.magicConn() + if err != nil { + return err + } + mc.Rebind() + return nil +} + +func (b *LocalBackend) DebugReSTUN() error { + mc, err := b.magicConn() + if err != nil { + return err + } + mc.ReSTUN("explicit-debug") + return nil +} + +func (b *LocalBackend) magicConn() (*magicsock.Conn, error) { + ig, ok := b.e.(wgengine.InternalsGetter) + if !ok { + return nil, errors.New("engine isn't InternalsGetter") + } + _, mc, ok := ig.GetInternals() + if !ok { + return nil, errors.New("failed to get internals") + } + return mc, nil +} diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 727386af0..713c13f00 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -113,6 +113,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.serveDERPMap(w, r) case "/localapi/v0/metrics": h.serveMetrics(w, r) + case "/localapi/v0/debug": + h.serveDebug(w, r) case "/": io.WriteString(w, "tailscaled\n") default: @@ -195,6 +197,35 @@ func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) { clientmetric.WritePrometheusExpositionFormat(w) } +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 + } + action := r.FormValue("action") + var err error + switch action { + case "rebind": + err = h.b.DebugRebind() + case "restun": + err = h.b.DebugReSTUN() + case "": + err = fmt.Errorf("missing parameter 'action'") + default: + err = fmt.Errorf("unknown action %q", action) + } + if err != nil { + http.Error(w, err.Error(), 400) + return + } + w.Header().Set("Content-Type", "text/plain") + io.WriteString(w, "done\n") +} + // serveProfileFunc is the implementation of Handler.serveProfile, after auth, // for platforms where we want to link it in. var serveProfileFunc func(http.ResponseWriter, *http.Request)