From 78654ee1bd9f55196be20ed7ca28098759b4eb94 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 28 Feb 2020 00:08:57 -0800 Subject: [PATCH] cmd/tailscale: switch to an ffcli based CLI. Two commands for now, `up` and `netcheck`. The commands and the flags they take will change a bunch in the future, but this is good enough to launch on parity with relaynode. Signed-Off-By: David Anderson --- cmd/tailscale/netcheck.go | 10 ++-- cmd/tailscale/tailscale.go | 106 ++++++++++++++++++++++++++++--------- go.mod | 3 ++ go.sum | 8 +++ 4 files changed, 96 insertions(+), 31 deletions(-) diff --git a/cmd/tailscale/netcheck.go b/cmd/tailscale/netcheck.go index 3dde45ac5..5cd529f1a 100644 --- a/cmd/tailscale/netcheck.go +++ b/cmd/tailscale/netcheck.go @@ -11,16 +11,11 @@ import ( "sort" "time" - "github.com/pborman/getopt/v2" "tailscale.com/netcheck" ) -func isSubcommand(cmd string) bool { - return len(getopt.Args()) == 1 && getopt.Args()[0] == cmd -} - -func netcheckCmd() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) +func runNetcheck(ctx context.Context, args []string) error { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() report, err := netcheck.GetReport(ctx, log.Printf) if err != nil { @@ -39,4 +34,5 @@ func netcheckCmd() { for _, s := range ss { fmt.Printf("\t\t- %s = %v\n", s, report.DERPLatency[s]) } + return nil } diff --git a/cmd/tailscale/tailscale.go b/cmd/tailscale/tailscale.go index df34b3d92..144b7f1fb 100644 --- a/cmd/tailscale/tailscale.go +++ b/cmd/tailscale/tailscale.go @@ -8,15 +8,18 @@ package main // import "tailscale.com/cmd/tailscale" import ( "context" + "flag" "fmt" "log" "net" "os" "os/signal" + "strings" "syscall" "github.com/apenwarr/fixconsole" "github.com/pborman/getopt/v2" + "github.com/peterbourgon/ff/v2/ffcli" "github.com/tailscale/wireguard-go/wgcfg" "tailscale.com/ipn" "tailscale.com/logpolicy" @@ -52,45 +55,98 @@ func main() { log.Printf("fixConsoleOutput: %v\n", err) } - socket := getopt.StringLong("socket", 0, "/run/tailscale/tailscaled.sock", "path of tailscaled's unix socket") - server := getopt.StringLong("server", 's', "https://login.tailscale.com", "URL to tailcontrol server") - nuroutes := getopt.BoolLong("no-single-routes", 'N', "disallow (non-subnet) routes to single nodes") - routeall := getopt.BoolLong("remote-routes", 'R', "accept routes advertised by remote nodes") - nopf := getopt.BoolLong("no-packet-filter", 'F', "disable packet filter") - advroutes := getopt.ListLong("routes", 'r', "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.1.0/24)") - getopt.Parse() - // TODO(bradfitz): move this into a proper subcommand system when we have that. - if isSubcommand("netcheck") { - netcheckCmd() - return + upf := flag.NewFlagSet("up", flag.ExitOnError) + upf.StringVar(&upArgs.socket, "socket", "/run/tailscale/tailscaled.sock", "path to tailscaled's unix socket") + upf.StringVar(&upArgs.server, "login-server", "https://login.tailscale.com", "base URL of control server") + upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes") + upf.BoolVar(&upArgs.noSingleRoutes, "no-single-routes", false, "don't install routes to single nodes") + upf.BoolVar(&upArgs.noPacketFilter, "no-packet-filter", false, "disable packet filter") + upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)") + upCmd := &ffcli.Command{ + Name: "up", + ShortUsage: "up [flags]", + ShortHelp: "Connect to your Tailscale network", + + LongHelp: strings.TrimSpace(` +"tailscale up" connects this machine to your Tailscale network, +triggering authentication if necessary. + +The flags passed to this command set tailscaled options that are +specific to this machine, such as whether to advertise some routes to +other nodes in the Tailscale network. If you don't specify any flags, +options are reset to their default. +`), + FlagSet: upf, + Exec: runUp, + } + + netcheckCmd := &ffcli.Command{ + Name: "netcheck", + ShortUsage: "netcheck", + ShortHelp: "Print an analysis of local network conditions", + Exec: runNetcheck, } + + rootCmd := &ffcli.Command{ + Name: "tailscale", + ShortUsage: "tailscale subcommand [flags]", + ShortHelp: "The easiest, most secure way to use WireGuard.", + LongHelp: strings.TrimSpace(` +This CLI is still under active development. Commands and flags will +change in the future. +`), + Subcommands: []*ffcli.Command{ + upCmd, + netcheckCmd, + }, + Exec: func(context.Context, []string) error { return flag.ErrHelp }, + } + + if err := rootCmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && err != flag.ErrHelp { + log.Fatal(err) + } +} + +var upArgs = struct { + socket string + server string + acceptRoutes bool + noSingleRoutes bool + noPacketFilter bool + advertiseRoutes string +}{} + +func runUp(ctx context.Context, args []string) error { pol := logpolicy.New("tailnode.log.tailscale.io") - if len(getopt.Args()) > 0 { + if len(args) > 0 { log.Fatalf("too many non-flag arguments: %#v", getopt.Args()[0]) } defer pol.Close() var adv []wgcfg.CIDR - for _, s := range *advroutes { - cidr, err := wgcfg.ParseCIDR(s) - if err != nil { - log.Fatalf("%q is not a valid CIDR prefix: %v", s, err) + if upArgs.advertiseRoutes != "" { + advroutes := strings.Split(upArgs.advertiseRoutes, ",") + for _, s := range advroutes { + cidr, err := wgcfg.ParseCIDR(s) + if err != nil { + log.Fatalf("%q is not a valid CIDR prefix: %v", s, err) + } + adv = append(adv, *cidr) } - adv = append(adv, *cidr) } // TODO(apenwarr): fix different semantics between prefs and uflags // TODO(apenwarr): allow setting/using CorpDNS prefs := ipn.NewPrefs() - prefs.ControlURL = *server + prefs.ControlURL = upArgs.server prefs.WantRunning = true - prefs.RouteAll = *routeall - prefs.AllowSingleHosts = !*nuroutes - prefs.UsePacketFilter = !*nopf + prefs.RouteAll = upArgs.acceptRoutes + prefs.AllowSingleHosts = !upArgs.noSingleRoutes + prefs.UsePacketFilter = !upArgs.noPacketFilter prefs.AdvertiseRoutes = adv - c, err := safesocket.Connect(*socket, 0) + c, err := safesocket.Connect(upArgs.socket, 0) if err != nil { log.Fatalf("safesocket.Connect: %v\n", err) } @@ -98,7 +154,7 @@ func main() { ipn.WriteMsg(c, b) } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() go func() { @@ -121,7 +177,7 @@ func main() { case ipn.NeedsLogin: bc.StartLoginInteractive() case ipn.NeedsMachineAuth: - fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", *server) + fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", upArgs.server) case ipn.Starting, ipn.Running: // Done full authentication process fmt.Fprintf(os.Stderr, "\ntailscaled is authenticated, nothing more to do.\n\n") @@ -142,4 +198,6 @@ func main() { // Windows/Mac state is moved into backend. bc.Start(opts) pump(ctx, bc, c) + + return nil } diff --git a/go.mod b/go.mod index b3e0d70f0..f1bb14f3b 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,8 @@ require ( github.com/kr/pty v1.1.1 github.com/mdlayher/netlink v1.1.0 github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 + github.com/peterbourgon/ff v1.7.0 + github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3 // indirect github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f github.com/tailscale/wireguard-go v0.0.0-20200225215529-3ec48fad1002 @@ -27,3 +29,4 @@ require ( honnef.co/go/tools v0.0.1-2019.2.3 // indirect rsc.io/goversion v1.2.0 ) + diff --git a/go.sum b/go.sum index 18b829a07..b44bb9c83 100644 --- a/go.sum +++ b/go.sum @@ -61,10 +61,16 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 h1:YtFkrqsMEj7YqpIhRteVxJxCeC3jJBieuLr0d4C4rSA= github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/peterbourgon/ff v1.7.0 h1:hknvTgsh90jNBIjPq7xeq32Y9AmSbpXvjrFW4sJwW+A= +github.com/peterbourgon/ff v1.7.0/go.mod h1:/KKxnU5cBj4w21jEMj4Rway/kslRP6XAOHh7CH8AyAM= +github.com/peterbourgon/ff/v2 v2.0.0 h1:lx0oYI5qr/FU1xnpNhQ+EZM04gKgn46jyYvGEEqBBbY= +github.com/peterbourgon/ff/v2 v2.0.0/go.mod h1:xjwr+t+SjWm4L46fcj/D+Ap+6ME7+HqFzaP22pP5Ggk= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -150,6 +156,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= @@ -162,6 +169,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gortc.io/stun v1.22.1 h1:96mOdDATYRqhYB+TZdenWBg4CzL2Ye5kPyBXQ8KAB+8=