diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index c0d4a9bf5..9f2811250 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -240,6 +240,16 @@ func BugReport(ctx context.Context, note string) (string, error) { return strings.TrimSpace(string(body)), nil } +// DebugAction invokes a debug action, such as "rebind" or "restun". +// These are development tools and subject to change or removal over time. +func DebugAction(ctx context.Context, action string) error { + body, err := send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil) + if err != nil { + return fmt.Errorf("error %w: %s", err, body) + } + return nil +} + // Status returns the Tailscale daemon's status. func Status(ctx context.Context) (*ipnstate.Status, error) { return status(ctx, "") diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index f7993e5b4..8d585168f 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -70,6 +70,16 @@ var debugCmd = &ffcli.Command{ Exec: runLocalCreds, ShortHelp: "print how to access Tailscale local API", }, + { + Name: "restun", + Exec: localAPIAction("restun"), + ShortHelp: "force a magicsock restun", + }, + { + Name: "rebind", + Exec: localAPIAction("rebind"), + ShortHelp: "force a magicsock rebind", + }, { Name: "prefs", Exec: runPrefs, @@ -244,6 +254,15 @@ func runDERPMap(ctx context.Context, args []string) error { return nil } +func localAPIAction(action string) func(context.Context, []string) error { + return func(ctx context.Context, args []string) error { + if len(args) > 0 { + return errors.New("unexpected arguments") + } + return tailscale.DebugAction(ctx, action) + } +} + func runEnv(ctx context.Context, args []string) error { for _, e := range os.Environ() { outln(e)