mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 12:16:44 +02:00
cmd/tailscale/cli: add routecheck command
Introduce a new `tailscale routecheck` command which prints a report of high-availability routers that are reachable. This command rhymes with the `tailscale netcheck` command and but instead of reporting on local network conditions, `routecheck` reports on remote connectivity. Updates #17366 Updates tailscale/corp#33033 Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
parent
0af7dc12ba
commit
8c714b2ac5
@ -211,6 +211,7 @@ func noDupFlagify(c *ffcli.Command) {
|
||||
var (
|
||||
fileCmd,
|
||||
sysPolicyCmd,
|
||||
maybeRoutecheckCmd,
|
||||
maybeWebCmd,
|
||||
maybeDriveCmd,
|
||||
maybeNetlockCmd,
|
||||
@ -252,6 +253,7 @@ change in the future.
|
||||
configureCmd(),
|
||||
nilOrCall(sysPolicyCmd),
|
||||
netcheckCmd,
|
||||
nilOrCall(maybeRoutecheckCmd),
|
||||
ipCmd,
|
||||
dnsCmd,
|
||||
statusCmd,
|
||||
|
||||
@ -143,7 +143,7 @@ func runNetcheck(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcheck: %w", err)
|
||||
}
|
||||
if err := printReport(dm, report); err != nil {
|
||||
if err := printNetCheckReport(dm, report); err != nil {
|
||||
return err
|
||||
}
|
||||
if netcheckArgs.every == 0 {
|
||||
@ -153,7 +153,7 @@ func runNetcheck(ctx context.Context, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
||||
func printNetCheckReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
|
||||
var j []byte
|
||||
var err error
|
||||
switch netcheckArgs.format {
|
||||
|
||||
101
cmd/tailscale/cli/routecheck.go
Normal file
101
cmd/tailscale/cli/routecheck.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_routecheck
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/ipn/routecheck"
|
||||
)
|
||||
|
||||
func init() {
|
||||
maybeRoutecheckCmd = routecheckCmd
|
||||
}
|
||||
|
||||
var routecheckCmd = func() *ffcli.Command {
|
||||
return &ffcli.Command{
|
||||
Name: "routecheck",
|
||||
ShortUsage: "tailscale routecheck",
|
||||
ShortHelp: "Print a reachability report for routes with multiple paths",
|
||||
Exec: runRoutecheck,
|
||||
FlagSet: routecheckFlagSet,
|
||||
}
|
||||
}
|
||||
|
||||
var routecheckFlagSet = func() *flag.FlagSet {
|
||||
fs := newFlagSet("routecheck")
|
||||
fs.BoolVar(&routecheckArgs.force, "force", false, "force probe to generate a new reachability report")
|
||||
fs.StringVar(&routecheckArgs.format, "format", "", `output format: empty (for human-readable), "json" or "json-line"`)
|
||||
return fs
|
||||
}()
|
||||
|
||||
var routecheckArgs struct {
|
||||
force bool
|
||||
format string
|
||||
}
|
||||
|
||||
func runRoutecheck(ctx context.Context, args []string) error {
|
||||
rp, err := localClient.RouteCheck(ctx, routecheckArgs.force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("routecheck: %w", err)
|
||||
}
|
||||
if err := printRouteCheckReport(rp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printRouteCheckReport(rp *routecheck.Report) error {
|
||||
var enc *jsontext.Encoder
|
||||
switch routecheckArgs.format {
|
||||
case "":
|
||||
case "json":
|
||||
enc = jsontext.NewEncoder(Stdout, jsontext.WithIndent("\t"))
|
||||
case "json-line":
|
||||
enc = jsontext.NewEncoder(Stdout, jsontext.Multiline(false))
|
||||
default:
|
||||
return fmt.Errorf("unknown output format %q", routecheckArgs.format)
|
||||
}
|
||||
|
||||
routes := rp.RoutablePrefixes()
|
||||
|
||||
if enc != nil {
|
||||
out := struct {
|
||||
Done time.Time `json:"done"`
|
||||
Routes routecheck.RoutingTable `json:"routes"`
|
||||
}{
|
||||
Done: rp.Done,
|
||||
Routes: routes,
|
||||
}
|
||||
if err := jsonv2.MarshalEncode(enc, out); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := Stdout.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(Stdout, 10, 5, 5, ' ', 0)
|
||||
defer w.Flush()
|
||||
fmt.Fprintf(w, "\nReachable routers at %s:\n", rp.Done.UTC().Format(time.DateTime+"Z07:00"))
|
||||
fmt.Fprintf(w, "\n %s\t%s\t%s\t", "PREFIX", "IP", "HOSTNAME")
|
||||
for prefix, nodes := range routes {
|
||||
for _, n := range nodes {
|
||||
fmt.Fprintf(w, "\n %s\t%s\t%s\t", prefix, n.Addr, strings.Trim(n.Name, "."))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
return nil
|
||||
}
|
||||
@ -201,7 +201,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/ipn from tailscale.com/client/local+
|
||||
tailscale.com/ipn/conffile from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/ipn/ipnstate from tailscale.com/client/local+
|
||||
tailscale.com/ipn/routecheck from tailscale.com/client/local
|
||||
tailscale.com/ipn/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/kube/kubetypes from tailscale.com/envknob
|
||||
tailscale.com/licenses from tailscale.com/client/web+
|
||||
tailscale.com/metrics from tailscale.com/tsweb+
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user