2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2021-02-04 21:12:42 +00:00
|
|
|
package ipnlocal
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
import (
|
2020-03-14 03:53:58 +00:00
|
|
|
"context"
|
2022-11-02 03:37:13 +00:00
|
|
|
"encoding/base64"
|
2022-11-07 23:32:53 +00:00
|
|
|
"encoding/json"
|
2020-02-03 18:35:52 +00:00
|
|
|
"errors"
|
2020-02-05 22:16:58 +00:00
|
|
|
"fmt"
|
2021-03-30 20:56:00 +01:00
|
|
|
"io"
|
2023-03-08 00:22:23 +00:00
|
|
|
"log"
|
2021-03-26 20:44:55 +00:00
|
|
|
"net"
|
2021-04-07 16:39:08 +01:00
|
|
|
"net/http"
|
2022-12-21 18:36:58 +00:00
|
|
|
"net/http/httputil"
|
2022-07-25 04:08:42 +01:00
|
|
|
"net/netip"
|
2022-11-02 03:37:13 +00:00
|
|
|
"net/url"
|
2020-09-17 15:59:55 +01:00
|
|
|
"os"
|
2021-04-17 05:01:29 +01:00
|
|
|
"os/user"
|
2021-03-29 18:42:33 +01:00
|
|
|
"path/filepath"
|
2020-10-21 20:55:03 +01:00
|
|
|
"runtime"
|
2021-04-08 22:54:25 +01:00
|
|
|
"sort"
|
2021-03-26 20:44:55 +00:00
|
|
|
"strconv"
|
2020-02-05 22:16:58 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2021-04-02 16:21:40 +01:00
|
|
|
"sync/atomic"
|
2020-02-05 22:16:58 +00:00
|
|
|
"time"
|
|
|
|
|
2022-11-07 23:32:53 +00:00
|
|
|
"go4.org/mem"
|
2022-07-25 04:08:42 +01:00
|
|
|
"go4.org/netipx"
|
2022-10-04 04:39:45 +01:00
|
|
|
"golang.org/x/exp/slices"
|
2023-06-09 00:57:40 +01:00
|
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
2021-04-13 16:13:46 +01:00
|
|
|
"tailscale.com/client/tailscale/apitype"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/control/controlclient"
|
2022-09-26 18:07:28 +01:00
|
|
|
"tailscale.com/doctor"
|
2023-03-29 16:27:12 +01:00
|
|
|
"tailscale.com/doctor/permissions"
|
2022-09-26 18:07:28 +01:00
|
|
|
"tailscale.com/doctor/routetable"
|
2022-01-24 18:52:57 +00:00
|
|
|
"tailscale.com/envknob"
|
2021-02-25 05:29:51 +00:00
|
|
|
"tailscale.com/health"
|
2022-11-11 17:43:49 +00:00
|
|
|
"tailscale.com/health/healthmsg"
|
2021-08-20 18:34:13 +01:00
|
|
|
"tailscale.com/hostinfo"
|
2021-02-04 21:12:42 +00:00
|
|
|
"tailscale.com/ipn"
|
2022-11-23 19:18:18 +00:00
|
|
|
"tailscale.com/ipn/ipnauth"
|
2020-03-26 05:57:46 +00:00
|
|
|
"tailscale.com/ipn/ipnstate"
|
2020-04-01 05:48:33 +01:00
|
|
|
"tailscale.com/ipn/policy"
|
2023-03-08 00:22:23 +00:00
|
|
|
"tailscale.com/log/sockstatlog"
|
|
|
|
"tailscale.com/logpolicy"
|
2021-03-25 21:50:21 +00:00
|
|
|
"tailscale.com/net/dns"
|
2023-01-24 19:31:20 +00:00
|
|
|
"tailscale.com/net/dnscache"
|
2022-09-05 19:36:30 +01:00
|
|
|
"tailscale.com/net/dnsfallback"
|
2020-08-28 05:25:17 +01:00
|
|
|
"tailscale.com/net/interfaces"
|
2023-01-25 18:17:40 +00:00
|
|
|
"tailscale.com/net/netns"
|
2022-03-28 18:24:11 +01:00
|
|
|
"tailscale.com/net/netutil"
|
2020-07-31 21:27:09 +01:00
|
|
|
"tailscale.com/net/tsaddr"
|
2021-12-01 04:39:12 +00:00
|
|
|
"tailscale.com/net/tsdial"
|
2021-03-29 18:42:33 +01:00
|
|
|
"tailscale.com/paths"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/portlist"
|
2022-08-04 18:43:49 +01:00
|
|
|
"tailscale.com/syncs"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/tailcfg"
|
2022-08-01 23:46:41 +01:00
|
|
|
"tailscale.com/tka"
|
2023-05-03 21:57:17 +01:00
|
|
|
"tailscale.com/tsd"
|
2021-08-05 22:05:24 +01:00
|
|
|
"tailscale.com/types/dnstype"
|
2020-02-14 21:09:19 +00:00
|
|
|
"tailscale.com/types/empty"
|
2020-03-26 05:57:46 +00:00
|
|
|
"tailscale.com/types/key"
|
2020-02-15 03:23:16 +00:00
|
|
|
"tailscale.com/types/logger"
|
2023-03-23 17:49:56 +00:00
|
|
|
"tailscale.com/types/logid"
|
2021-02-05 23:44:46 +00:00
|
|
|
"tailscale.com/types/netmap"
|
2021-02-05 23:23:01 +00:00
|
|
|
"tailscale.com/types/persist"
|
2021-05-27 18:07:17 +01:00
|
|
|
"tailscale.com/types/preftype"
|
2022-12-01 01:39:43 +00:00
|
|
|
"tailscale.com/types/ptr"
|
2022-02-22 17:52:49 +00:00
|
|
|
"tailscale.com/types/views"
|
2023-06-08 03:46:59 +01:00
|
|
|
"tailscale.com/util/cmpx"
|
2021-07-03 05:30:29 +01:00
|
|
|
"tailscale.com/util/deephash"
|
2021-04-09 23:24:47 +01:00
|
|
|
"tailscale.com/util/dnsname"
|
2022-10-04 04:39:45 +01:00
|
|
|
"tailscale.com/util/mak"
|
2021-11-02 21:30:48 +00:00
|
|
|
"tailscale.com/util/multierr"
|
2021-04-22 08:25:00 +01:00
|
|
|
"tailscale.com/util/osshare"
|
2022-11-28 18:34:35 +00:00
|
|
|
"tailscale.com/util/set"
|
2020-11-24 23:35:04 +00:00
|
|
|
"tailscale.com/util/systemd"
|
2022-11-07 23:32:53 +00:00
|
|
|
"tailscale.com/util/uniq"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/version"
|
2021-05-27 18:07:17 +01:00
|
|
|
"tailscale.com/version/distro"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/wgengine"
|
2023-01-19 22:28:49 +00:00
|
|
|
"tailscale.com/wgengine/capture"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/wgengine/filter"
|
2021-12-28 21:39:04 +00:00
|
|
|
"tailscale.com/wgengine/magicsock"
|
2020-05-11 22:02:12 +01:00
|
|
|
"tailscale.com/wgengine/router"
|
2021-01-29 20:16:36 +00:00
|
|
|
"tailscale.com/wgengine/wgcfg"
|
2021-02-05 20:44:43 +00:00
|
|
|
"tailscale.com/wgengine/wgcfg/nmcfg"
|
2020-02-05 22:16:58 +00:00
|
|
|
)
|
|
|
|
|
2020-10-20 18:40:12 +01:00
|
|
|
var controlDebugFlags = getControlDebugFlags()
|
|
|
|
|
|
|
|
func getControlDebugFlags() []string {
|
2022-01-24 18:52:57 +00:00
|
|
|
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
|
2020-10-20 18:40:12 +01:00
|
|
|
return strings.Split(e, ",")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-10-19 23:56:59 +01:00
|
|
|
|
2022-04-15 21:19:13 +01:00
|
|
|
// SSHServer is the interface of the conditionally linked ssh/tailssh.server.
|
|
|
|
type SSHServer interface {
|
|
|
|
HandleSSHConn(net.Conn) error
|
2022-04-17 19:49:56 +01:00
|
|
|
|
|
|
|
// OnPolicyChange is called when the SSH access policy changes,
|
|
|
|
// so that existing sessions can be re-evaluated for validity
|
|
|
|
// and closed if they'd no longer be accepted.
|
|
|
|
OnPolicyChange()
|
2022-05-28 12:33:46 +01:00
|
|
|
|
|
|
|
// Shutdown is called when tailscaled is shutting down.
|
|
|
|
Shutdown()
|
2022-04-15 21:19:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type newSSHServerFunc func(logger.Logf, *LocalBackend) (SSHServer, error)
|
|
|
|
|
|
|
|
var newSSHServer newSSHServerFunc // or nil
|
|
|
|
|
|
|
|
// RegisterNewSSHServer lets the conditionally linked ssh/tailssh package register itself.
|
|
|
|
func RegisterNewSSHServer(fn newSSHServerFunc) {
|
|
|
|
newSSHServer = fn
|
|
|
|
}
|
|
|
|
|
2020-05-19 03:32:20 +01:00
|
|
|
// LocalBackend is the glue between the major pieces of the Tailscale
|
|
|
|
// network software: the cloud control plane (via controlclient), the
|
|
|
|
// network data plane (via wgengine), and the user-facing UIs and CLIs
|
|
|
|
// (collectively called "frontends", via LocalBackend's implementation
|
|
|
|
// of the Backend interface).
|
|
|
|
//
|
|
|
|
// LocalBackend implements the overall state machine for the Tailscale
|
|
|
|
// application. Frontends, controlclient and wgengine can feed events
|
|
|
|
// into LocalBackend to advance the state machine, and advancing the
|
|
|
|
// state machine generates events back out to zero or more components.
|
2020-02-05 22:16:58 +00:00
|
|
|
type LocalBackend struct {
|
2020-05-19 03:32:20 +01:00
|
|
|
// Elements that are thread-safe or constant after construction.
|
2021-03-16 05:20:48 +00:00
|
|
|
ctx context.Context // canceled by Close
|
|
|
|
ctxCancel context.CancelFunc // cancels ctx
|
|
|
|
logf logger.Logf // general logging
|
|
|
|
keyLogf logger.Logf // for printing list of peers on change
|
|
|
|
statsLogf logger.Logf // for printing peers stats on change
|
2023-05-03 21:57:17 +01:00
|
|
|
sys *tsd.System
|
|
|
|
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
|
2022-11-09 05:58:10 +00:00
|
|
|
pm *profileManager
|
2023-05-03 21:57:17 +01:00
|
|
|
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
|
|
|
|
dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys
|
2023-03-23 17:49:56 +00:00
|
|
|
backendLogID logid.PublicID
|
2023-04-18 22:26:58 +01:00
|
|
|
unregisterNetMon func()
|
2021-03-16 05:20:48 +00:00
|
|
|
unregisterHealthWatch func()
|
|
|
|
portpoll *portlist.Poller // may be nil
|
|
|
|
portpollOnce sync.Once // guards starting readPoller
|
|
|
|
gotPortPollRes chan struct{} // closed upon first readPoller result
|
|
|
|
newDecompressor func() (controlclient.Decompressor, error)
|
2023-01-11 20:59:08 +00:00
|
|
|
varRoot string // or empty if SetVarRoot never called
|
|
|
|
logFlushFunc func() // or nil if SetLogFlusher wasn't called
|
|
|
|
em *expiryManager // non-nil
|
2022-08-04 05:51:02 +01:00
|
|
|
sshAtomicBool atomic.Bool
|
2022-05-30 10:06:46 +01:00
|
|
|
shutdownCalled bool // if Shutdown has been called
|
2023-01-19 22:28:49 +00:00
|
|
|
debugSink *capture.Sink
|
2023-03-08 00:22:23 +00:00
|
|
|
sockstatLogger *sockstatlog.Logger
|
2020-05-19 03:32:20 +01:00
|
|
|
|
2023-03-08 20:36:41 +00:00
|
|
|
// getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for
|
|
|
|
// the provided srcAddr and dstPort if one exists.
|
|
|
|
//
|
|
|
|
// srcAddr is the source address of the flow, not the address of the Funnel
|
|
|
|
// node relaying the flow.
|
|
|
|
// dstPort is the destination port of the flow.
|
|
|
|
//
|
|
|
|
// It returns nil if there is no known handler for this flow.
|
|
|
|
//
|
|
|
|
// This is specifically used to handle TCP flows for Funnel connections to tsnet
|
|
|
|
// servers.
|
|
|
|
//
|
|
|
|
// It is set once during initialization, and can be nil if SetTCPHandlerForFunnelFlow
|
|
|
|
// is never called.
|
|
|
|
getTCPHandlerForFunnelFlow func(srcAddr netip.AddrPort, dstPort uint16) (handler func(net.Conn))
|
|
|
|
|
2022-11-09 05:58:10 +00:00
|
|
|
// lastProfileID tracks the last profile we've seen from the ProfileManager.
|
|
|
|
// It's used to detect when the user has changed their profile.
|
|
|
|
lastProfileID ipn.ProfileID
|
|
|
|
|
2022-11-07 23:32:53 +00:00
|
|
|
filterAtomic atomic.Pointer[filter.Filter]
|
|
|
|
containsViaIPFuncAtomic syncs.AtomicValue[func(netip.Addr) bool]
|
|
|
|
shouldInterceptTCPPortAtomic syncs.AtomicValue[func(uint16) bool]
|
2023-01-11 20:59:08 +00:00
|
|
|
numClientStatusCalls atomic.Uint32
|
2021-11-25 17:43:39 +00:00
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
// The mutex protects the following elements.
|
2020-09-28 23:28:26 +01:00
|
|
|
mu sync.Mutex
|
2022-04-17 22:49:16 +01:00
|
|
|
filterHash deephash.Sum
|
2021-04-07 16:39:08 +01:00
|
|
|
httpTestClient *http.Client // for controlclient. nil by default, used by tests.
|
2021-04-30 15:23:22 +01:00
|
|
|
ccGen clientGen // function for producing controlclient; lazily populated
|
2022-05-30 10:06:46 +01:00
|
|
|
sshServer SSHServer // or nil, initialized lazily.
|
2021-02-04 21:12:42 +00:00
|
|
|
notify func(ipn.Notify)
|
2021-04-30 04:27:00 +01:00
|
|
|
cc controlclient.Client
|
2022-06-20 02:00:50 +01:00
|
|
|
ccAuto *controlclient.Auto // if cc is of type *controlclient.Auto
|
2021-09-03 21:17:46 +01:00
|
|
|
machinePrivKey key.MachinePrivate
|
2022-08-26 17:45:16 +01:00
|
|
|
tka *tkaState
|
2021-02-04 21:12:42 +00:00
|
|
|
state ipn.State
|
2021-04-22 08:25:00 +01:00
|
|
|
capFileSharing bool // whether netMap contains the file sharing capability
|
2022-11-30 18:34:59 +00:00
|
|
|
capTailnetLock bool // whether netMap contains the tailnet lock capability
|
2020-06-16 00:04:12 +01:00
|
|
|
// hostinfo is mutated in-place while mu is held.
|
|
|
|
hostinfo *tailcfg.Hostinfo
|
|
|
|
// netMap is not mutated in-place once set.
|
2021-03-25 22:38:40 +00:00
|
|
|
netMap *netmap.NetworkMap
|
2023-01-11 20:59:08 +00:00
|
|
|
nmExpiryTimer *time.Timer // for updating netMap on node expiry; can be nil
|
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 05:14:09 +01:00
|
|
|
nodeByAddr map[netip.Addr]*tailcfg.Node
|
2021-03-25 22:38:40 +00:00
|
|
|
activeLogin string // last logged LoginName from netMap
|
|
|
|
engineStatus ipn.EngineStatus
|
tailcfg: add Endpoint, EndpointType, MapRequest.EndpointType
Track endpoints internally with a new tailcfg.Endpoint type that
includes a typed netaddr.IPPort (instead of just a string) and
includes a type for how that endpoint was discovered (STUN, local,
etc).
Use []tailcfg.Endpoint instead of []string internally.
At the last second, send it to the control server as the existing
[]string for endpoints, but also include a new parallel
MapRequest.EndpointType []tailcfg.EndpointType, so the control server
can start filtering out less-important endpoint changes from
new-enough clients. Notably, STUN-discovered endpoints can be filtered
out from 1.6+ clients, as they can discover them amongst each other
via CallMeMaybe disco exchanges started over DERP. And STUN endpoints
change a lot, causing a lot of MapResposne updates. But portmapped
endpoints are worth keeping for now, as they they work right away
without requiring the firewall traversal extra RTT dance.
End result will be less control->client bandwidth. (despite negligible
increase in client->control bandwidth)
Updates tailscale/corp#1543
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-04-12 21:24:29 +01:00
|
|
|
endpoints []tailcfg.Endpoint
|
2021-03-25 22:38:40 +00:00
|
|
|
blocked bool
|
2021-11-04 19:19:00 +00:00
|
|
|
keyExpired bool
|
2021-04-21 21:37:36 +01:00
|
|
|
authURL string // cleared on Notify
|
|
|
|
authURLSticky string // not cleared on Notify
|
2021-03-25 22:38:40 +00:00
|
|
|
interact bool
|
2022-09-01 17:27:06 +01:00
|
|
|
egg bool
|
2021-03-25 22:38:40 +00:00
|
|
|
prevIfState *interfaces.State
|
2021-03-30 19:19:42 +01:00
|
|
|
peerAPIServer *peerAPIServer // or nil
|
2021-03-25 22:38:40 +00:00
|
|
|
peerAPIListeners []*peerAPIListener
|
2022-02-18 20:55:22 +00:00
|
|
|
loginFlags controlclient.LoginFlags
|
2021-04-08 22:54:25 +01:00
|
|
|
incomingFiles map[*incomingFile]bool
|
2022-11-28 18:34:35 +00:00
|
|
|
fileWaiters set.HandleSet[context.CancelFunc] // of wake-up funcs
|
|
|
|
notifyWatchers set.HandleSet[chan *ipn.Notify]
|
2022-11-22 19:41:03 +00:00
|
|
|
lastStatusTime time.Time // status.AsOf value of the last processed status update
|
2021-04-12 22:05:44 +01:00
|
|
|
// directFileRoot, if non-empty, means to write received files
|
|
|
|
// directly to this directory, without staging them in an
|
|
|
|
// intermediate buffered directory for "pick-up" later. If
|
|
|
|
// empty, the files are received in a daemon-owned location
|
|
|
|
// and the localapi is used to enumerate, download, and delete
|
|
|
|
// them. This is used on macOS where the GUI lifetime is the
|
|
|
|
// same as the Network Extension lifetime and we can thus avoid
|
|
|
|
// double-copying files by writing them to the right location
|
|
|
|
// immediately.
|
2022-07-17 18:05:36 +01:00
|
|
|
// It's also used on several NAS platforms (Synology, TrueNAS, etc)
|
|
|
|
// but in that case DoFinalRename is also set true, which moves the
|
|
|
|
// *.partial file to its final name on completion.
|
2021-12-06 20:24:25 +00:00
|
|
|
directFileRoot string
|
2022-07-17 18:05:36 +01:00
|
|
|
directFileDoFinalRename bool // false on macOS, true on several NAS platforms
|
2022-10-04 04:39:45 +01:00
|
|
|
componentLogUntil map[string]componentLogState
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2022-11-07 23:32:53 +00:00
|
|
|
// ServeConfig fields. (also guarded by mu)
|
2022-11-09 21:15:59 +00:00
|
|
|
lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
|
|
|
|
serveConfig ipn.ServeConfigView // or !Valid if none
|
2022-11-07 23:32:53 +00:00
|
|
|
|
2022-12-21 18:36:58 +00:00
|
|
|
serveListeners map[netip.AddrPort]*serveListener // addrPort => serveListener
|
|
|
|
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *httputil.ReverseProxy
|
2022-11-12 02:46:26 +00:00
|
|
|
|
2020-05-19 03:32:20 +01:00
|
|
|
// statusLock must be held before calling statusChanged.Wait() or
|
2020-02-05 22:16:58 +00:00
|
|
|
// statusChanged.Broadcast().
|
|
|
|
statusLock sync.Mutex
|
|
|
|
statusChanged *sync.Cond
|
2022-09-23 18:06:55 +01:00
|
|
|
|
|
|
|
// dialPlan is any dial plan that we've received from the control
|
|
|
|
// server during a previous connection; it is cleared on logout.
|
|
|
|
dialPlan atomic.Pointer[tailcfg.ControlDialPlan]
|
2022-10-06 19:09:13 +01:00
|
|
|
|
|
|
|
// tkaSyncLock is used to make tkaSyncIfNeeded an exclusive
|
|
|
|
// section. This is needed to stop two map-responses in quick succession
|
|
|
|
// from racing each other through TKA sync logic / RPCs.
|
|
|
|
//
|
|
|
|
// tkaSyncLock MUST be taken before mu (or inversely, mu must not be held
|
|
|
|
// at the moment that tkaSyncLock is taken).
|
|
|
|
tkaSyncLock sync.Mutex
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-30 15:23:22 +01:00
|
|
|
// clientGen is a func that creates a control plane client.
|
|
|
|
// It's the type used by LocalBackend.SetControlClientGetterForTesting.
|
2021-04-30 04:27:00 +01:00
|
|
|
type clientGen func(controlclient.Options) (controlclient.Client, error)
|
|
|
|
|
2020-02-03 18:35:52 +00:00
|
|
|
// NewLocalBackend returns a new LocalBackend that is ready to run,
|
|
|
|
// but is not actually running.
|
2021-12-01 04:39:12 +00:00
|
|
|
//
|
|
|
|
// If dialer is nil, a new one is made.
|
2023-05-03 21:57:17 +01:00
|
|
|
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
|
|
|
|
e := sys.Engine.Get()
|
|
|
|
store := sys.StateStore.Get()
|
|
|
|
dialer := sys.Dialer.Get()
|
2022-02-14 16:32:23 +00:00
|
|
|
|
2023-01-31 01:28:13 +00:00
|
|
|
pm, err := newProfileManager(store, logf)
|
2022-11-09 05:58:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-03-29 21:51:53 +01:00
|
|
|
if sds, ok := store.(ipn.StateStoreDialerSetter); ok {
|
|
|
|
sds.SetDialer(dialer.SystemDial)
|
|
|
|
}
|
2022-11-09 05:58:10 +00:00
|
|
|
|
2022-02-14 16:32:23 +00:00
|
|
|
hi := hostinfo.New()
|
2022-02-18 04:41:49 +00:00
|
|
|
logf.JSON(1, "Hostinfo", hi)
|
2022-02-13 04:42:38 +00:00
|
|
|
envknob.LogCurrent(logf)
|
2021-12-01 04:39:12 +00:00
|
|
|
if dialer == nil {
|
2022-09-30 22:15:17 +01:00
|
|
|
dialer = &tsdial.Dialer{Logf: logf}
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-22 08:25:00 +01:00
|
|
|
osshare.SetFileSharingEnabled(false, logf)
|
|
|
|
|
2020-03-14 03:53:58 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2023-06-05 17:33:59 +01:00
|
|
|
portpoll := new(portlist.Poller)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2020-02-25 15:36:32 +00:00
|
|
|
b := &LocalBackend{
|
2020-10-14 22:07:40 +01:00
|
|
|
ctx: ctx,
|
|
|
|
ctxCancel: cancel,
|
|
|
|
logf: logf,
|
|
|
|
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
2020-10-29 22:26:10 +00:00
|
|
|
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
2023-05-03 21:57:17 +01:00
|
|
|
sys: sys,
|
2020-10-14 22:07:40 +01:00
|
|
|
e: e,
|
2021-12-01 04:39:12 +00:00
|
|
|
dialer: dialer,
|
2023-05-03 21:57:17 +01:00
|
|
|
store: store,
|
|
|
|
pm: pm,
|
2023-03-23 17:49:56 +00:00
|
|
|
backendLogID: logID,
|
2021-02-04 21:12:42 +00:00
|
|
|
state: ipn.NoState,
|
2020-10-14 22:07:40 +01:00
|
|
|
portpoll: portpoll,
|
2023-01-11 20:59:08 +00:00
|
|
|
em: newExpiryManager(logf),
|
2020-10-14 22:07:40 +01:00
|
|
|
gotPortPollRes: make(chan struct{}),
|
2022-02-18 20:55:22 +00:00
|
|
|
loginFlags: loginFlags,
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2021-12-01 04:39:12 +00:00
|
|
|
|
2023-05-03 21:57:17 +01:00
|
|
|
netMon := sys.NetMon.Get()
|
|
|
|
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
|
2023-04-03 22:21:48 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("error setting up sockstat logger: %v", err)
|
|
|
|
}
|
|
|
|
// Enable sockstats logs only on unstable builds
|
|
|
|
if version.IsUnstableBuild() && b.sockstatLogger != nil {
|
|
|
|
b.sockstatLogger.SetLoggingEnabled(true)
|
2023-03-08 00:22:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-25 17:43:39 +00:00
|
|
|
// Default filter blocks everything and logs nothing, until Start() is called.
|
2022-07-25 04:08:42 +01:00
|
|
|
b.setFilter(filter.NewAllowNone(logf, &netipx.IPSet{}))
|
2021-11-25 17:43:39 +00:00
|
|
|
|
2022-11-07 23:32:53 +00:00
|
|
|
b.setTCPPortsIntercepted(nil)
|
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
b.statusChanged = sync.NewCond(&b.statusLock)
|
2021-07-19 19:07:42 +01:00
|
|
|
b.e.SetStatusCallback(b.setWgengineStatus)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2023-04-18 22:26:58 +01:00
|
|
|
b.prevIfState = netMon.InterfaceState()
|
2021-03-02 04:45:30 +00:00
|
|
|
// Call our linkChange code once with the current state, and
|
|
|
|
// then also whenever it changes:
|
2023-04-18 22:26:58 +01:00
|
|
|
b.linkChange(false, netMon.InterfaceState())
|
|
|
|
b.unregisterNetMon = netMon.RegisterChangeCallback(b.linkChange)
|
2021-03-02 04:45:30 +00:00
|
|
|
|
2021-03-16 05:20:48 +00:00
|
|
|
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
|
|
|
|
|
2023-05-03 21:57:17 +01:00
|
|
|
if tunWrap, ok := b.sys.Tun.GetOK(); ok {
|
|
|
|
tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
|
|
|
} else {
|
2022-11-16 17:38:38 +00:00
|
|
|
b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
|
2021-03-29 23:17:05 +01:00
|
|
|
}
|
|
|
|
|
2022-10-04 04:39:45 +01:00
|
|
|
for _, component := range debuggableComponents {
|
|
|
|
key := componentStateKey(component)
|
2022-11-09 05:58:10 +00:00
|
|
|
if ut, err := ipn.ReadStoreInt(pm.Store(), key); err == nil {
|
2022-10-04 04:39:45 +01:00
|
|
|
if until := time.Unix(ut, 0); until.After(time.Now()) {
|
|
|
|
// conditional to avoid log spam at start when off
|
|
|
|
b.SetComponentDebugLogging(component, until)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 15:36:32 +00:00
|
|
|
return b, nil
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 04:39:45 +01:00
|
|
|
type componentLogState struct {
|
|
|
|
until time.Time
|
|
|
|
timer *time.Timer // if non-nil, the AfterFunc to disable it
|
|
|
|
}
|
|
|
|
|
|
|
|
var debuggableComponents = []string{
|
|
|
|
"magicsock",
|
2023-03-27 22:34:56 +01:00
|
|
|
"sockstats",
|
2022-10-04 04:39:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func componentStateKey(component string) ipn.StateKey {
|
|
|
|
return ipn.StateKey("_debug_" + component + "_until")
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetComponentDebugLogging sets component's debug logging enabled until the until time.
|
|
|
|
// If until is in the past, the component's debug logging is disabled.
|
|
|
|
//
|
|
|
|
// The following components are recognized:
|
|
|
|
//
|
|
|
|
// - magicsock
|
2023-03-30 15:03:09 +01:00
|
|
|
// - sockstats
|
2022-10-04 04:39:45 +01:00
|
|
|
func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Time) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
|
|
var setEnabled func(bool)
|
|
|
|
switch component {
|
|
|
|
case "magicsock":
|
|
|
|
mc, err := b.magicConn()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
setEnabled = mc.SetDebugLoggingEnabled
|
2023-03-27 22:34:56 +01:00
|
|
|
case "sockstats":
|
|
|
|
if b.sockstatLogger != nil {
|
2023-04-03 22:16:53 +01:00
|
|
|
setEnabled = func(v bool) {
|
|
|
|
b.sockstatLogger.SetLoggingEnabled(v)
|
|
|
|
// Flush (and thus upload) logs when the enabled period ends,
|
|
|
|
// so that the logs are available for debugging.
|
|
|
|
if !v {
|
|
|
|
b.sockstatLogger.Flush()
|
|
|
|
}
|
|
|
|
}
|
2023-03-27 22:34:56 +01:00
|
|
|
}
|
2022-10-04 04:39:45 +01:00
|
|
|
}
|
|
|
|
if setEnabled == nil || !slices.Contains(debuggableComponents, component) {
|
|
|
|
return fmt.Errorf("unknown component %q", component)
|
|
|
|
}
|
|
|
|
timeUnixOrZero := func(t time.Time) int64 {
|
|
|
|
if t.IsZero() {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return t.Unix()
|
|
|
|
}
|
|
|
|
ipn.PutStoreInt(b.store, componentStateKey(component), timeUnixOrZero(until))
|
|
|
|
now := time.Now()
|
|
|
|
on := now.Before(until)
|
|
|
|
setEnabled(on)
|
|
|
|
var onFor time.Duration
|
|
|
|
if on {
|
|
|
|
onFor = until.Sub(now)
|
|
|
|
b.logf("debugging logging for component %q enabled for %v (until %v)", component, onFor.Round(time.Second), until.UTC().Format(time.RFC3339))
|
|
|
|
} else {
|
|
|
|
b.logf("debugging logging for component %q disabled", component)
|
|
|
|
}
|
|
|
|
if oldSt, ok := b.componentLogUntil[component]; ok && oldSt.timer != nil {
|
|
|
|
oldSt.timer.Stop()
|
|
|
|
}
|
|
|
|
newSt := componentLogState{until: until}
|
|
|
|
if on {
|
|
|
|
newSt.timer = time.AfterFunc(onFor, func() {
|
|
|
|
// Turn off logging after the timer fires, as long as the state is
|
|
|
|
// unchanged when the timer actually fires.
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2023-04-20 02:54:19 +01:00
|
|
|
if ls := b.componentLogUntil[component]; ls.until.Equal(until) {
|
2022-10-04 04:39:45 +01:00
|
|
|
setEnabled(false)
|
|
|
|
b.logf("debugging logging for component %q disabled (by timer)", component)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
mak.Set(&b.componentLogUntil, component, newSt)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-15 18:31:35 +01:00
|
|
|
// GetComponentDebugLogging gets the time that component's debug logging is
|
|
|
|
// enabled until, or the zero time if component's time is not currently
|
|
|
|
// enabled.
|
|
|
|
func (b *LocalBackend) GetComponentDebugLogging(component string) time.Time {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
ls := b.componentLogUntil[component]
|
|
|
|
if ls.until.IsZero() || ls.until.Before(now) {
|
|
|
|
return time.Time{}
|
|
|
|
}
|
|
|
|
return ls.until
|
|
|
|
}
|
|
|
|
|
2021-12-01 04:39:12 +00:00
|
|
|
// Dialer returns the backend's dialer.
|
2023-05-03 21:57:17 +01:00
|
|
|
// It is always non-nil.
|
2021-12-01 04:39:12 +00:00
|
|
|
func (b *LocalBackend) Dialer() *tsdial.Dialer {
|
|
|
|
return b.dialer
|
|
|
|
}
|
|
|
|
|
2021-04-12 22:05:44 +01:00
|
|
|
// SetDirectFileRoot sets the directory to download files to directly,
|
|
|
|
// without buffering them through an intermediate daemon-owned
|
|
|
|
// tailcfg.UserID-specific directory.
|
|
|
|
//
|
|
|
|
// This must be called before the LocalBackend starts being used.
|
|
|
|
func (b *LocalBackend) SetDirectFileRoot(dir string) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
b.directFileRoot = dir
|
|
|
|
}
|
|
|
|
|
2021-12-06 20:24:25 +00:00
|
|
|
// SetDirectFileDoFinalRename sets whether the peerapi file server should rename
|
|
|
|
// a received "name.partial" file to "name" when the download is complete.
|
|
|
|
//
|
|
|
|
// This only applies when SetDirectFileRoot is non-empty.
|
|
|
|
// The default is false.
|
|
|
|
func (b *LocalBackend) SetDirectFileDoFinalRename(v bool) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
b.directFileDoFinalRename = v
|
|
|
|
}
|
|
|
|
|
2021-07-16 23:21:00 +01:00
|
|
|
// b.mu must be held.
|
|
|
|
func (b *LocalBackend) maybePauseControlClientLocked() {
|
|
|
|
if b.cc == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
networkUp := b.prevIfState.AnyInterfaceUp()
|
|
|
|
b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || !networkUp)
|
|
|
|
}
|
|
|
|
|
2023-04-18 22:26:58 +01:00
|
|
|
// linkChange is our network monitor callback, called whenever the network changes.
|
2021-03-02 04:45:30 +00:00
|
|
|
// major is whether ifst is different than earlier.
|
2020-08-28 05:25:17 +01:00
|
|
|
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
|
2020-10-02 06:03:25 +01:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
2020-10-05 23:12:35 +01:00
|
|
|
hadPAC := b.prevIfState.HasPAC()
|
|
|
|
b.prevIfState = ifst
|
2021-07-16 23:21:00 +01:00
|
|
|
b.maybePauseControlClientLocked()
|
2020-10-06 23:22:46 +01:00
|
|
|
|
2020-10-05 23:12:35 +01:00
|
|
|
// If the PAC-ness of the network changed, reconfig wireguard+route to
|
|
|
|
// add/remove subnets.
|
|
|
|
if hadPAC != ifst.HasPAC() {
|
|
|
|
b.logf("linkChange: in state %v; PAC changed from %v->%v", b.state, hadPAC, ifst.HasPAC())
|
|
|
|
switch b.state {
|
2021-02-04 21:12:42 +00:00
|
|
|
case ipn.NoState, ipn.Stopped:
|
2020-10-05 23:12:35 +01:00
|
|
|
// Do nothing.
|
|
|
|
default:
|
|
|
|
go b.authReconfig()
|
2020-10-02 06:03:25 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-23 04:43:35 +00:00
|
|
|
|
|
|
|
// If the local network configuration has changed, our filter may
|
|
|
|
// need updating to tweak default routes.
|
2022-11-09 05:58:10 +00:00
|
|
|
b.updateFilterLocked(b.netMap, b.pm.CurrentPrefs())
|
2021-03-30 21:49:08 +01:00
|
|
|
|
2021-06-14 16:59:09 +01:00
|
|
|
if peerAPIListenAsync && b.netMap != nil && b.state == ipn.Running {
|
2021-03-30 21:49:08 +01:00
|
|
|
want := len(b.netMap.Addresses)
|
|
|
|
if len(b.peerAPIListeners) < want {
|
2021-07-06 17:07:18 +01:00
|
|
|
b.logf("linkChange: peerAPIListeners too low; trying again")
|
2021-03-30 21:49:08 +01:00
|
|
|
go b.initPeerAPIListener()
|
|
|
|
}
|
|
|
|
}
|
2020-08-28 05:25:17 +01:00
|
|
|
}
|
|
|
|
|
2021-03-16 05:20:48 +00:00
|
|
|
func (b *LocalBackend) onHealthChange(sys health.Subsystem, err error) {
|
|
|
|
if err == nil {
|
|
|
|
b.logf("health(%q): ok", sys)
|
|
|
|
} else {
|
|
|
|
b.logf("health(%q): error: %v", sys, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 03:32:20 +01:00
|
|
|
// Shutdown halts the backend and all its sub-components. The backend
|
|
|
|
// can no longer be used after Shutdown returns.
|
2020-02-05 22:16:58 +00:00
|
|
|
func (b *LocalBackend) Shutdown() {
|
2020-05-19 03:32:20 +01:00
|
|
|
b.mu.Lock()
|
2022-07-19 04:36:14 +01:00
|
|
|
if b.shutdownCalled {
|
|
|
|
b.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
2022-04-25 19:02:59 +01:00
|
|
|
b.shutdownCalled = true
|
2022-07-19 04:36:14 +01:00
|
|
|
|
|
|
|
if b.loginFlags&controlclient.LoginEphemeral != 0 {
|
|
|
|
b.mu.Unlock()
|
|
|
|
ctx, cancel := context.WithTimeout(b.ctx, 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
b.LogoutSync(ctx) // best effort
|
|
|
|
b.mu.Lock()
|
|
|
|
}
|
2021-04-08 05:12:16 +01:00
|
|
|
cc := b.cc
|
2022-05-28 12:33:46 +01:00
|
|
|
if b.sshServer != nil {
|
|
|
|
b.sshServer.Shutdown()
|
2022-05-30 10:06:46 +01:00
|
|
|
b.sshServer = nil
|
2022-05-28 12:33:46 +01:00
|
|
|
}
|
2022-04-21 03:15:43 +01:00
|
|
|
b.closePeerAPIListenersLocked()
|
2023-01-19 22:28:49 +00:00
|
|
|
if b.debugSink != nil {
|
|
|
|
b.e.InstallCaptureHook(nil)
|
|
|
|
b.debugSink.Close()
|
|
|
|
b.debugSink = nil
|
|
|
|
}
|
2020-05-19 03:32:20 +01:00
|
|
|
b.mu.Unlock()
|
2020-05-21 21:30:20 +01:00
|
|
|
|
2023-03-08 00:22:23 +00:00
|
|
|
if b.sockstatLogger != nil {
|
|
|
|
b.sockstatLogger.Shutdown()
|
|
|
|
}
|
|
|
|
|
2023-04-18 22:26:58 +01:00
|
|
|
b.unregisterNetMon()
|
2021-03-16 05:20:48 +00:00
|
|
|
b.unregisterHealthWatch()
|
2021-04-08 05:12:16 +01:00
|
|
|
if cc != nil {
|
|
|
|
cc.Shutdown()
|
2020-05-19 03:32:20 +01:00
|
|
|
}
|
2020-05-21 21:30:20 +01:00
|
|
|
b.ctxCancel()
|
2020-02-05 22:16:58 +00:00
|
|
|
b.e.Close()
|
|
|
|
b.e.Wait()
|
|
|
|
}
|
|
|
|
|
2022-10-24 01:15:04 +01:00
|
|
|
func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView {
|
2022-11-29 20:00:40 +00:00
|
|
|
if !p.Valid() || !p.Persist().Valid() {
|
2022-10-24 01:15:04 +01:00
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
p2 := p.AsStruct()
|
|
|
|
p2.Persist.LegacyFrontendPrivateMachineKey = key.MachinePrivate{}
|
|
|
|
p2.Persist.PrivateNodeKey = key.NodePrivate{}
|
|
|
|
p2.Persist.OldPrivateNodeKey = key.NodePrivate{}
|
2022-11-18 19:23:57 +00:00
|
|
|
p2.Persist.NetworkLockKey = key.NLPrivate{}
|
2022-10-24 01:15:04 +01:00
|
|
|
return p2.View()
|
|
|
|
}
|
|
|
|
|
2021-04-07 16:27:35 +01:00
|
|
|
// Prefs returns a copy of b's current prefs, with any private keys removed.
|
2022-10-24 01:15:04 +01:00
|
|
|
func (b *LocalBackend) Prefs() ipn.PrefsView {
|
2021-04-07 16:27:35 +01:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-12-02 08:54:32 +00:00
|
|
|
return b.sanitizedPrefsLocked()
|
2022-12-01 01:39:43 +00:00
|
|
|
}
|
|
|
|
|
2022-12-02 08:54:32 +00:00
|
|
|
func (b *LocalBackend) sanitizedPrefsLocked() ipn.PrefsView {
|
2022-11-09 05:58:10 +00:00
|
|
|
return stripKeysFromPrefs(b.pm.CurrentPrefs())
|
2021-04-07 16:27:35 +01:00
|
|
|
}
|
|
|
|
|
2020-05-19 03:32:20 +01:00
|
|
|
// Status returns the latest status of the backend and its
|
|
|
|
// sub-components.
|
2020-03-26 05:57:46 +00:00
|
|
|
func (b *LocalBackend) Status() *ipnstate.Status {
|
2022-12-19 18:23:47 +00:00
|
|
|
sb := &ipnstate.StatusBuilder{WantPeers: true}
|
2020-03-26 05:57:46 +00:00
|
|
|
b.UpdateStatus(sb)
|
|
|
|
return sb.Status()
|
|
|
|
}
|
|
|
|
|
2021-03-19 04:07:58 +00:00
|
|
|
// StatusWithoutPeers is like Status but omits any details
|
|
|
|
// of peers.
|
|
|
|
func (b *LocalBackend) StatusWithoutPeers() *ipnstate.Status {
|
2022-12-19 18:23:47 +00:00
|
|
|
sb := &ipnstate.StatusBuilder{WantPeers: false}
|
|
|
|
b.UpdateStatus(sb)
|
2021-03-19 04:07:58 +00:00
|
|
|
return sb.Status()
|
|
|
|
}
|
|
|
|
|
2020-05-19 03:32:20 +01:00
|
|
|
// UpdateStatus implements ipnstate.StatusUpdater.
|
2020-03-26 05:57:46 +00:00
|
|
|
func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
|
|
|
|
b.e.UpdateStatus(sb)
|
2022-12-19 18:23:47 +00:00
|
|
|
var extraLocked func(*ipnstate.StatusBuilder)
|
|
|
|
if sb.WantPeers {
|
|
|
|
extraLocked = b.populatePeerStatusLocked
|
|
|
|
}
|
|
|
|
b.updateStatus(sb, extraLocked)
|
2021-03-19 04:07:58 +00:00
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
|
2021-03-19 04:07:58 +00:00
|
|
|
// updateStatus populates sb with status.
|
|
|
|
//
|
|
|
|
// extraLocked, if non-nil, is called while b.mu is still held.
|
|
|
|
func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) {
|
2020-03-26 05:57:46 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2021-03-25 22:38:40 +00:00
|
|
|
sb.MutateStatus(func(s *ipnstate.Status) {
|
2023-02-11 06:20:36 +00:00
|
|
|
s.Version = version.Long()
|
2023-05-03 21:57:17 +01:00
|
|
|
s.TUN = !b.sys.IsNetstack()
|
2021-03-25 22:38:40 +00:00
|
|
|
s.BackendState = b.state.String()
|
2021-04-21 21:37:36 +01:00
|
|
|
s.AuthURL = b.authURLSticky
|
2021-09-02 03:27:22 +01:00
|
|
|
if err := health.OverallError(); err != nil {
|
|
|
|
switch e := err.(type) {
|
2021-11-02 21:30:48 +00:00
|
|
|
case multierr.Error:
|
|
|
|
for _, err := range e.Errors() {
|
2021-09-02 03:27:22 +01:00
|
|
|
s.Health = append(s.Health, err.Error())
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
s.Health = append(s.Health, err.Error())
|
|
|
|
}
|
|
|
|
}
|
2022-06-17 20:09:23 +01:00
|
|
|
if m := b.sshOnButUnusableHealthCheckMessageLocked(); m != "" {
|
|
|
|
s.Health = append(s.Health, m)
|
|
|
|
}
|
2022-11-19 21:46:08 +00:00
|
|
|
if version.IsUnstableBuild() {
|
|
|
|
s.Health = append(s.Health, "This is an unstable (development) version of Tailscale; frequent updates and bugs are likely")
|
|
|
|
}
|
2021-03-25 22:38:40 +00:00
|
|
|
if b.netMap != nil {
|
2021-06-15 20:12:15 +01:00
|
|
|
s.CertDomains = append([]string(nil), b.netMap.DNS.CertDomains...)
|
2022-02-15 17:36:01 +00:00
|
|
|
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
|
|
|
if s.CurrentTailnet == nil {
|
|
|
|
s.CurrentTailnet = &ipnstate.TailnetStatus{}
|
|
|
|
}
|
|
|
|
s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
|
|
|
s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied
|
|
|
|
s.CurrentTailnet.Name = b.netMap.Domain
|
2022-11-11 17:43:49 +00:00
|
|
|
if prefs := b.pm.CurrentPrefs(); prefs.Valid() {
|
|
|
|
if !prefs.RouteAll() && b.netMap.AnyPeersAdvertiseRoutes() {
|
|
|
|
s.Health = append(s.Health, healthmsg.WarnAcceptRoutesOff)
|
|
|
|
}
|
|
|
|
if !prefs.ExitNodeID().IsZero() {
|
|
|
|
if exitPeer, ok := b.netMap.PeerWithStableID(prefs.ExitNodeID()); ok {
|
|
|
|
var online = false
|
|
|
|
if exitPeer.Online != nil {
|
|
|
|
online = *exitPeer.Online
|
|
|
|
}
|
|
|
|
s.ExitNodeStatus = &ipnstate.ExitNodeStatus{
|
|
|
|
ID: prefs.ExitNodeID(),
|
|
|
|
Online: online,
|
|
|
|
TailscaleIPs: exitPeer.Addresses,
|
|
|
|
}
|
2022-06-07 20:31:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-25 22:38:40 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
|
2021-12-16 16:06:32 +00:00
|
|
|
ss.Online = health.GetInPollNetMap()
|
2021-11-26 01:11:01 +00:00
|
|
|
if b.netMap != nil {
|
2022-10-28 17:39:01 +01:00
|
|
|
ss.InNetworkMap = true
|
2021-11-26 01:11:01 +00:00
|
|
|
ss.HostName = b.netMap.Hostinfo.Hostname
|
|
|
|
ss.DNSName = b.netMap.Name
|
|
|
|
ss.UserID = b.netMap.User
|
|
|
|
if sn := b.netMap.SelfNode; sn != nil {
|
2022-10-28 17:39:01 +01:00
|
|
|
peerStatusFromNode(ss, sn)
|
2021-11-26 01:11:01 +00:00
|
|
|
if c := sn.Capabilities; len(c) > 0 {
|
|
|
|
ss.Capabilities = append([]string(nil), c...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ss.HostName, _ = os.Hostname()
|
2021-05-07 15:27:14 +01:00
|
|
|
}
|
2021-03-25 22:38:40 +00:00
|
|
|
for _, pln := range b.peerAPIListeners {
|
2021-03-26 20:44:55 +00:00
|
|
|
ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr)
|
2021-03-25 22:38:40 +00:00
|
|
|
}
|
|
|
|
})
|
2020-03-26 05:57:46 +00:00
|
|
|
// TODO: hostinfo, and its networkinfo
|
|
|
|
// TODO: EngineStatus copy (and deprecate it?)
|
2021-03-19 04:07:58 +00:00
|
|
|
|
|
|
|
if extraLocked != nil {
|
|
|
|
extraLocked(sb)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
|
|
|
|
if b.netMap == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for id, up := range b.netMap.UserProfiles {
|
|
|
|
sb.AddUser(id, up)
|
|
|
|
}
|
2022-11-09 05:58:10 +00:00
|
|
|
exitNodeID := b.pm.CurrentPrefs().ExitNodeID()
|
2021-03-19 04:07:58 +00:00
|
|
|
for _, p := range b.netMap.Peers {
|
|
|
|
var lastSeen time.Time
|
|
|
|
if p.LastSeen != nil {
|
|
|
|
lastSeen = *p.LastSeen
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
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 05:14:09 +01:00
|
|
|
var tailscaleIPs = make([]netip.Addr, 0, len(p.Addresses))
|
2021-03-19 04:07:58 +00:00
|
|
|
for _, addr := range p.Addresses {
|
2022-07-25 04:08:42 +01:00
|
|
|
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.Addr()) {
|
|
|
|
tailscaleIPs = append(tailscaleIPs, addr.Addr())
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-28 17:39:01 +01:00
|
|
|
ps := &ipnstate.PeerStatus{
|
|
|
|
InNetworkMap: true,
|
|
|
|
UserID: p.User,
|
|
|
|
TailscaleIPs: tailscaleIPs,
|
|
|
|
HostName: p.Hostinfo.Hostname(),
|
|
|
|
DNSName: p.Name,
|
|
|
|
OS: p.Hostinfo.OS(),
|
|
|
|
LastSeen: lastSeen,
|
|
|
|
Online: p.Online != nil && *p.Online,
|
|
|
|
ShareeNode: p.Hostinfo.ShareeNode(),
|
2022-11-09 05:58:10 +00:00
|
|
|
ExitNode: p.StableID != "" && p.StableID == exitNodeID,
|
2022-10-28 17:39:01 +01:00
|
|
|
SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(),
|
2023-07-14 05:33:53 +01:00
|
|
|
Location: p.Hostinfo.Location(),
|
2022-02-22 17:52:49 +00:00
|
|
|
}
|
2022-10-28 17:39:01 +01:00
|
|
|
peerStatusFromNode(ps, p)
|
2022-11-17 20:22:00 +00:00
|
|
|
|
|
|
|
p4, p6 := peerAPIPorts(p)
|
2022-11-19 21:10:45 +00:00
|
|
|
if u := peerAPIURL(nodeIP(p, netip.Addr.Is4), p4); u != "" {
|
|
|
|
ps.PeerAPIURL = append(ps.PeerAPIURL, u)
|
2022-11-17 20:22:00 +00:00
|
|
|
}
|
2022-11-19 21:10:45 +00:00
|
|
|
if u := peerAPIURL(nodeIP(p, netip.Addr.Is6), p6); u != "" {
|
|
|
|
ps.PeerAPIURL = append(ps.PeerAPIURL, u)
|
2022-11-17 20:22:00 +00:00
|
|
|
}
|
2022-10-28 17:39:01 +01:00
|
|
|
sb.AddPeer(p.Key, ps)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// peerStatusFromNode copies fields that exist in the Node struct for
|
|
|
|
// current node and peers into the provided PeerStatus.
|
|
|
|
func peerStatusFromNode(ps *ipnstate.PeerStatus, n *tailcfg.Node) {
|
|
|
|
ps.ID = n.StableID
|
|
|
|
ps.Created = n.Created
|
|
|
|
ps.ExitNodeOption = tsaddr.ContainsExitRoutes(n.AllowedIPs)
|
|
|
|
if n.Tags != nil {
|
|
|
|
v := views.SliceOf(n.Tags)
|
|
|
|
ps.Tags = &v
|
|
|
|
}
|
|
|
|
if n.PrimaryRoutes != nil {
|
|
|
|
v := views.IPPrefixSliceOf(n.PrimaryRoutes)
|
|
|
|
ps.PrimaryRoutes = &v
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
2023-01-13 23:41:11 +00:00
|
|
|
|
|
|
|
if n.Expired {
|
|
|
|
ps.Expired = true
|
|
|
|
}
|
2023-01-23 20:33:58 +00:00
|
|
|
if t := n.KeyExpiry; !t.IsZero() {
|
|
|
|
t = t.Round(time.Second)
|
|
|
|
ps.KeyExpiry = &t
|
|
|
|
}
|
2021-01-28 23:29:17 +00:00
|
|
|
}
|
2020-03-26 05:57:46 +00:00
|
|
|
|
2021-03-15 21:59:35 +00:00
|
|
|
// WhoIs reports the node and user who owns the node with the given IP:port.
|
|
|
|
// If the IP address is a Tailscale IP, the provided port may be 0.
|
2021-01-28 23:29:17 +00:00
|
|
|
// If ok == true, n and u are valid.
|
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 05:14:09 +01:00
|
|
|
func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
|
2021-01-28 23:29:17 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-07-25 04:08:42 +01:00
|
|
|
n, ok = b.nodeByAddr[ipp.Addr()]
|
2021-01-28 23:29:17 +00:00
|
|
|
if !ok {
|
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 05:14:09 +01:00
|
|
|
var ip netip.Addr
|
2021-05-15 02:07:28 +01:00
|
|
|
if ipp.Port() != 0 {
|
2021-03-15 21:59:35 +00:00
|
|
|
ip, ok = b.e.WhoIsIPPort(ipp)
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
return nil, u, false
|
|
|
|
}
|
|
|
|
n, ok = b.nodeByAddr[ip]
|
|
|
|
if !ok {
|
|
|
|
return nil, u, false
|
|
|
|
}
|
2021-01-28 23:29:17 +00:00
|
|
|
}
|
|
|
|
u, ok = b.netMap.UserProfiles[n.User]
|
|
|
|
if !ok {
|
|
|
|
return nil, u, false
|
|
|
|
}
|
|
|
|
return n, u, true
|
2020-03-26 05:57:46 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 18:48:40 +00:00
|
|
|
// PeerCaps returns the capabilities that remote src IP has to
|
|
|
|
// ths current node.
|
2023-07-25 05:07:00 +01:00
|
|
|
func (b *LocalBackend) PeerCaps(src netip.Addr) tailcfg.PeerCapMap {
|
2022-03-18 18:48:40 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-09-19 18:36:07 +01:00
|
|
|
return b.peerCapsLocked(src)
|
|
|
|
}
|
|
|
|
|
2023-07-25 05:07:00 +01:00
|
|
|
func (b *LocalBackend) peerCapsLocked(src netip.Addr) tailcfg.PeerCapMap {
|
2022-03-18 18:48:40 +00:00
|
|
|
if b.netMap == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-04 05:31:40 +01:00
|
|
|
filt := b.filterAtomic.Load()
|
|
|
|
if filt == nil {
|
2022-03-18 18:48:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, a := range b.netMap.Addresses {
|
|
|
|
if !a.IsSingleIP() {
|
|
|
|
continue
|
|
|
|
}
|
2022-09-19 18:36:07 +01:00
|
|
|
dst := a.Addr()
|
|
|
|
if dst.BitLen() == src.BitLen() { // match on family
|
2023-07-25 05:07:00 +01:00
|
|
|
return filt.CapsWithValues(src, dst)
|
2022-03-18 18:48:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-05 22:16:58 +00:00
|
|
|
// SetDecompressor sets a decompression function, which must be a zstd
|
|
|
|
// reader.
|
|
|
|
//
|
|
|
|
// This exists because the iOS/Mac NetworkExtension is very resource
|
|
|
|
// constrained, and the zstd package is too heavy to fit in the
|
|
|
|
// constrained RSS limit.
|
|
|
|
func (b *LocalBackend) SetDecompressor(fn func() (controlclient.Decompressor, error)) {
|
|
|
|
b.newDecompressor = fn
|
|
|
|
}
|
|
|
|
|
2020-06-16 00:04:12 +01:00
|
|
|
// setClientStatus is the callback invoked by the control client whenever it posts a new status.
|
|
|
|
// Among other things, this is where we update the netmap, packet filters, DNS and DERP maps.
|
|
|
|
func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
2020-07-29 02:47:23 +01:00
|
|
|
// The following do not depend on any data for which we need to lock b.
|
2021-10-26 18:19:35 +01:00
|
|
|
if st.Err != nil {
|
2020-07-29 02:47:23 +01:00
|
|
|
// TODO(crawshaw): display in the UI.
|
2021-10-26 18:19:35 +01:00
|
|
|
if errors.Is(st.Err, io.EOF) {
|
2020-12-21 18:58:06 +00:00
|
|
|
b.logf("[v1] Received error: EOF")
|
2021-11-03 22:42:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
b.logf("Received error: %v", st.Err)
|
|
|
|
var uerr controlclient.UserVisibleError
|
|
|
|
if errors.As(st.Err, &uerr) {
|
|
|
|
s := uerr.UserVisibleError()
|
|
|
|
b.send(ipn.Notify{ErrMessage: &s})
|
2020-12-21 18:58:06 +00:00
|
|
|
}
|
2020-07-29 02:47:23 +01:00
|
|
|
return
|
|
|
|
}
|
2021-05-06 04:28:29 +01:00
|
|
|
|
2023-01-11 20:59:08 +00:00
|
|
|
// Track the number of calls
|
|
|
|
currCall := b.numClientStatusCalls.Add(1)
|
|
|
|
|
2021-05-06 04:28:29 +01:00
|
|
|
b.mu.Lock()
|
2023-01-11 20:59:08 +00:00
|
|
|
|
|
|
|
// Handle node expiry in the netmap
|
|
|
|
if st.NetMap != nil {
|
2023-02-06 23:24:48 +00:00
|
|
|
now := time.Now()
|
|
|
|
b.em.flagExpiredPeers(st.NetMap, now)
|
2023-01-11 20:59:08 +00:00
|
|
|
|
|
|
|
// Always stop the existing netmap timer if we have a netmap;
|
|
|
|
// it's possible that we have no nodes expiring, so we should
|
|
|
|
// always cancel the timer and then possibly restart it below.
|
|
|
|
if b.nmExpiryTimer != nil {
|
|
|
|
// Ignore if we can't stop; the atomic check in the
|
|
|
|
// AfterFunc (below) will skip running.
|
|
|
|
b.nmExpiryTimer.Stop()
|
|
|
|
|
|
|
|
// Nil so we don't attempt to stop on the next netmap
|
|
|
|
b.nmExpiryTimer = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Figure out when the next node in the netmap is expiring so we can
|
|
|
|
// start a timer to reconfigure at that point.
|
2023-02-06 23:24:48 +00:00
|
|
|
nextExpiry := b.em.nextPeerExpiry(st.NetMap, now)
|
2023-01-11 20:59:08 +00:00
|
|
|
if !nextExpiry.IsZero() {
|
|
|
|
tmrDuration := nextExpiry.Sub(now) + 10*time.Second
|
|
|
|
b.nmExpiryTimer = time.AfterFunc(tmrDuration, func() {
|
|
|
|
// Skip if the world has moved on past the
|
|
|
|
// saved call (e.g. if we race stopping this
|
|
|
|
// timer).
|
|
|
|
if b.numClientStatusCalls.Load() != currCall {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.logf("setClientStatus: netmap expiry timer triggered after %v", tmrDuration)
|
|
|
|
|
|
|
|
// Call ourselves with the current status again; the logic in
|
|
|
|
// setClientStatus will take care of updating the expired field
|
|
|
|
// of peers in the netmap.
|
|
|
|
b.setClientStatus(st)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 04:28:29 +01:00
|
|
|
wasBlocked := b.blocked
|
2021-11-04 19:19:00 +00:00
|
|
|
keyExpiryExtended := false
|
|
|
|
if st.NetMap != nil {
|
|
|
|
wasExpired := b.keyExpired
|
|
|
|
isExpired := !st.NetMap.Expiry.IsZero() && st.NetMap.Expiry.Before(time.Now())
|
|
|
|
if wasExpired && !isExpired {
|
|
|
|
keyExpiryExtended = true
|
|
|
|
}
|
|
|
|
b.keyExpired = isExpired
|
|
|
|
}
|
2021-05-06 04:28:29 +01:00
|
|
|
b.mu.Unlock()
|
|
|
|
|
2021-11-04 19:19:00 +00:00
|
|
|
if keyExpiryExtended && wasBlocked {
|
|
|
|
// Key extended, unblock the engine
|
|
|
|
b.blockEngineUpdates(false)
|
|
|
|
}
|
|
|
|
|
2021-05-06 04:28:29 +01:00
|
|
|
if st.LoginFinished != nil && wasBlocked {
|
2020-06-16 00:04:12 +01:00
|
|
|
// Auth completed, unblock the engine
|
|
|
|
b.blockEngineUpdates(false)
|
|
|
|
b.authReconfig()
|
2021-02-04 21:12:42 +00:00
|
|
|
b.send(ipn.Notify{LoginFinished: &empty.Message{}})
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
2020-07-29 02:47:23 +01:00
|
|
|
|
|
|
|
// Lock b once and do only the things that require locking.
|
|
|
|
b.mu.Lock()
|
|
|
|
|
ipnlocal: don't assume NeedsLogin immediately after StartLogout().
Previously, there was no server round trip required to log out, so when
you asked ipnlocal to Logout(), it could clear the netmap immediately
and switch to NeedsLogin state.
In v1.8, we added a true Logout operation. ipn.Logout() would trigger
an async cc.StartLogout() and *also* immediately switch to NeedsLogin.
Unfortunately, some frontends would see NeedsLogin and immediately
trigger a new StartInteractiveLogin() operation, before the
controlclient auth state machine actually acted on the Logout command,
thus accidentally invalidating the entire logout operation, retaining
the netmap, and violating the user's expectations.
Instead, add a new LogoutFinished signal from controlclient
(paralleling LoginFinished) and, upon starting a logout, don't update
the ipn state machine until it's received.
Updates: #1918 (BUG-2)
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2021-05-20 07:46:57 +01:00
|
|
|
if st.LogoutFinished != nil {
|
2022-11-29 20:00:40 +00:00
|
|
|
if p := b.pm.CurrentPrefs(); !p.Persist().Valid() || p.Persist().LoginName() == "" {
|
2022-11-12 12:39:29 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := b.pm.DeleteProfile(b.pm.CurrentProfile().ID); err != nil {
|
|
|
|
b.logf("error deleting profile: %v", err)
|
|
|
|
}
|
2022-12-05 04:07:03 +00:00
|
|
|
if err := b.resetForProfileChangeLockedOnEntry(); err != nil {
|
|
|
|
b.logf("resetForProfileChangeLockedOnEntry err: %v", err)
|
|
|
|
}
|
2022-11-12 12:39:29 +00:00
|
|
|
return
|
ipnlocal: don't assume NeedsLogin immediately after StartLogout().
Previously, there was no server round trip required to log out, so when
you asked ipnlocal to Logout(), it could clear the netmap immediately
and switch to NeedsLogin state.
In v1.8, we added a true Logout operation. ipn.Logout() would trigger
an async cc.StartLogout() and *also* immediately switch to NeedsLogin.
Unfortunately, some frontends would see NeedsLogin and immediately
trigger a new StartInteractiveLogin() operation, before the
controlclient auth state machine actually acted on the Logout command,
thus accidentally invalidating the entire logout operation, retaining
the netmap, and violating the user's expectations.
Instead, add a new LogoutFinished signal from controlclient
(paralleling LoginFinished) and, upon starting a logout, don't update
the ipn state machine until it's received.
Updates: #1918 (BUG-2)
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2021-05-20 07:46:57 +01:00
|
|
|
}
|
|
|
|
|
2022-11-12 12:39:29 +00:00
|
|
|
prefsChanged := false
|
2022-11-09 05:58:10 +00:00
|
|
|
prefs := b.pm.CurrentPrefs().AsStruct()
|
2020-07-29 02:47:23 +01:00
|
|
|
netMap := b.netMap
|
|
|
|
interact := b.interact
|
|
|
|
|
ipn{,/ipnlocal}, cmd/tailscale/cli: don't check pref reverts on initial up
The ipn.NewPrefs func returns a populated ipn.Prefs for historical
reasons. It's not used or as important as it once was, but it hasn't
yet been removed. Meanwhile, it contains some default values that are
used on some platforms. Notably, for this bug (#1725), Windows/Mac use
its Prefs.RouteAll true value (to accept subnets), but Linux users
have always gotten a "false" value for that, because that's what
cmd/tailscale's CLI default flag is _for all operating systems_. That
meant that "tailscale up" was rightfully reporting that the user was
changing an implicit setting: RouteAll was changing from true with
false with the user explicitly saying so.
An obvious fix might be to change ipn.NewPrefs to return
Prefs.RouteAll == false on some platforms, but the logic is
complicated by darwin: we want RouteAll true on windows, android, ios,
and the GUI mac app, but not the CLI tailscaled-on-macOS mode. But
even if we used build tags (e.g. the "redo" build tag) to determine
what the default is, that then means we have duplicated and differing
"defaults" between both the CLI up flags and ipn.NewPrefs. Furthering
that complication didn't seem like a good idea.
So, changing the NewPrefs defaults is too invasive at this stage of
the release, as is removing the NewPrefs func entirely.
Instead, tweak slightly the semantics of the ipn.Prefs.ControlURL
field. This now defines that a ControlURL of the empty string means
both "we're uninitialized" and also "just use the default".
Then, once we have the "empty-string-means-unintialized" semantics,
use that to suppress "tailscale up"'s recent implicit-setting-revert
checking safety net, if we've never initialized Tailscale yet.
And update/add tests.
Fixes #1725
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-04-18 06:50:58 +01:00
|
|
|
if prefs.ControlURL == "" {
|
|
|
|
// Once we get a message from the control plane, set
|
|
|
|
// our ControlURL pref explicitly. This causes a
|
|
|
|
// future "tailscale up" to start checking for
|
|
|
|
// implicit setting reverts, which it doesn't do when
|
|
|
|
// ControlURL is blank.
|
|
|
|
prefs.ControlURL = prefs.ControlURLOrDefault()
|
|
|
|
prefsChanged = true
|
|
|
|
}
|
2022-11-10 14:43:59 +00:00
|
|
|
if st.Persist != nil && st.Persist.Valid() {
|
|
|
|
if !prefs.Persist.View().Equals(*st.Persist) {
|
2020-07-29 02:47:23 +01:00
|
|
|
prefsChanged = true
|
2022-11-10 14:43:59 +00:00
|
|
|
prefs.Persist = st.Persist.AsStruct()
|
2020-07-29 02:47:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if st.URL != "" {
|
|
|
|
b.authURL = st.URL
|
2021-04-21 21:37:36 +01:00
|
|
|
b.authURLSticky = st.URL
|
2020-07-29 02:47:23 +01:00
|
|
|
}
|
2021-05-06 04:28:29 +01:00
|
|
|
if wasBlocked && st.LoginFinished != nil {
|
|
|
|
// Interactive login finished successfully (URL visited).
|
|
|
|
// After an interactive login, the user always wants
|
|
|
|
// WantRunning.
|
2022-10-23 18:07:10 +01:00
|
|
|
if !prefs.WantRunning || prefs.LoggedOut {
|
2020-07-29 02:47:23 +01:00
|
|
|
prefsChanged = true
|
|
|
|
}
|
2022-10-23 18:07:10 +01:00
|
|
|
prefs.WantRunning = true
|
|
|
|
prefs.LoggedOut = false
|
2020-07-29 02:47:23 +01:00
|
|
|
}
|
2022-10-25 19:01:33 +01:00
|
|
|
if findExitNodeIDLocked(prefs, st.NetMap) {
|
|
|
|
prefsChanged = true
|
|
|
|
}
|
2022-12-05 04:07:03 +00:00
|
|
|
|
|
|
|
// Perform all mutations of prefs based on the netmap here.
|
|
|
|
if st.NetMap != nil {
|
|
|
|
if b.updatePersistFromNetMapLocked(st.NetMap, prefs) {
|
|
|
|
prefsChanged = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Prefs will be written out if stale; this is not safe unless locked or cloned.
|
|
|
|
if prefsChanged {
|
|
|
|
if err := b.pm.SetPrefs(prefs.View()); err != nil {
|
|
|
|
b.logf("Failed to save new controlclient state: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// initTKALocked is dependent on CurrentProfile.ID, which is initialized
|
|
|
|
// (for new profiles) on the first call to b.pm.SetPrefs.
|
|
|
|
if err := b.initTKALocked(); err != nil {
|
|
|
|
b.logf("initTKALocked: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform all reconfiguration based on the netmap here.
|
2022-04-17 22:49:16 +01:00
|
|
|
if st.NetMap != nil {
|
2023-07-07 16:39:35 +01:00
|
|
|
b.capTailnetLock = hasCapability(st.NetMap, tailcfg.CapabilityTailnetLock)
|
2022-11-30 18:34:59 +00:00
|
|
|
|
2022-10-25 19:01:33 +01:00
|
|
|
b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded
|
2022-11-11 06:29:03 +00:00
|
|
|
if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil {
|
2022-10-25 19:01:33 +01:00
|
|
|
b.logf("[v1] TKA sync error: %v", err)
|
|
|
|
}
|
|
|
|
b.mu.Lock()
|
2022-10-27 21:40:31 +01:00
|
|
|
if b.tka != nil {
|
|
|
|
head, err := b.tka.authority.Head().MarshalText()
|
|
|
|
if err != nil {
|
|
|
|
b.logf("[v1] error marshalling tka head: %v", err)
|
|
|
|
} else {
|
|
|
|
b.cc.SetTKAHead(string(head))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
b.cc.SetTKAHead("")
|
|
|
|
}
|
2022-10-25 19:01:33 +01:00
|
|
|
|
|
|
|
if !envknob.TKASkipSignatureCheck() {
|
|
|
|
b.tkaFilterNetmapLocked(st.NetMap)
|
|
|
|
}
|
|
|
|
b.setNetMapLocked(st.NetMap)
|
2022-11-09 05:58:10 +00:00
|
|
|
b.updateFilterLocked(st.NetMap, prefs.View())
|
2022-04-17 22:49:16 +01:00
|
|
|
}
|
2022-11-09 05:58:10 +00:00
|
|
|
b.mu.Unlock()
|
2022-11-09 23:28:36 +00:00
|
|
|
|
2022-11-09 05:58:10 +00:00
|
|
|
// Now complete the lock-free parts of what we started while locked.
|
|
|
|
if prefsChanged {
|
2022-12-02 08:54:32 +00:00
|
|
|
b.send(ipn.Notify{Prefs: ptr.To(prefs.View())})
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
2022-11-09 05:58:10 +00:00
|
|
|
|
2020-06-16 00:04:12 +01:00
|
|
|
if st.NetMap != nil {
|
2022-10-18 21:54:07 +01:00
|
|
|
if envknob.NoLogsNoSupport() && hasCapability(st.NetMap, tailcfg.CapabilityDataPlaneAuditLogs) {
|
2022-11-29 18:51:10 +00:00
|
|
|
msg := "tailnet requires logging to be enabled. Remove --no-logs-no-support from tailscaled command line."
|
2022-10-18 21:54:07 +01:00
|
|
|
health.SetLocalLogConfigHealth(errors.New(msg))
|
|
|
|
// Connecting to this tailnet without logging is forbidden; boot us outta here.
|
|
|
|
b.mu.Lock()
|
|
|
|
prefs.WantRunning = false
|
|
|
|
p := prefs.View()
|
|
|
|
if err := b.pm.SetPrefs(p); err != nil {
|
|
|
|
b.logf("Failed to save new controlclient state: %v", err)
|
|
|
|
}
|
|
|
|
b.mu.Unlock()
|
2022-12-02 08:54:32 +00:00
|
|
|
b.send(ipn.Notify{ErrMessage: &msg, Prefs: &p})
|
2022-10-18 21:54:07 +01:00
|
|
|
return
|
|
|
|
}
|
2020-07-29 02:47:23 +01:00
|
|
|
if netMap != nil {
|
|
|
|
diff := st.NetMap.ConciseDiffFrom(netMap)
|
2020-06-16 00:04:12 +01:00
|
|
|
if strings.TrimSpace(diff) == "" {
|
2020-12-21 18:58:06 +00:00
|
|
|
b.logf("[v1] netmap diff: (none)")
|
2020-06-16 00:04:12 +01:00
|
|
|
} else {
|
2022-02-13 00:22:33 +00:00
|
|
|
b.logf("[v1] netmap diff:\n%v", diff)
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-29 02:47:23 +01:00
|
|
|
b.e.SetNetworkMap(st.NetMap)
|
2020-09-18 15:44:01 +01:00
|
|
|
b.e.SetDERPMap(st.NetMap.DERPMap)
|
2020-07-29 02:47:23 +01:00
|
|
|
|
2022-09-05 19:36:30 +01:00
|
|
|
// Update our cached DERP map
|
2023-04-17 18:58:40 +01:00
|
|
|
dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf)
|
2022-09-05 19:36:30 +01:00
|
|
|
|
2021-02-04 21:12:42 +00:00
|
|
|
b.send(ipn.Notify{NetMap: st.NetMap})
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
|
|
|
if st.URL != "" {
|
|
|
|
b.logf("Received auth URL: %.20v...", st.URL)
|
2020-10-27 20:57:10 +00:00
|
|
|
if interact {
|
2020-06-16 00:04:12 +01:00
|
|
|
b.popBrowserAuthNow()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.stateMachine()
|
2020-07-29 02:47:23 +01:00
|
|
|
// This is currently (2020-07-28) necessary; conditionally disabling it is fragile!
|
|
|
|
// This is where netmap information gets propagated to router and magicsock.
|
|
|
|
b.authReconfig()
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
|
|
|
|
2022-10-23 18:07:10 +01:00
|
|
|
// findExitNodeIDLocked updates prefs to reference an exit node by ID, rather
|
|
|
|
// than by IP. It returns whether prefs was mutated.
|
|
|
|
func findExitNodeIDLocked(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) {
|
2021-12-01 23:33:14 +00:00
|
|
|
if nm == nil {
|
|
|
|
// No netmap, can't resolve anything.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-21 01:24:16 +00:00
|
|
|
// If we have a desired IP on file, try to find the corresponding
|
|
|
|
// node.
|
2022-10-23 18:07:10 +01:00
|
|
|
if !prefs.ExitNodeIP.IsValid() {
|
2021-02-25 04:05:23 +00:00
|
|
|
return false
|
|
|
|
}
|
2021-01-21 01:24:16 +00:00
|
|
|
|
2021-02-25 04:05:23 +00:00
|
|
|
// IP takes precedence over ID, so if both are set, clear ID.
|
2022-10-23 18:07:10 +01:00
|
|
|
if prefs.ExitNodeID != "" {
|
|
|
|
prefs.ExitNodeID = ""
|
2021-02-25 04:05:23 +00:00
|
|
|
prefsChanged = true
|
2021-01-21 01:24:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, peer := range nm.Peers {
|
2021-02-25 04:05:23 +00:00
|
|
|
for _, addr := range peer.Addresses {
|
2022-10-23 18:07:10 +01:00
|
|
|
if !addr.IsSingleIP() || addr.Addr() != prefs.ExitNodeIP {
|
2021-01-21 01:24:16 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-02-25 04:05:23 +00:00
|
|
|
// Found the node being referenced, upgrade prefs to
|
|
|
|
// reference it directly for next time.
|
2022-10-23 18:07:10 +01:00
|
|
|
prefs.ExitNodeID = peer.StableID
|
|
|
|
prefs.ExitNodeIP = netip.Addr{}
|
2021-02-25 04:05:23 +00:00
|
|
|
return true
|
2021-01-21 01:24:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 18:07:10 +01:00
|
|
|
return prefsChanged
|
2021-01-21 01:24:16 +00:00
|
|
|
}
|
|
|
|
|
2020-06-16 00:04:12 +01:00
|
|
|
// setWgengineStatus is the callback by the wireguard engine whenever it posts a new status.
|
|
|
|
// This updates the endpoints both in the backend and in the control client.
|
|
|
|
func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) {
|
|
|
|
if err != nil {
|
2021-01-07 04:18:29 +00:00
|
|
|
b.logf("wgengine status error: %v", err)
|
2021-09-15 20:59:47 +01:00
|
|
|
b.broadcastStatusChanged()
|
2020-06-16 00:04:12 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if s == nil {
|
|
|
|
b.logf("[unexpected] non-error wgengine update with status=nil: %v", s)
|
2021-09-15 20:59:47 +01:00
|
|
|
b.broadcastStatusChanged()
|
2020-06-16 00:04:12 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b.mu.Lock()
|
2022-03-27 04:12:12 +01:00
|
|
|
if s.AsOf.Before(b.lastStatusTime) {
|
|
|
|
// Don't process a status update that is older than the one we have
|
|
|
|
// already processed. (corp#2579)
|
|
|
|
b.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b.lastStatusTime = s.AsOf
|
2020-10-29 22:26:10 +00:00
|
|
|
es := b.parseWgStatusLocked(s)
|
2021-04-08 05:12:16 +01:00
|
|
|
cc := b.cc
|
2020-06-16 00:04:12 +01:00
|
|
|
b.engineStatus = es
|
2021-09-15 20:10:15 +01:00
|
|
|
needUpdateEndpoints := !endpointsEqual(s.LocalAddrs, b.endpoints)
|
|
|
|
if needUpdateEndpoints {
|
|
|
|
b.endpoints = append([]tailcfg.Endpoint{}, s.LocalAddrs...)
|
|
|
|
}
|
2020-06-16 00:04:12 +01:00
|
|
|
b.mu.Unlock()
|
|
|
|
|
2021-04-08 05:12:16 +01:00
|
|
|
if cc != nil {
|
2021-09-15 20:10:15 +01:00
|
|
|
if needUpdateEndpoints {
|
2022-06-20 00:31:54 +01:00
|
|
|
cc.UpdateEndpoints(s.LocalAddrs)
|
2021-09-15 20:10:15 +01:00
|
|
|
}
|
2021-07-19 19:07:42 +01:00
|
|
|
b.stateMachine()
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
2021-09-15 20:59:47 +01:00
|
|
|
b.broadcastStatusChanged()
|
|
|
|
b.send(ipn.Notify{Engine: &es})
|
|
|
|
}
|
2021-09-16 20:56:43 +01:00
|
|
|
|
2021-09-15 20:59:47 +01:00
|
|
|
func (b *LocalBackend) broadcastStatusChanged() {
|
|
|
|
// The sync.Cond docs say: "It is allowed but not required for the caller to hold c.L during the call."
|
|
|
|
// In this particular case, we must acquire b.statusLock. Otherwise we might broadcast before
|
|
|
|
// the waiter (in requestEngineStatusAndWait) starts to wait, in which case
|
|
|
|
// the waiter can get stuck indefinitely. See PR 2865.
|
2021-09-16 20:56:43 +01:00
|
|
|
b.statusLock.Lock()
|
2020-06-16 00:04:12 +01:00
|
|
|
b.statusChanged.Broadcast()
|
2021-09-16 20:56:43 +01:00
|
|
|
b.statusLock.Unlock()
|
2020-06-16 00:04:12 +01:00
|
|
|
}
|
|
|
|
|
2021-09-15 20:10:15 +01:00
|
|
|
func endpointsEqual(x, y []tailcfg.Endpoint) bool {
|
|
|
|
if len(x) != len(y) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i := range x {
|
|
|
|
if x[i] != y[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-04-07 06:11:50 +01:00
|
|
|
func (b *LocalBackend) SetNotifyCallback(notify func(ipn.Notify)) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
b.notify = notify
|
|
|
|
}
|
|
|
|
|
2021-04-07 16:39:08 +01:00
|
|
|
// SetHTTPTestClient sets an alternate HTTP client to use with
|
|
|
|
// connections to the coordination server. It exists for
|
|
|
|
// testing. Using nil means to use the default.
|
|
|
|
func (b *LocalBackend) SetHTTPTestClient(c *http.Client) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
b.httpTestClient = c
|
|
|
|
}
|
|
|
|
|
2021-04-30 15:23:22 +01:00
|
|
|
// SetControlClientGetterForTesting sets the func that creates a
|
|
|
|
// control plane client. It can be called at most once, before Start.
|
|
|
|
func (b *LocalBackend) SetControlClientGetterForTesting(newControlClient func(controlclient.Options) (controlclient.Client, error)) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.ccGen != nil {
|
|
|
|
panic("invalid use of SetControlClientGetterForTesting after Start")
|
|
|
|
}
|
|
|
|
b.ccGen = newControlClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *LocalBackend) getNewControlClientFunc() clientGen {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.ccGen == nil {
|
|
|
|
// Initialize it rather than just returning the
|
|
|
|
// default to make any future call to
|
|
|
|
// SetControlClientGetterForTesting panic.
|
|
|
|
b.ccGen = func(opts controlclient.Options) (controlclient.Client, error) {
|
|
|
|
return controlclient.New(opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return b.ccGen
|
|
|
|
|