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 <dave@natulte.net>
This commit is contained in:
David Anderson 2020-02-28 00:08:57 -08:00 committed by Dave Anderson
parent 4e3ff4b63b
commit 78654ee1bd
4 changed files with 96 additions and 31 deletions

View File

@ -11,16 +11,11 @@
"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
}

View File

@ -8,15 +8,18 @@
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
}

3
go.mod
View File

@ -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
)

8
go.sum
View File

@ -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=