|
|
|
|
@ -70,12 +70,28 @@ func runSSH(ctx context.Context, args []string) error { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
prefs, err := localClient.GetPrefs(ctx) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// hostForSSH is the hostname we'll tell OpenSSH we're
|
|
|
|
|
// connecting to, so we have to maintain fewer entries in the
|
|
|
|
|
// known_hosts files.
|
|
|
|
|
hostForSSH := host |
|
|
|
|
if v, ok := nodeDNSNameFromArg(st, host); ok { |
|
|
|
|
hostForSSH = v |
|
|
|
|
ps, ok := peerStatusFromArg(st, host) |
|
|
|
|
if ok { |
|
|
|
|
hostForSSH = ps.DNSName |
|
|
|
|
|
|
|
|
|
// If MagicDNS isn't enabled on the client,
|
|
|
|
|
// we will use the first IPv4 we know about
|
|
|
|
|
// or fallback to the first IPv6 address
|
|
|
|
|
if !prefs.CorpDNS { |
|
|
|
|
ipHost, found := ipFromPeerStatus(ps) |
|
|
|
|
if found { |
|
|
|
|
hostForSSH = ipHost |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ssh, err := findSSH() |
|
|
|
|
@ -169,11 +185,40 @@ func genKnownHosts(st *ipnstate.Status) []byte { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
fmt.Fprintf(&buf, "%s %s\n", ps.DNSName, hostKey) |
|
|
|
|
for _, ip := range ps.TailscaleIPs { |
|
|
|
|
fmt.Fprintf(&buf, "%s %s\n", ip.String(), hostKey) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return buf.Bytes() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// peerStatusFromArg returns the PeerStatus that matches
|
|
|
|
|
// the input arg which can be a base name, full DNS name, or an IP.
|
|
|
|
|
func peerStatusFromArg(st *ipnstate.Status, arg string) (*ipnstate.PeerStatus, bool) { |
|
|
|
|
if arg == "" { |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
argIP, _ := netip.ParseAddr(arg) |
|
|
|
|
for _, ps := range st.Peer { |
|
|
|
|
if argIP.IsValid() { |
|
|
|
|
for _, ip := range ps.TailscaleIPs { |
|
|
|
|
if ip == argIP { |
|
|
|
|
return ps, true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
if strings.EqualFold(strings.TrimSuffix(arg, "."), strings.TrimSuffix(ps.DNSName, ".")) { |
|
|
|
|
return ps, true |
|
|
|
|
} |
|
|
|
|
if base, _, ok := strings.Cut(ps.DNSName, "."); ok && strings.EqualFold(base, arg) { |
|
|
|
|
return ps, true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// nodeDNSNameFromArg returns the PeerStatus.DNSName value from a peer
|
|
|
|
|
// in st that matches the input arg which can be a base name, full
|
|
|
|
|
// DNS name, or an IP.
|
|
|
|
|
@ -202,6 +247,20 @@ func nodeDNSNameFromArg(st *ipnstate.Status, arg string) (dnsName string, ok boo |
|
|
|
|
return "", false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func ipFromPeerStatus(ps *ipnstate.PeerStatus) (string, bool) { |
|
|
|
|
if len(ps.TailscaleIPs) < 1 { |
|
|
|
|
return "", false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Look for a IPv4 address or default to the first IP of the list
|
|
|
|
|
for _, ip := range ps.TailscaleIPs { |
|
|
|
|
if ip.Is4() { |
|
|
|
|
return ip.String(), true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return ps.TailscaleIPs[0].String(), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getSSHClientEnvVar returns the "SSH_CLIENT" environment variable
|
|
|
|
|
// for the current process group, if any.
|
|
|
|
|
var getSSHClientEnvVar = func() string { |
|
|
|
|
|