diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 3bbdb1a41..1d7f95766 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -64,6 +64,7 @@ change in the future. upCmd, downCmd, netcheckCmd, + ipCmd, statusCmd, pingCmd, versionCmd, diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go new file mode 100644 index 000000000..053ea165e --- /dev/null +++ b/cmd/tailscale/cli/ip.go @@ -0,0 +1,69 @@ +// 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" + "errors" + "flag" + "fmt" + + "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/client/tailscale" +) + +var ipCmd = &ffcli.Command{ + Name: "ip", + ShortUsage: "ip [-4] [-6]", + ShortHelp: "Show this machine's current Tailscale IP address(es)", + Exec: runIP, + FlagSet: (func() *flag.FlagSet { + fs := flag.NewFlagSet("ip", flag.ExitOnError) + fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address") + fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address") + return fs + })(), +} + +var ipArgs struct { + want4 bool + want6 bool +} + +func runIP(ctx context.Context, args []string) error { + if len(args) > 0 { + return errors.New("unknown arguments") + } + v4, v6 := ipArgs.want4, ipArgs.want6 + if v4 && v6 { + return errors.New("tailscale up -4 and -6 are mutually exclusive") + } + if !v4 && !v6 { + v4, v6 = true, true + } + st, err := tailscale.Status(ctx) + if err != nil { + return err + } + if len(st.TailscaleIPs) == 0 { + return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState) + } + match := false + for _, ip := range st.TailscaleIPs { + if ip.Is4() && v4 || ip.Is6() && v6 { + match = true + fmt.Println(ip) + } + } + if !match { + if ipArgs.want4 { + return errors.New("no Tailscale IPv4 address") + } + if ipArgs.want6 { + return errors.New("no Tailscale IPv6 address") + } + } + return nil +}