2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-03-26 05:57:46 +00:00
|
|
|
|
|
|
|
// Package ipnstate captures the entire state of the Tailscale network.
|
|
|
|
//
|
|
|
|
// It's a leaf package so ipn, wgengine, and magicsock can all depend on it.
|
|
|
|
package ipnstate
|
|
|
|
|
|
|
|
import (
|
2020-03-27 20:26:35 +00:00
|
|
|
"fmt"
|
|
|
|
"html"
|
|
|
|
"io"
|
2020-03-26 05:57:46 +00:00
|
|
|
"log"
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
"net/netip"
|
2020-03-26 05:57:46 +00:00
|
|
|
"sort"
|
2020-03-27 20:26:35 +00:00
|
|
|
"strings"
|
2020-03-26 05:57:46 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/types/key"
|
2023-01-23 20:33:58 +00:00
|
|
|
"tailscale.com/types/ptr"
|
2022-02-22 17:52:49 +00:00
|
|
|
"tailscale.com/types/views"
|
2021-01-26 16:28:34 +00:00
|
|
|
"tailscale.com/util/dnsname"
|
2020-03-26 05:57:46 +00:00
|
|
|
)
|
|
|
|
|
2022-11-30 21:04:43 +00:00
|
|
|
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=TKAFilteredPeer
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
// Status represents the entire state of the IPN network.
|
|
|
|
type Status struct {
|
2021-03-19 04:07:58 +00:00
|
|
|
// Version is the daemon's long version (see version.Long).
|
|
|
|
Version string
|
|
|
|
|
2023-01-13 15:21:08 +00:00
|
|
|
// TUN is whether /dev/net/tun (or equivalent kernel interface) is being
|
|
|
|
// used. If false, it's running in userspace mode.
|
|
|
|
TUN bool
|
|
|
|
|
2021-03-19 04:07:58 +00:00
|
|
|
// BackendState is an ipn.State string value:
|
|
|
|
// "NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped",
|
|
|
|
// "Starting", "Running".
|
2021-01-22 22:28:44 +00:00
|
|
|
BackendState string
|
2021-03-19 04:07:58 +00:00
|
|
|
|
2021-03-12 14:43:33 +00:00
|
|
|
AuthURL string // current URL provided by control to authorize client
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
TailscaleIPs []netip.Addr // Tailscale IP(s) assigned to this node
|
2021-01-22 22:28:44 +00:00
|
|
|
Self *PeerStatus
|
|
|
|
|
2022-06-07 19:31:10 +00:00
|
|
|
// ExitNodeStatus describes the current exit node.
|
|
|
|
// If nil, an exit node is not in use.
|
|
|
|
ExitNodeStatus *ExitNodeStatus `json:"ExitNodeStatus,omitempty"`
|
|
|
|
|
2021-09-02 02:27:22 +00:00
|
|
|
// Health contains health check problems.
|
|
|
|
// Empty means everything is good. (or at least that no known
|
|
|
|
// problems are detected)
|
|
|
|
Health []string
|
|
|
|
|
2022-02-15 17:36:01 +00:00
|
|
|
// This field is the legacy name of CurrentTailnet.MagicDNSSuffix.
|
|
|
|
//
|
|
|
|
// Deprecated: use CurrentTailnet.MagicDNSSuffix instead.
|
2021-01-22 22:28:44 +00:00
|
|
|
MagicDNSSuffix string
|
2020-08-25 23:26:10 +00:00
|
|
|
|
2022-02-15 17:36:01 +00:00
|
|
|
// CurrentTailnet is information about the tailnet that the node
|
|
|
|
// is currently connected to. When not connected, this field is nil.
|
|
|
|
CurrentTailnet *TailnetStatus
|
|
|
|
|
2021-06-15 19:12:15 +00:00
|
|
|
// CertDomains are the set of DNS names for which the control
|
|
|
|
// plane server will assist with provisioning TLS
|
|
|
|
// certificates. See SetDNSRequest for dns-01 ACME challenges
|
|
|
|
// for e.g. LetsEncrypt. These names are FQDNs without
|
|
|
|
// trailing periods, and without any "_acme-challenge." prefix.
|
|
|
|
CertDomains []string
|
|
|
|
|
2021-10-28 23:56:44 +00:00
|
|
|
Peer map[key.NodePublic]*PeerStatus
|
2020-08-25 23:26:10 +00:00
|
|
|
User map[tailcfg.UserID]tailcfg.UserProfile
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 17:25:20 +00:00
|
|
|
// TKAKey describes a key trusted by network lock.
|
|
|
|
type TKAKey struct {
|
|
|
|
Key key.NLPublic
|
|
|
|
Metadata map[string]string
|
|
|
|
Votes uint
|
|
|
|
}
|
|
|
|
|
2022-11-30 21:04:43 +00:00
|
|
|
// TKAFilteredPeer describes a peer which was removed from the netmap
|
|
|
|
// (i.e. no connectivity) because it failed tailnet lock
|
|
|
|
// checks.
|
|
|
|
type TKAFilteredPeer struct {
|
|
|
|
Name string // DNS
|
|
|
|
ID tailcfg.NodeID
|
|
|
|
StableID tailcfg.StableNodeID
|
|
|
|
TailscaleIPs []netip.Addr // Tailscale IP(s) assigned to this node
|
2023-03-08 22:16:23 +00:00
|
|
|
NodeKey key.NodePublic
|
2022-11-30 21:04:43 +00:00
|
|
|
}
|
|
|
|
|
2022-08-11 17:43:09 +00:00
|
|
|
// NetworkLockStatus represents whether network-lock is enabled,
|
|
|
|
// along with details about the locally-known state of the tailnet
|
|
|
|
// key authority.
|
|
|
|
type NetworkLockStatus struct {
|
|
|
|
// Enabled is true if network lock is enabled.
|
|
|
|
Enabled bool
|
|
|
|
|
|
|
|
// Head describes the AUM hash of the leaf AUM. Head is nil
|
|
|
|
// if network lock is not enabled.
|
|
|
|
Head *[32]byte
|
|
|
|
|
2022-11-03 17:25:20 +00:00
|
|
|
// PublicKey describes the node's network-lock public key.
|
2022-11-14 12:29:49 +00:00
|
|
|
// It may be zero if the node has not logged in.
|
2022-08-11 17:43:09 +00:00
|
|
|
PublicKey key.NLPublic
|
2022-11-03 17:25:20 +00:00
|
|
|
|
|
|
|
// NodeKey describes the node's current node-key. This field is not
|
|
|
|
// populated if the node is not operating (i.e. waiting for a login).
|
|
|
|
NodeKey *key.NodePublic
|
|
|
|
|
|
|
|
// NodeKeySigned is true if our node is authorized by network-lock.
|
|
|
|
NodeKeySigned bool
|
|
|
|
|
|
|
|
// TrustedKeys describes the keys currently trusted to make changes
|
|
|
|
// to network-lock.
|
|
|
|
TrustedKeys []TKAKey
|
2022-11-30 21:04:43 +00:00
|
|
|
|
|
|
|
// FilteredPeers describes peers which were removed from the netmap
|
|
|
|
// (i.e. no connectivity) because they failed tailnet lock
|
|
|
|
// checks.
|
|
|
|
FilteredPeers []*TKAFilteredPeer
|
2023-05-24 20:36:25 +00:00
|
|
|
|
|
|
|
// StateID is a nonce associated with the network lock authority,
|
|
|
|
// generated upon enablement. This field is not populated if the
|
|
|
|
// network lock is disabled.
|
|
|
|
StateID uint64
|
2022-08-11 17:43:09 +00:00
|
|
|
}
|
|
|
|
|
2022-11-14 23:04:10 +00:00
|
|
|
// NetworkLockUpdate describes a change to network-lock state.
|
|
|
|
type NetworkLockUpdate struct {
|
|
|
|
Hash [32]byte
|
|
|
|
Change string // values of tka.AUMKind.String()
|
|
|
|
|
|
|
|
// Raw contains the serialized AUM. The AUM is sent in serialized
|
|
|
|
// form to avoid transitive dependences bloating this package.
|
|
|
|
Raw []byte
|
|
|
|
}
|
|
|
|
|
2022-02-15 17:36:01 +00:00
|
|
|
// TailnetStatus is information about a Tailscale network ("tailnet").
|
|
|
|
type TailnetStatus struct {
|
|
|
|
// Name is the name of the network that's currently in use.
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// MagicDNSSuffix is the network's MagicDNS suffix for nodes
|
|
|
|
// in the network such as "userfoo.tailscale.net".
|
|
|
|
// There are no surrounding dots.
|
|
|
|
// MagicDNSSuffix should be populated regardless of whether a domain
|
|
|
|
// has MagicDNS enabled.
|
|
|
|
MagicDNSSuffix string
|
|
|
|
|
|
|
|
// MagicDNSEnabled is whether or not the network has MagicDNS enabled.
|
|
|
|
// Note that the current device may still not support MagicDNS if
|
|
|
|
// `--accept-dns=false` was used.
|
|
|
|
MagicDNSEnabled bool
|
|
|
|
}
|
|
|
|
|
2022-06-07 19:31:10 +00:00
|
|
|
// ExitNodeStatus describes the current exit node.
|
|
|
|
type ExitNodeStatus struct {
|
|
|
|
// ID is the exit node's ID.
|
|
|
|
ID tailcfg.StableNodeID
|
|
|
|
|
|
|
|
// Online is whether the exit node is alive.
|
|
|
|
Online bool
|
|
|
|
|
|
|
|
// TailscaleIPs are the exit node's IP addresses assigned to the node.
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
TailscaleIPs []netip.Prefix
|
2022-06-07 19:31:10 +00:00
|
|
|
}
|
|
|
|
|
2021-10-28 23:56:44 +00:00
|
|
|
func (s *Status) Peers() []key.NodePublic {
|
|
|
|
kk := make([]key.NodePublic, 0, len(s.Peer))
|
2020-03-26 05:57:46 +00:00
|
|
|
for k := range s.Peer {
|
|
|
|
kk = append(kk, k)
|
|
|
|
}
|
2021-10-28 23:56:44 +00:00
|
|
|
sort.Slice(kk, func(i, j int) bool { return kk[i].Less(kk[j]) })
|
2020-03-26 05:57:46 +00:00
|
|
|
return kk
|
|
|
|
}
|
|
|
|
|
2021-02-04 21:12:42 +00:00
|
|
|
type PeerStatusLite struct {
|
2021-11-22 23:26:54 +00:00
|
|
|
// TxBytes/RxBytes is the total number of bytes transmitted to/received from this peer.
|
2021-02-04 21:12:42 +00:00
|
|
|
TxBytes, RxBytes int64
|
2021-11-22 23:26:54 +00:00
|
|
|
// LastHandshake is the last time a handshake succeeded with this peer.
|
|
|
|
// (Or we got key confirmation via the first data message,
|
|
|
|
// which is approximately the same thing.)
|
|
|
|
LastHandshake time.Time
|
|
|
|
// NodeKey is this peer's public node key.
|
|
|
|
NodeKey key.NodePublic
|
2021-02-04 21:12:42 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
type PeerStatus struct {
|
2023-02-08 23:09:58 +00:00
|
|
|
ID tailcfg.StableNodeID
|
|
|
|
PublicKey key.NodePublic
|
|
|
|
HostName string // HostInfo's Hostname (not a DNS name or necessarily unique)
|
|
|
|
|
|
|
|
// DNSName is the Peer's FQDN. It ends with a dot.
|
|
|
|
// It has the form "host.<MagicDNSSuffix>."
|
|
|
|
DNSName string
|
|
|
|
OS string // HostInfo.OS
|
|
|
|
UserID tailcfg.UserID
|
|
|
|
|
|
|
|
// TailscaleIPs are the IP addresses assigned to the node.
|
|
|
|
TailscaleIPs []netip.Addr
|
2020-03-26 05:57:46 +00:00
|
|
|
|
2022-02-21 22:53:20 +00:00
|
|
|
// Tags are the list of ACL tags applied to this node.
|
|
|
|
// See tailscale.com/tailcfg#Node.Tags for more information.
|
2022-03-17 04:45:19 +00:00
|
|
|
Tags *views.Slice[string] `json:",omitempty"`
|
2022-02-21 22:53:20 +00:00
|
|
|
|
|
|
|
// PrimaryRoutes are the routes this node is currently the primary
|
|
|
|
// subnet router for, as determined by the control plane. It does
|
|
|
|
// not include the IPs in TailscaleIPs.
|
2023-08-18 17:57:04 +00:00
|
|
|
PrimaryRoutes *views.Slice[netip.Prefix] `json:",omitempty"`
|
2022-02-21 22:53:20 +00:00
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
// Endpoints:
|
|
|
|
Addrs []string
|
|
|
|
CurAddr string // one of Addrs, or unique if roaming
|
2020-07-03 20:44:22 +00:00
|
|
|
Relay string // DERP region
|
2020-03-26 05:57:46 +00:00
|
|
|
|
2021-11-30 19:46:12 +00:00
|
|
|
RxBytes int64
|
|
|
|
TxBytes int64
|
|
|
|
Created time.Time // time registered with tailcontrol
|
|
|
|
LastWrite time.Time // time last packet sent
|
2021-12-08 17:10:39 +00:00
|
|
|
LastSeen time.Time // last seen to tailcontrol; only present if offline
|
2021-11-30 19:46:12 +00:00
|
|
|
LastHandshake time.Time // with local wireguard
|
2021-12-08 17:10:39 +00:00
|
|
|
Online bool // whether node is connected to the control plane
|
2023-07-08 04:29:54 +00:00
|
|
|
ExitNode bool // true if this is the currently selected exit node.
|
|
|
|
ExitNodeOption bool // true if this node can be an exit node (offered && approved)
|
2020-03-26 05:57:46 +00:00
|
|
|
|
2021-08-04 15:01:35 +00:00
|
|
|
// Active is whether the node was recently active. The
|
|
|
|
// definition is somewhat undefined but has historically and
|
|
|
|
// currently means that there was some packet sent to this
|
|
|
|
// peer in the past two minutes. That definition is subject to
|
|
|
|
// change.
|
|
|
|
Active bool
|
|
|
|
|
2023-02-08 23:09:58 +00:00
|
|
|
// PeerAPIURL are the URLs of the node's PeerAPI servers.
|
|
|
|
PeerAPIURL []string
|
|
|
|
|
|
|
|
// Capabilities are capabilities that the node has.
|
|
|
|
// They're free-form strings, but should be in the form of URLs/URIs
|
|
|
|
// such as:
|
|
|
|
// "https://tailscale.com/cap/is-admin"
|
|
|
|
// "https://tailscale.com/cap/file-sharing"
|
|
|
|
// "funnel"
|
2021-04-16 17:57:46 +00:00
|
|
|
Capabilities []string `json:",omitempty"`
|
2021-03-25 22:38:40 +00:00
|
|
|
|
2022-03-25 02:44:27 +00:00
|
|
|
// SSH_HostKeys are the node's SSH host keys, if known.
|
|
|
|
SSH_HostKeys []string `json:"sshHostKeys,omitempty"`
|
|
|
|
|
2020-12-01 02:05:51 +00:00
|
|
|
// ShareeNode indicates this node exists in the netmap because
|
|
|
|
// it's owned by a shared-to user and that node might connect
|
|
|
|
// to us. These nodes should be hidden by "tailscale status"
|
|
|
|
// etc by default.
|
|
|
|
ShareeNode bool `json:",omitempty"`
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
// InNetworkMap means that this peer was seen in our latest network map.
|
|
|
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
|
|
|
InNetworkMap bool
|
|
|
|
|
|
|
|
// InMagicSock means that this peer is being tracked by magicsock.
|
|
|
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
|
|
|
InMagicSock bool
|
|
|
|
|
|
|
|
// InEngine means that this peer is tracked by the wireguard engine.
|
|
|
|
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
|
|
|
|
InEngine bool
|
2023-01-13 23:41:11 +00:00
|
|
|
|
|
|
|
// Expired means that this peer's node key has expired, based on either
|
|
|
|
// information from control or optimisically set on the client if the
|
|
|
|
// expiration time has passed.
|
|
|
|
Expired bool `json:",omitempty"`
|
2023-01-23 20:33:58 +00:00
|
|
|
|
|
|
|
// KeyExpiry, if present, is the time at which the node key expired or
|
|
|
|
// will expire.
|
|
|
|
KeyExpiry *time.Time `json:",omitempty"`
|
2023-07-14 04:33:53 +00:00
|
|
|
|
|
|
|
Location *tailcfg.Location `json:",omitempty"`
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type StatusBuilder struct {
|
2022-12-19 18:23:47 +00:00
|
|
|
WantPeers bool // whether caller wants peers
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
locked bool
|
|
|
|
st Status
|
|
|
|
}
|
|
|
|
|
2021-03-25 22:38:40 +00:00
|
|
|
// MutateStatus calls f with the status to mutate.
|
|
|
|
//
|
|
|
|
// It may not assume other fields of status are already populated, and
|
|
|
|
// may not retain or write to the Status after f returns.
|
|
|
|
//
|
|
|
|
// MutateStatus acquires a lock so f must not call back into sb.
|
|
|
|
func (sb *StatusBuilder) MutateStatus(f func(*Status)) {
|
2021-01-10 20:03:01 +00:00
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
2021-03-25 22:38:40 +00:00
|
|
|
f(&sb.st)
|
2021-01-10 20:03:01 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
func (sb *StatusBuilder) Status() *Status {
|
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
|
|
|
sb.locked = true
|
|
|
|
return &sb.st
|
|
|
|
}
|
|
|
|
|
2021-03-25 22:38:40 +00:00
|
|
|
// MutateSelfStatus calls f with the PeerStatus of our own node to mutate.
|
|
|
|
//
|
|
|
|
// It may not assume other fields of status are already populated, and
|
|
|
|
// may not retain or write to the Status after f returns.
|
|
|
|
//
|
|
|
|
// MutateStatus acquires a lock so f must not call back into sb.
|
|
|
|
func (sb *StatusBuilder) MutateSelfStatus(f func(*PeerStatus)) {
|
2020-08-25 23:26:10 +00:00
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
2021-03-25 22:38:40 +00:00
|
|
|
if sb.st.Self == nil {
|
|
|
|
sb.st.Self = new(PeerStatus)
|
|
|
|
}
|
|
|
|
f(sb.st.Self)
|
2020-08-25 23:26:10 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
// AddUser adds a user profile to the status.
|
2020-09-30 04:39:43 +00:00
|
|
|
func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
|
2020-03-26 05:57:46 +00:00
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
|
|
|
if sb.locked {
|
|
|
|
log.Printf("[unexpected] ipnstate: AddUser after Locked")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if sb.st.User == nil {
|
|
|
|
sb.st.User = make(map[tailcfg.UserID]tailcfg.UserProfile)
|
|
|
|
}
|
|
|
|
|
2020-09-30 04:39:43 +00:00
|
|
|
sb.st.User[id] = up
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
|
2020-07-27 20:25:25 +00:00
|
|
|
// AddIP adds a Tailscale IP address to the status.
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
func (sb *StatusBuilder) AddTailscaleIP(ip netip.Addr) {
|
2020-07-27 20:25:25 +00:00
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
|
|
|
if sb.locked {
|
|
|
|
log.Printf("[unexpected] ipnstate: AddIP after Locked")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sb.st.TailscaleIPs = append(sb.st.TailscaleIPs, ip)
|
|
|
|
}
|
|
|
|
|
2020-03-26 05:57:46 +00:00
|
|
|
// AddPeer adds a peer node to the status.
|
|
|
|
//
|
|
|
|
// Its PeerStatus is mixed with any previous status already added.
|
2021-10-28 23:56:44 +00:00
|
|
|
func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
|
2020-03-26 05:57:46 +00:00
|
|
|
if st == nil {
|
|
|
|
panic("nil PeerStatus")
|
|
|
|
}
|
|
|
|
|
|
|
|
sb.mu.Lock()
|
|
|
|
defer sb.mu.Unlock()
|
|
|
|
if sb.locked {
|
|
|
|
log.Printf("[unexpected] ipnstate: AddPeer after Locked")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if sb.st.Peer == nil {
|
2021-10-28 23:56:44 +00:00
|
|
|
sb.st.Peer = make(map[key.NodePublic]*PeerStatus)
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
e, ok := sb.st.Peer[peer]
|
|
|
|
if !ok {
|
|
|
|
sb.st.Peer[peer] = st
|
|
|
|
st.PublicKey = peer
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-07 14:27:14 +00:00
|
|
|
if v := st.ID; v != "" {
|
|
|
|
e.ID = v
|
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
if v := st.HostName; v != "" {
|
|
|
|
e.HostName = v
|
|
|
|
}
|
2020-08-27 20:24:59 +00:00
|
|
|
if v := st.DNSName; v != "" {
|
|
|
|
e.DNSName = v
|
|
|
|
}
|
2020-07-03 20:44:22 +00:00
|
|
|
if v := st.Relay; v != "" {
|
|
|
|
e.Relay = v
|
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
if v := st.UserID; v != 0 {
|
|
|
|
e.UserID = v
|
|
|
|
}
|
2021-04-14 14:20:27 +00:00
|
|
|
if v := st.TailscaleIPs; v != nil {
|
|
|
|
e.TailscaleIPs = v
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
2022-02-22 17:52:49 +00:00
|
|
|
if v := st.PrimaryRoutes; v != nil && !v.IsNil() {
|
2022-02-21 22:53:20 +00:00
|
|
|
e.PrimaryRoutes = v
|
|
|
|
}
|
2022-02-22 17:52:49 +00:00
|
|
|
if v := st.Tags; v != nil && !v.IsNil() {
|
2022-02-21 22:53:20 +00:00
|
|
|
e.Tags = v
|
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
if v := st.OS; v != "" {
|
|
|
|
e.OS = st.OS
|
|
|
|
}
|
2022-03-25 02:44:27 +00:00
|
|
|
if v := st.SSH_HostKeys; v != nil {
|
|
|
|
e.SSH_HostKeys = v
|
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
if v := st.Addrs; v != nil {
|
|
|
|
e.Addrs = v
|
|
|
|
}
|
|
|
|
if v := st.CurAddr; v != "" {
|
|
|
|
e.CurAddr = v
|
|
|
|
}
|
|
|
|
if v := st.RxBytes; v != 0 {
|
|
|
|
e.RxBytes = v
|
|
|
|
}
|
|
|
|
if v := st.TxBytes; v != 0 {
|
|
|
|
e.TxBytes = v
|
|
|
|
}
|
|
|
|
if v := st.LastHandshake; !v.IsZero() {
|
|
|
|
e.LastHandshake = v
|
|
|
|
}
|
|
|
|
if v := st.Created; !v.IsZero() {
|
|
|
|
e.Created = v
|
|
|
|
}
|
|
|
|
if v := st.LastSeen; !v.IsZero() {
|
|
|
|
e.LastSeen = v
|
|
|
|
}
|
2020-07-03 20:44:22 +00:00
|
|
|
if v := st.LastWrite; !v.IsZero() {
|
|
|
|
e.LastWrite = v
|
|
|
|
}
|
2021-12-08 17:10:39 +00:00
|
|
|
if st.Online {
|
|
|
|
e.Online = true
|
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
if st.InNetworkMap {
|
|
|
|
e.InNetworkMap = true
|
|
|
|
}
|
|
|
|
if st.InMagicSock {
|
|
|
|
e.InMagicSock = true
|
|
|
|
}
|
|
|
|
if st.InEngine {
|
|
|
|
e.InEngine = true
|
|
|
|
}
|
2021-02-05 21:07:48 +00:00
|
|
|
if st.ExitNode {
|
|
|
|
e.ExitNode = true
|
|
|
|
}
|
2021-11-30 19:46:12 +00:00
|
|
|
if st.ExitNodeOption {
|
|
|
|
e.ExitNodeOption = true
|
|
|
|
}
|
2020-12-01 02:05:51 +00:00
|
|
|
if st.ShareeNode {
|
|
|
|
e.ShareeNode = true
|
|
|
|
}
|
2021-08-04 15:01:35 +00:00
|
|
|
if st.Active {
|
|
|
|
e.Active = true
|
|
|
|
}
|
2022-11-19 21:10:45 +00:00
|
|
|
if st.PeerAPIURL != nil {
|
|
|
|
e.PeerAPIURL = st.PeerAPIURL
|
|
|
|
}
|
2023-01-13 23:41:11 +00:00
|
|
|
if st.Expired {
|
|
|
|
e.Expired = true
|
|
|
|
}
|
2023-01-23 20:33:58 +00:00
|
|
|
if t := st.KeyExpiry; t != nil {
|
|
|
|
e.KeyExpiry = ptr.To(*t)
|
|
|
|
}
|
2023-07-14 04:33:53 +00:00
|
|
|
e.Location = st.Location
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type StatusUpdater interface {
|
|
|
|
UpdateStatus(*StatusBuilder)
|
|
|
|
}
|
2020-03-27 20:26:35 +00:00
|
|
|
|
|
|
|
func (st *Status) WriteHTML(w io.Writer) {
|
2022-03-16 23:27:57 +00:00
|
|
|
f := func(format string, args ...any) { fmt.Fprintf(w, format, args...) }
|
2020-03-27 20:26:35 +00:00
|
|
|
|
2020-05-01 21:55:16 +00:00
|
|
|
f(`<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
2022-04-04 20:15:43 +00:00
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
2020-05-01 21:55:16 +00:00
|
|
|
<title>Tailscale State</title>
|
|
|
|
<style>
|
|
|
|
body { font-family: monospace; }
|
|
|
|
.owner { text-decoration: underline; }
|
|
|
|
.tailaddr { font-style: italic; }
|
|
|
|
.acenter { text-align: center; }
|
|
|
|
.aright { text-align: right; }
|
|
|
|
table, th, td { border: 1px solid black; border-spacing : 0; border-collapse : collapse; }
|
|
|
|
thead { background-color: #FFA500; }
|
|
|
|
th, td { padding: 5px; }
|
|
|
|
td { vertical-align: top; }
|
|
|
|
table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>Tailscale State</h1>
|
|
|
|
`)
|
|
|
|
|
2020-03-27 20:26:35 +00:00
|
|
|
//f("<p><b>logid:</b> %s</p>\n", logid)
|
|
|
|
//f("<p><b>opts:</b> <code>%s</code></p>\n", html.EscapeString(fmt.Sprintf("%+v", opts)))
|
|
|
|
|
2020-07-27 20:25:25 +00:00
|
|
|
ips := make([]string, 0, len(st.TailscaleIPs))
|
|
|
|
for _, ip := range st.TailscaleIPs {
|
|
|
|
ips = append(ips, ip.String())
|
|
|
|
}
|
|
|
|
f("<p>Tailscale IP: %s", strings.Join(ips, ", "))
|
|
|
|
|
2020-05-01 21:55:16 +00:00
|
|
|
f("<table>\n<thead>\n")
|
2021-01-26 16:28:34 +00:00
|
|
|
f("<tr><th>Peer</th><th>OS</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Connection</th></tr>\n")
|
2020-05-01 21:55:16 +00:00
|
|
|
f("</thead>\n<tbody>\n")
|
2020-03-27 20:26:35 +00:00
|
|
|
|
2021-08-04 15:01:35 +00:00
|
|
|
now := time.Now()
|
2020-03-27 20:26:35 +00:00
|
|
|
|
2021-01-26 16:28:34 +00:00
|
|
|
var peers []*PeerStatus
|
2020-03-27 20:26:35 +00:00
|
|
|
for _, peer := range st.Peers() {
|
|
|
|
ps := st.Peer[peer]
|
2021-01-26 16:28:34 +00:00
|
|
|
if ps.ShareeNode {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
peers = append(peers, ps)
|
|
|
|
}
|
|
|
|
SortPeers(peers)
|
|
|
|
|
|
|
|
for _, ps := range peers {
|
2020-07-03 20:44:22 +00:00
|
|
|
var actAgo string
|
|
|
|
if !ps.LastWrite.IsZero() {
|
|
|
|
ago := now.Sub(ps.LastWrite)
|
|
|
|
actAgo = ago.Round(time.Second).String() + " ago"
|
|
|
|
if ago < 5*time.Minute {
|
|
|
|
actAgo = "<b>" + actAgo + "</b>"
|
2020-03-27 20:26:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
var owner string
|
|
|
|
if up, ok := st.User[ps.UserID]; ok {
|
|
|
|
owner = up.LoginName
|
|
|
|
if i := strings.Index(owner, "@"); i != -1 {
|
|
|
|
owner = owner[:i]
|
|
|
|
}
|
|
|
|
}
|
2021-01-26 16:28:34 +00:00
|
|
|
|
2021-02-18 22:15:38 +00:00
|
|
|
hostName := dnsname.SanitizeHostname(ps.HostName)
|
|
|
|
dnsName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
|
2021-01-26 16:28:34 +00:00
|
|
|
if strings.EqualFold(dnsName, hostName) || ps.UserID != st.Self.UserID {
|
|
|
|
hostName = ""
|
|
|
|
}
|
|
|
|
var hostNameHTML string
|
|
|
|
if hostName != "" {
|
|
|
|
hostNameHTML = "<br>" + html.EscapeString(hostName)
|
|
|
|
}
|
|
|
|
|
2021-04-14 14:20:27 +00:00
|
|
|
var tailAddr string
|
|
|
|
if len(ps.TailscaleIPs) > 0 {
|
|
|
|
tailAddr = ps.TailscaleIPs[0].String()
|
|
|
|
}
|
2021-01-26 16:28:34 +00:00
|
|
|
f("<tr><td>%s</td><td class=acenter>%s</td>"+
|
|
|
|
"<td><b>%s</b>%s<div class=\"tailaddr\">%s</div></td><td class=\"acenter owner\">%s</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td><td class=\"aright\">%v</td>",
|
|
|
|
ps.PublicKey.ShortString(),
|
2020-05-01 21:55:16 +00:00
|
|
|
osEmoji(ps.OS),
|
2021-01-26 16:28:34 +00:00
|
|
|
html.EscapeString(dnsName),
|
|
|
|
hostNameHTML,
|
2021-04-14 14:20:27 +00:00
|
|
|
tailAddr,
|
2020-05-01 21:55:16 +00:00
|
|
|
html.EscapeString(owner),
|
2020-03-27 20:26:35 +00:00
|
|
|
ps.RxBytes,
|
|
|
|
ps.TxBytes,
|
2020-07-03 20:44:22 +00:00
|
|
|
actAgo,
|
2020-03-27 20:26:35 +00:00
|
|
|
)
|
2021-01-26 16:28:34 +00:00
|
|
|
f("<td>")
|
|
|
|
|
2021-08-04 15:01:35 +00:00
|
|
|
if ps.Active {
|
2021-01-26 16:28:34 +00:00
|
|
|
if ps.Relay != "" && ps.CurAddr == "" {
|
|
|
|
f("relay <b>%s</b>", html.EscapeString(ps.Relay))
|
|
|
|
} else if ps.CurAddr != "" {
|
|
|
|
f("direct <b>%s</b>", html.EscapeString(ps.CurAddr))
|
2020-07-03 20:44:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 21:55:16 +00:00
|
|
|
f("</td>") // end Addrs
|
2020-03-27 20:26:35 +00:00
|
|
|
|
|
|
|
f("</tr>\n")
|
|
|
|
}
|
2020-05-01 21:55:16 +00:00
|
|
|
f("</tbody>\n</table>\n")
|
|
|
|
f("</body>\n</html>\n")
|
2020-03-27 20:26:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func osEmoji(os string) string {
|
|
|
|
switch os {
|
|
|
|
case "linux":
|
|
|
|
return "🐧"
|
|
|
|
case "macOS":
|
|
|
|
return "🍎"
|
|
|
|
case "windows":
|
|
|
|
return "🖥️"
|
|
|
|
case "iOS":
|
|
|
|
return "📱"
|
2023-06-07 19:19:31 +00:00
|
|
|
case "tvOS":
|
|
|
|
return "🍎📺"
|
2020-03-27 20:26:35 +00:00
|
|
|
case "android":
|
|
|
|
return "🤖"
|
|
|
|
case "freebsd":
|
|
|
|
return "👿"
|
|
|
|
case "openbsd":
|
|
|
|
return "🐡"
|
2022-08-28 14:54:33 +00:00
|
|
|
case "illumos":
|
|
|
|
return "☀️"
|
2020-03-27 20:26:35 +00:00
|
|
|
}
|
|
|
|
return "👽"
|
|
|
|
}
|
2020-08-09 21:49:42 +00:00
|
|
|
|
|
|
|
// PingResult contains response information for the "tailscale ping" subcommand,
|
|
|
|
// saying how Tailscale can reach a Tailscale IP or subnet-routed IP.
|
2022-04-15 16:00:27 +00:00
|
|
|
// See tailcfg.PingResponse for a related response that is sent back to control
|
|
|
|
// for remote diagnostic pings.
|
2020-08-09 21:49:42 +00:00
|
|
|
type PingResult struct {
|
|
|
|
IP string // ping destination
|
|
|
|
NodeIP string // Tailscale IP of node handling IP (different for subnet routers)
|
|
|
|
NodeName string // DNS name base or (possibly not unique) hostname
|
|
|
|
|
|
|
|
Err string
|
|
|
|
LatencySeconds float64
|
|
|
|
|
2021-03-23 22:16:15 +00:00
|
|
|
// Endpoint is the ip:port if direct UDP was used.
|
|
|
|
// It is not currently set for TSMP pings.
|
|
|
|
Endpoint string
|
|
|
|
|
|
|
|
// DERPRegionID is non-zero DERP region ID if DERP was used.
|
|
|
|
// It is not currently set for TSMP pings.
|
|
|
|
DERPRegionID int
|
|
|
|
|
|
|
|
// DERPRegionCode is the three-letter region code
|
|
|
|
// corresponding to DERPRegionID.
|
|
|
|
// It is not currently set for TSMP pings.
|
|
|
|
DERPRegionCode string
|
2020-08-09 21:49:42 +00:00
|
|
|
|
2021-03-29 22:17:05 +00:00
|
|
|
// PeerAPIPort is set by TSMP ping responses for peers that
|
|
|
|
// are running a peerapi server. This is the port they're
|
|
|
|
// running the server on.
|
|
|
|
PeerAPIPort uint16 `json:",omitempty"`
|
|
|
|
|
2022-05-28 04:34:36 +00:00
|
|
|
// PeerAPIURL is the URL that was hit for pings of type "peerapi" (tailcfg.PingPeerAPI).
|
|
|
|
// It's of the form "http://ip:port" (or [ip]:port for IPv6).
|
|
|
|
PeerAPIURL string `json:",omitempty"`
|
|
|
|
|
2021-09-07 18:57:23 +00:00
|
|
|
// IsLocalIP is whether the ping request error is due to it being
|
|
|
|
// a ping to the local node.
|
|
|
|
IsLocalIP bool `json:",omitempty"`
|
|
|
|
|
2020-08-09 21:49:42 +00:00
|
|
|
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
|
|
|
|
}
|
2021-01-26 16:28:34 +00:00
|
|
|
|
2022-04-22 01:49:01 +00:00
|
|
|
func (pr *PingResult) ToPingResponse(pingType tailcfg.PingType) *tailcfg.PingResponse {
|
2022-04-15 16:00:27 +00:00
|
|
|
return &tailcfg.PingResponse{
|
|
|
|
Type: pingType,
|
|
|
|
IP: pr.IP,
|
|
|
|
NodeIP: pr.NodeIP,
|
|
|
|
NodeName: pr.NodeName,
|
|
|
|
Err: pr.Err,
|
|
|
|
LatencySeconds: pr.LatencySeconds,
|
|
|
|
Endpoint: pr.Endpoint,
|
|
|
|
DERPRegionID: pr.DERPRegionID,
|
|
|
|
DERPRegionCode: pr.DERPRegionCode,
|
|
|
|
PeerAPIPort: pr.PeerAPIPort,
|
|
|
|
IsLocalIP: pr.IsLocalIP,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-26 16:28:34 +00:00
|
|
|
func SortPeers(peers []*PeerStatus) {
|
|
|
|
sort.Slice(peers, func(i, j int) bool { return sortKey(peers[i]) < sortKey(peers[j]) })
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortKey(ps *PeerStatus) string {
|
|
|
|
if ps.DNSName != "" {
|
|
|
|
return ps.DNSName
|
|
|
|
}
|
|
|
|
if ps.HostName != "" {
|
|
|
|
return ps.HostName
|
|
|
|
}
|
2021-04-14 14:20:27 +00:00
|
|
|
// TODO(bradfitz): add PeerStatus.Less and avoid these allocs in a Less func.
|
|
|
|
if len(ps.TailscaleIPs) > 0 {
|
|
|
|
return ps.TailscaleIPs[0].String()
|
|
|
|
}
|
2021-10-28 23:56:44 +00:00
|
|
|
raw := ps.PublicKey.Raw32()
|
|
|
|
return string(raw[:])
|
2021-01-26 16:28:34 +00:00
|
|
|
}
|
2022-11-26 22:23:00 +00:00
|
|
|
|
|
|
|
// DebugDERPRegionReport is the result of a "tailscale debug derp" command,
|
|
|
|
// to let people debug a custom DERP setup.
|
|
|
|
type DebugDERPRegionReport struct {
|
|
|
|
Info []string
|
|
|
|
Warnings []string
|
|
|
|
Errors []string
|
|
|
|
}
|