diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 1c9e25993..83afce82c 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -32,6 +32,7 @@ func ActLikeCLI() bool { } switch os.Args[1] { case "up", "status", "netcheck", "ping", "version", + "debug", "-V", "--version", "-h", "--help": return true } @@ -66,6 +67,11 @@ change in the future. Exec: func(context.Context, []string) error { return flag.ErrHelp }, } + // Don't advertise the debug command, but it exists. + if strSliceContains(args, "debug") { + rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd) + } + if err := rootCmd.Parse(args); err != nil { return err } @@ -127,3 +133,12 @@ func pump(ctx context.Context, bc *ipn.BackendClient, conn net.Conn) { bc.GotNotifyMsg(msg) } } + +func strSliceContains(ss []string, s string) bool { + for _, v := range ss { + if v == s { + return true + } + } + return false +} diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go new file mode 100644 index 000000000..fff8b388a --- /dev/null +++ b/cmd/tailscale/cli/debug.go @@ -0,0 +1,67 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cli + +import ( + "context" + "encoding/json" + "errors" + "flag" + "log" + "os" + + "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/net/interfaces" + "tailscale.com/wgengine/monitor" +) + +var debugCmd = &ffcli.Command{ + Name: "debug", + Exec: runDebug, + FlagSet: (func() *flag.FlagSet { + fs := flag.NewFlagSet("debug", flag.ExitOnError) + fs.BoolVar(&debugArgs.monitor, "monitor", false, "") + return fs + })(), +} + +var debugArgs struct { + monitor bool +} + +func runDebug(ctx context.Context, args []string) error { + if len(args) > 0 { + return errors.New("unknown arguments") + } + if debugArgs.monitor { + return runMonitor(ctx) + } + return errors.New("only --monitor is available at the moment") +} + +func runMonitor(ctx context.Context) error { + dump := func() { + st, err := interfaces.GetState() + if err != nil { + log.Printf("error getting state: %v", err) + return + } + j, _ := json.MarshalIndent(st, "", " ") + os.Stderr.Write(j) + } + mon, err := monitor.New(log.Printf, func() { + log.Printf("Link monitor fired. State:") + dump() + }) + if err != nil { + return err + } + log.Printf("Starting link change monitor; initial state:") + dump() + mon.Start() + log.Printf("Started link change monitor; waiting...") + select {} + return nil +}