summaryrefslogtreecommitdiffhomepage
path: root/cmd/tailscaled/cli/netcheck.go
diff options
context:
space:
mode:
authorDenton Gentry <dgentry@tailscale.com>2021-05-17 08:15:50 -0700
committerDenton Gentry <dgentry@tailscale.com>2021-05-17 08:16:50 -0700
commit1dc90404f32e9f4437e188109622dc598cc185cb (patch)
tree0b853c62f1b28589de037cd975f95f3e64e4e7f3 /cmd/tailscaled/cli/netcheck.go
parent25df067dd0c854eebcd2841b82ad92ebb1d77165 (diff)
downloadtailscale-onebinary.tar.xz
tailscale-onebinary.zip
cmd/tailscale{,d}: combine into a single binaryonebinary
To reduce size, combine tailscaled and tailscale into a single binary which will figure out what it should do based on argv[0]. Signed-off-by: Denton Gentry <dgentry@tailscale.com>
Diffstat (limited to 'cmd/tailscaled/cli/netcheck.go')
-rw-r--r--cmd/tailscaled/cli/netcheck.go178
1 files changed, 178 insertions, 0 deletions
diff --git a/cmd/tailscaled/cli/netcheck.go b/cmd/tailscaled/cli/netcheck.go
new file mode 100644
index 000000000..09ce664cd
--- /dev/null
+++ b/cmd/tailscaled/cli/netcheck.go
@@ -0,0 +1,178 @@
+// 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"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/peterbourgon/ff/v2/ffcli"
+ "tailscale.com/derp/derpmap"
+ "tailscale.com/net/netcheck"
+ "tailscale.com/net/portmapper"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/logger"
+)
+
+var netcheckCmd = &ffcli.Command{
+ Name: "netcheck",
+ ShortUsage: "netcheck",
+ ShortHelp: "Print an analysis of local network conditions",
+ Exec: runNetcheck,
+ FlagSet: (func() *flag.FlagSet {
+ fs := flag.NewFlagSet("netcheck", flag.ExitOnError)
+ fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`)
+ fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency")
+ fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs")
+ return fs
+ })(),
+}
+
+var netcheckArgs struct {
+ format string
+ every time.Duration
+ verbose bool
+}
+
+func runNetcheck(ctx context.Context, args []string) error {
+ c := &netcheck.Client{
+ UDPBindAddr: os.Getenv("TS_DEBUG_NETCHECK_UDP_BIND"),
+ PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: ")),
+ }
+ if netcheckArgs.verbose {
+ c.Logf = logger.WithPrefix(log.Printf, "netcheck: ")
+ c.Verbose = true
+ } else {
+ c.Logf = logger.Discard
+ }
+
+ if strings.HasPrefix(netcheckArgs.format, "json") {
+ fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
+ }
+
+ dm := derpmap.Prod()
+ for {
+ t0 := time.Now()
+ report, err := c.GetReport(ctx, dm)
+ d := time.Since(t0)
+ if netcheckArgs.verbose {
+ c.Logf("GetReport took %v; err=%v", d.Round(time.Millisecond), err)
+ }
+ if err != nil {
+ log.Fatalf("netcheck: %v", err)
+ }
+ if err := printReport(dm, report); err != nil {
+ return err
+ }
+ if netcheckArgs.every == 0 {
+ return nil
+ }
+ time.Sleep(netcheckArgs.every)
+ }
+}
+
+func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
+ var j []byte
+ var err error
+ switch netcheckArgs.format {
+ case "":
+ break
+ case "json":
+ j, err = json.MarshalIndent(report, "", "\t")
+ case "json-line":
+ j, err = json.Marshal(report)
+ default:
+ return fmt.Errorf("unknown output format %q", netcheckArgs.format)
+ }
+ if err != nil {
+ return err
+ }
+ if j != nil {
+ j = append(j, '\n')
+ os.Stdout.Write(j)
+ return nil
+ }
+
+ fmt.Printf("\nReport:\n")
+ fmt.Printf("\t* UDP: %v\n", report.UDP)
+ if report.GlobalV4 != "" {
+ fmt.Printf("\t* IPv4: yes, %v\n", report.GlobalV4)
+ } else {
+ fmt.Printf("\t* IPv4: (no addr found)\n")
+ }
+ if report.GlobalV6 != "" {
+ fmt.Printf("\t* IPv6: yes, %v\n", report.GlobalV6)
+ } else if report.IPv6 {
+ fmt.Printf("\t* IPv6: (no addr found)\n")
+ } else {
+ fmt.Printf("\t* IPv6: no\n")
+ }
+ fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
+ fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
+ fmt.Printf("\t* PortMapping: %v\n", portMapping(report))
+
+ // When DERP latency checking failed,
+ // magicsock will try to pick the DERP server that
+ // most of your other nodes are also using
+ if len(report.RegionLatency) == 0 {
+ fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
+ } else {
+ fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
+ fmt.Printf("\t* DERP latency:\n")
+ var rids []int
+ for rid := range dm.Regions {
+ rids = append(rids, rid)
+ }
+ sort.Slice(rids, func(i, j int) bool {
+ l1, ok1 := report.RegionLatency[rids[i]]
+ l2, ok2 := report.RegionLatency[rids[j]]
+ if ok1 != ok2 {
+ return ok1 // defined things sort first
+ }
+ if !ok1 {
+ return rids[i] < rids[j]
+ }
+ return l1 < l2
+ })
+ for _, rid := range rids {
+ d, ok := report.RegionLatency[rid]
+ var latency string
+ if ok {
+ latency = d.Round(time.Millisecond / 10).String()
+ }
+ r := dm.Regions[rid]
+ var derpNum string
+ if netcheckArgs.verbose {
+ derpNum = fmt.Sprintf("derp%d, ", rid)
+ }
+ fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
+ }
+ }
+ return nil
+}
+
+func portMapping(r *netcheck.Report) string {
+ if !r.AnyPortMappingChecked() {
+ return "not checked"
+ }
+ var got []string
+ if r.UPnP.EqualBool(true) {
+ got = append(got, "UPnP")
+ }
+ if r.PMP.EqualBool(true) {
+ got = append(got, "NAT-PMP")
+ }
+ if r.PCP.EqualBool(true) {
+ got = append(got, "PCP")
+ }
+ return strings.Join(got, ", ")
+}