8736fbb754
This provides a mechanism to block, waiting for Tailscale's IP to be ready for a bind/listen, to gate the starting of other services. It also adds a new --assert=[IP] option to "tailscale ip", for services that want extra paranoia about what IP is in use, if they're worried about having switched to the wrong tailnet prior to reboot or something. Updates #3340 Updates #11504 ... and many more, IIRC Change-Id: I88ab19ac5fae58fd8c516065bab685e292395565 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
132 lines
2.8 KiB
Go
132 lines
2.8 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
"github.com/peterbourgon/ff/v3/ffcli"
|
|
"tailscale.com/ipn/ipnstate"
|
|
)
|
|
|
|
var ipCmd = &ffcli.Command{
|
|
Name: "ip",
|
|
ShortUsage: "tailscale ip [-1] [-4] [-6] [peer hostname or ip address]",
|
|
ShortHelp: "Show Tailscale IP addresses",
|
|
LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.",
|
|
Exec: runIP,
|
|
FlagSet: (func() *flag.FlagSet {
|
|
fs := newFlagSet("ip")
|
|
fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address")
|
|
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
|
|
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
|
|
fs.StringVar(&ipArgs.assert, "assert", "", "assert that one of the node's IP(s) matches this IP address")
|
|
return fs
|
|
})(),
|
|
}
|
|
|
|
var ipArgs struct {
|
|
want1 bool
|
|
want4 bool
|
|
want6 bool
|
|
assert string
|
|
}
|
|
|
|
func runIP(ctx context.Context, args []string) error {
|
|
if len(args) > 1 {
|
|
return errors.New("too many arguments, expected at most one peer")
|
|
}
|
|
var of string
|
|
if len(args) == 1 {
|
|
of = args[0]
|
|
}
|
|
|
|
v4, v6 := ipArgs.want4, ipArgs.want6
|
|
nflags := 0
|
|
for _, b := range []bool{ipArgs.want1, v4, v6} {
|
|
if b {
|
|
nflags++
|
|
}
|
|
}
|
|
if nflags > 1 {
|
|
return errors.New("tailscale ip -1, -4, and -6 are mutually exclusive")
|
|
}
|
|
if !v4 && !v6 {
|
|
v4, v6 = true, true
|
|
}
|
|
st, err := localClient.Status(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ips := st.TailscaleIPs
|
|
if ipArgs.assert != "" {
|
|
for _, ip := range ips {
|
|
if ip.String() == ipArgs.assert {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("assertion failed: IP %q not found among %v", ipArgs.assert, ips)
|
|
}
|
|
if of != "" {
|
|
ip, _, err := tailscaleIPFromArg(ctx, of)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peer, ok := peerMatchingIP(st, ip)
|
|
if !ok {
|
|
return fmt.Errorf("no peer found with IP %v", ip)
|
|
}
|
|
ips = peer.TailscaleIPs
|
|
}
|
|
if len(ips) == 0 {
|
|
return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState)
|
|
}
|
|
|
|
if ipArgs.want1 {
|
|
ips = ips[:1]
|
|
}
|
|
match := false
|
|
for _, ip := range ips {
|
|
if ip.Is4() && v4 || ip.Is6() && v6 {
|
|
match = true
|
|
outln(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
|
|
}
|
|
|
|
func peerMatchingIP(st *ipnstate.Status, ipStr string) (ps *ipnstate.PeerStatus, ok bool) {
|
|
ip, err := netip.ParseAddr(ipStr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, ps = range st.Peer {
|
|
for _, pip := range ps.TailscaleIPs {
|
|
if ip == pip {
|
|
return ps, true
|
|
}
|
|
}
|
|
}
|
|
if ps := st.Self; ps != nil {
|
|
for _, pip := range ps.TailscaleIPs {
|
|
if ip == pip {
|
|
return ps, true
|
|
}
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|