mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 22:47:30 +00:00
ipn/ipnlocal: add support for multiple user profiles
Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
@@ -124,6 +124,7 @@ type LocalBackend struct {
|
||||
keyLogf logger.Logf // for printing list of peers on change
|
||||
statsLogf logger.Logf // for printing peers stats on change
|
||||
e wgengine.Engine
|
||||
pm *profileManager
|
||||
store ipn.StateStore
|
||||
dialer *tsdial.Dialer // non-nil
|
||||
backendLogID string
|
||||
@@ -138,6 +139,10 @@ type LocalBackend struct {
|
||||
sshAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
|
||||
// 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
|
||||
|
||||
filterAtomic atomic.Pointer[filter.Filter]
|
||||
containsViaIPFuncAtomic syncs.AtomicValue[func(netip.Addr) bool]
|
||||
shouldInterceptTCPPortAtomic syncs.AtomicValue[func(uint16) bool]
|
||||
@@ -151,9 +156,6 @@ type LocalBackend struct {
|
||||
notify func(ipn.Notify)
|
||||
cc controlclient.Client
|
||||
ccAuto *controlclient.Auto // if cc is of type *controlclient.Auto
|
||||
stateKey ipn.StateKey // computed in part from user-provided value
|
||||
userID string // current controlling user ID (for Windows, primarily)
|
||||
prefs ipn.PrefsView // may not be Valid.
|
||||
inServerMode bool
|
||||
machinePrivKey key.MachinePrivate
|
||||
nlPrivKey key.NLPrivate
|
||||
@@ -226,11 +228,16 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
|
||||
// but is not actually running.
|
||||
//
|
||||
// If dialer is nil, a new one is made.
|
||||
func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
|
||||
func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, stateKey ipn.StateKey, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
|
||||
if e == nil {
|
||||
panic("ipn.NewLocalBackend: engine must not be nil")
|
||||
}
|
||||
|
||||
pm, err := newProfileManager(store, logf, stateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hi := hostinfo.New()
|
||||
logf.JSON(1, "Hostinfo", hi)
|
||||
envknob.LogCurrent(logf)
|
||||
@@ -253,7 +260,8 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
|
||||
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
||||
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
|
||||
e: e,
|
||||
store: store,
|
||||
pm: pm,
|
||||
store: pm.Store(),
|
||||
dialer: dialer,
|
||||
backendLogID: logid,
|
||||
state: ipn.NoState,
|
||||
@@ -292,7 +300,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
|
||||
|
||||
for _, component := range debuggableComponents {
|
||||
key := componentStateKey(component)
|
||||
if ut, err := ipn.ReadStoreInt(store, key); err == nil {
|
||||
if ut, err := ipn.ReadStoreInt(pm.Store(), key); err == nil {
|
||||
if until := time.Unix(ut, 0); until.After(time.Now()) {
|
||||
// conditional to avoid log spam at start when off
|
||||
b.SetComponentDebugLogging(component, until)
|
||||
@@ -450,7 +458,7 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
|
||||
|
||||
// If the local network configuration has changed, our filter may
|
||||
// need updating to tweak default routes.
|
||||
b.updateFilterLocked(b.netMap, b.prefs)
|
||||
b.updateFilterLocked(b.netMap, b.pm.CurrentPrefs())
|
||||
|
||||
if peerAPIListenAsync && b.netMap != nil && b.state == ipn.Running {
|
||||
want := len(b.netMap.Addresses)
|
||||
@@ -520,7 +528,7 @@ func stripKeysFromPrefs(p ipn.PrefsView) ipn.PrefsView {
|
||||
func (b *LocalBackend) Prefs() ipn.PrefsView {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return stripKeysFromPrefs(b.prefs)
|
||||
return stripKeysFromPrefs(b.pm.CurrentPrefs())
|
||||
}
|
||||
|
||||
// Status returns the latest status of the backend and its
|
||||
@@ -577,14 +585,14 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
|
||||
s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
|
||||
s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied
|
||||
s.CurrentTailnet.Name = b.netMap.Domain
|
||||
if b.prefs.Valid() && !b.prefs.ExitNodeID().IsZero() {
|
||||
if exitPeer, ok := b.netMap.PeerWithStableID(b.prefs.ExitNodeID()); ok {
|
||||
if prefs := b.pm.CurrentPrefs(); prefs.Valid() && !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: b.prefs.ExitNodeID(),
|
||||
ID: prefs.ExitNodeID(),
|
||||
Online: online,
|
||||
TailscaleIPs: exitPeer.Addresses,
|
||||
}
|
||||
@@ -628,6 +636,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
|
||||
for id, up := range b.netMap.UserProfiles {
|
||||
sb.AddUser(id, up)
|
||||
}
|
||||
exitNodeID := b.pm.CurrentPrefs().ExitNodeID()
|
||||
for _, p := range b.netMap.Peers {
|
||||
var lastSeen time.Time
|
||||
if p.LastSeen != nil {
|
||||
@@ -650,7 +659,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
|
||||
LastSeen: lastSeen,
|
||||
Online: p.Online != nil && *p.Online,
|
||||
ShareeNode: p.Hostinfo.ShareeNode(),
|
||||
ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID(),
|
||||
ExitNode: p.StableID != "" && p.StableID == exitNodeID,
|
||||
SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(),
|
||||
}
|
||||
peerStatusFromNode(ps, p)
|
||||
@@ -796,8 +805,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
||||
b.e.SetNetworkMap(new(netmap.NetworkMap))
|
||||
}
|
||||
|
||||
prefs := b.prefs.AsStruct()
|
||||
stateKey := b.stateKey
|
||||
prefs := b.pm.CurrentPrefs().AsStruct()
|
||||
netMap := b.netMap
|
||||
interact := b.interact
|
||||
|
||||
@@ -834,9 +842,6 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
||||
prefsChanged = true
|
||||
}
|
||||
// Prefs will be written out; this is not safe unless locked or cloned.
|
||||
if prefsChanged {
|
||||
b.prefs = prefs.View()
|
||||
}
|
||||
if st.NetMap != nil {
|
||||
b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded
|
||||
if err := b.tkaSyncIfNeeded(st.NetMap); err != nil {
|
||||
@@ -858,23 +863,22 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
||||
b.tkaFilterNetmapLocked(st.NetMap)
|
||||
}
|
||||
b.setNetMapLocked(st.NetMap)
|
||||
b.updateFilterLocked(st.NetMap, b.prefs)
|
||||
b.updateFilterLocked(st.NetMap, prefs.View())
|
||||
}
|
||||
|
||||
if prefsChanged {
|
||||
if err := b.pm.SetPrefs(prefs.View()); err != nil {
|
||||
b.logf("Failed to save new controlclient state: %v", err)
|
||||
}
|
||||
}
|
||||
userID := b.userID
|
||||
b.mu.Unlock()
|
||||
|
||||
// Now complete the lock-free parts of what we started while locked.
|
||||
if prefsChanged {
|
||||
if stateKey != "" {
|
||||
if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
|
||||
b.logf("Failed to save new controlclient state: %v", err)
|
||||
}
|
||||
}
|
||||
b.writeServerModeStartState(userID, prefs.View())
|
||||
|
||||
p := prefs.View()
|
||||
b.send(ipn.Notify{Prefs: &p})
|
||||
}
|
||||
|
||||
if st.NetMap != nil {
|
||||
if netMap != nil {
|
||||
diff := st.NetMap.ConciseDiffFrom(netMap)
|
||||
@@ -1064,8 +1068,7 @@ func (b *LocalBackend) startIsNoopLocked(opts ipn.Options) bool {
|
||||
return b.state == ipn.Running &&
|
||||
b.hostinfo != nil &&
|
||||
b.hostinfo.FrontendLogID == opts.FrontendLogID &&
|
||||
b.stateKey == opts.StateKey &&
|
||||
opts.Prefs == nil &&
|
||||
opts.LegacyMigrationPrefs == nil &&
|
||||
opts.UpdatePrefs == nil &&
|
||||
opts.AuthKey == ""
|
||||
}
|
||||
@@ -1081,29 +1084,30 @@ func (b *LocalBackend) startIsNoopLocked(opts ipn.Options) bool {
|
||||
// actually a supported operation (it should be, but it's very unclear
|
||||
// from the following whether or not that is a safe transition).
|
||||
func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
if opts.Prefs == nil && opts.StateKey == "" {
|
||||
return errors.New("no state key or prefs provided")
|
||||
if opts.LegacyMigrationPrefs == nil && !b.pm.CurrentPrefs().Valid() {
|
||||
return errors.New("no prefs provided")
|
||||
}
|
||||
|
||||
if opts.Prefs != nil {
|
||||
b.logf("Start: %v", opts.Prefs.Pretty())
|
||||
if opts.LegacyMigrationPrefs != nil {
|
||||
b.logf("Start: %v", opts.LegacyMigrationPrefs.Pretty())
|
||||
} else {
|
||||
b.logf("Start")
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
profileID := b.pm.CurrentProfile().ID
|
||||
|
||||
// The iOS client sends a "Start" whenever its UI screen comes
|
||||
// up, just because it wants a netmap. That should be fixed,
|
||||
// but meanwhile we can make Start cheaper here for such a
|
||||
// case and not restart the world (which takes a few seconds).
|
||||
// Instead, just send a notify with the state that iOS needs.
|
||||
if b.startIsNoopLocked(opts) {
|
||||
if b.startIsNoopLocked(opts) && profileID == b.lastProfileID {
|
||||
b.logf("Start: already running; sending notify")
|
||||
nm := b.netMap
|
||||
state := b.state
|
||||
b.mu.Unlock()
|
||||
p := b.prefs
|
||||
p := b.pm.CurrentPrefs()
|
||||
b.send(ipn.Notify{
|
||||
State: &state,
|
||||
NetMap: nm,
|
||||
@@ -1139,26 +1143,24 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
b.hostinfo = hostinfo
|
||||
b.state = ipn.NoState
|
||||
|
||||
if err := b.loadStateLocked(opts.StateKey, opts.Prefs); err != nil {
|
||||
if err := b.migrateStateLocked(opts.LegacyMigrationPrefs); err != nil {
|
||||
b.mu.Unlock()
|
||||
return fmt.Errorf("loading requested state: %v", err)
|
||||
}
|
||||
|
||||
if opts.UpdatePrefs != nil {
|
||||
newPrefs := opts.UpdatePrefs
|
||||
newPrefs.Persist = b.prefs.Persist()
|
||||
b.prefs = newPrefs.View()
|
||||
|
||||
if opts.StateKey != "" {
|
||||
if err := b.store.WriteState(opts.StateKey, b.prefs.ToBytes()); err != nil {
|
||||
b.logf("failed to save UpdatePrefs state: %v", err)
|
||||
}
|
||||
oldPrefs := b.pm.CurrentPrefs()
|
||||
newPrefs := opts.UpdatePrefs.Clone()
|
||||
newPrefs.Persist = oldPrefs.Persist()
|
||||
pv := newPrefs.View()
|
||||
if err := b.pm.SetPrefs(pv); err != nil {
|
||||
b.logf("failed to save UpdatePrefs state: %v", err)
|
||||
}
|
||||
b.setAtomicValuesFromPrefs(b.prefs)
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.setAtomicValuesFromPrefs(pv)
|
||||
}
|
||||
|
||||
wantRunning := b.prefs.WantRunning()
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
wantRunning := prefs.WantRunning()
|
||||
if wantRunning {
|
||||
if err := b.initMachineKeyLocked(); err != nil {
|
||||
return fmt.Errorf("initMachineKeyLocked: %w", err)
|
||||
@@ -1168,17 +1170,20 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
return fmt.Errorf("initNLKeyLocked: %w", err)
|
||||
}
|
||||
|
||||
loggedOut := b.prefs.LoggedOut()
|
||||
loggedOut := prefs.LoggedOut()
|
||||
|
||||
b.inServerMode = b.prefs.ForceDaemon()
|
||||
b.serverURL = b.prefs.ControlURLOrDefault()
|
||||
b.inServerMode = prefs.ForceDaemon()
|
||||
b.serverURL = prefs.ControlURLOrDefault()
|
||||
if b.inServerMode || runtime.GOOS == "windows" {
|
||||
b.logf("Start: serverMode=%v", b.inServerMode)
|
||||
}
|
||||
b.applyPrefsToHostinfo(hostinfo, b.prefs)
|
||||
b.applyPrefsToHostinfo(hostinfo, prefs)
|
||||
|
||||
b.setNetMapLocked(nil)
|
||||
persistv := b.prefs.Persist()
|
||||
persistv := prefs.Persist()
|
||||
if persistv == nil {
|
||||
persistv = new(persist.Persist)
|
||||
}
|
||||
b.updateFilterLocked(nil, ipn.PrefsView{})
|
||||
b.mu.Unlock()
|
||||
|
||||
@@ -1206,10 +1211,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
discoPublic := b.e.DiscoPublicKey()
|
||||
|
||||
var err error
|
||||
if persistv == nil {
|
||||
// let controlclient initialize it
|
||||
persistv = &persist.Persist{}
|
||||
}
|
||||
|
||||
isNetstack := wgengine.IsNetstackRouter(b.e)
|
||||
debugFlags := controlDebugFlags
|
||||
@@ -1272,10 +1273,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
|
||||
b.e.SetNetInfoCallback(b.setNetInfo)
|
||||
|
||||
b.mu.Lock()
|
||||
prefs := b.prefs
|
||||
b.mu.Unlock()
|
||||
|
||||
blid := b.backendLogID
|
||||
b.logf("Backend: logs: be:%v fe:%v", blid, opts.FrontendLogID)
|
||||
b.send(ipn.Notify{BackendLogID: &blid})
|
||||
@@ -1779,8 +1776,8 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
|
||||
}
|
||||
|
||||
var legacyMachineKey key.MachinePrivate
|
||||
if b.prefs.Persist() != nil {
|
||||
legacyMachineKey = b.prefs.Persist().LegacyFrontendPrivateMachineKey
|
||||
if p := b.pm.CurrentPrefs().Persist(); p != nil {
|
||||
legacyMachineKey = p.LegacyFrontendPrivateMachineKey
|
||||
}
|
||||
|
||||
keyText, err := b.store.ReadState(ipn.MachineKeyStateKey)
|
||||
@@ -1804,11 +1801,6 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
|
||||
// have a legacy machine key, use that. Otherwise generate a
|
||||
// new one.
|
||||
if !legacyMachineKey.IsZero() {
|
||||
if b.stateKey == "" {
|
||||
b.logf("using frontend-provided legacy machine key")
|
||||
} else {
|
||||
b.logf("using legacy machine key from state key %q", b.stateKey)
|
||||
}
|
||||
b.machinePrivKey = legacyMachineKey
|
||||
} else {
|
||||
b.logf("generating new machine key")
|
||||
@@ -1865,110 +1857,23 @@ func (b *LocalBackend) initNLKeyLocked() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeServerModeStartState stores the ServerModeStartKey value based on the current
|
||||
// user and prefs. If userID is blank or prefs is blank, no work is done.
|
||||
//
|
||||
// b.mu may either be held or not.
|
||||
func (b *LocalBackend) writeServerModeStartState(userID string, prefs ipn.PrefsView) {
|
||||
if userID == "" || !prefs.Valid() {
|
||||
return
|
||||
// migrateStateLocked migrates state from the frontend to the backend.
|
||||
// It is a no-op if prefs is nil
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) migrateStateLocked(prefs *ipn.Prefs) (err error) {
|
||||
if prefs == nil && !b.pm.CurrentPrefs().Valid() {
|
||||
return fmt.Errorf("no prefs provided and no current profile")
|
||||
}
|
||||
|
||||
if prefs.ForceDaemon() {
|
||||
stateKey := ipn.StateKey("user-" + userID)
|
||||
if err := b.store.WriteState(ipn.ServerModeStartKey, []byte(stateKey)); err != nil {
|
||||
b.logf("WriteState error: %v", err)
|
||||
}
|
||||
// It's important we do this here too, even if it looks
|
||||
// redundant with the one in the 'if stateKey != ""'
|
||||
// check block above. That one won't fire in the case
|
||||
// where the Windows client started up in client mode.
|
||||
// This happens when we transition into server mode:
|
||||
if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
|
||||
b.logf("WriteState error: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := b.store.WriteState(ipn.ServerModeStartKey, nil); err != nil {
|
||||
b.logf("WriteState error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadStateLocked sets b.prefs and b.stateKey based on a complex
|
||||
// combination of key, prefs, and legacyPath. b.mu must be held when
|
||||
// calling.
|
||||
func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err error) {
|
||||
if prefs == nil && key == "" {
|
||||
panic("state key and prefs are both unset")
|
||||
}
|
||||
|
||||
// Optimistically set stateKey (for initMachineKeyLocked's
|
||||
// logging), but revert it if we return an error so a later SetPrefs
|
||||
// call can't pick it up if it's bogus.
|
||||
b.stateKey = key
|
||||
defer func() {
|
||||
if err != nil {
|
||||
b.stateKey = ""
|
||||
}
|
||||
}()
|
||||
|
||||
if key == "" {
|
||||
// Frontend owns the state, we just need to obey it.
|
||||
//
|
||||
// If the frontend (e.g. on Windows) supplied the
|
||||
// optional/legacy machine key then it's used as the
|
||||
// value instead of making up a new one.
|
||||
b.logf("using frontend prefs: %s", prefs.Pretty())
|
||||
b.prefs = prefs.Clone().View()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.writeServerModeStartState(b.userID, b.prefs)
|
||||
return nil
|
||||
}
|
||||
|
||||
if prefs != nil {
|
||||
// Backend owns the state, but frontend is trying to migrate
|
||||
// state into the backend.
|
||||
b.logf("importing frontend prefs into backend store; frontend prefs: %s", prefs.Pretty())
|
||||
if err := b.store.WriteState(key, prefs.ToBytes()); err != nil {
|
||||
if err := b.pm.SetPrefs(prefs.View()); err != nil {
|
||||
return fmt.Errorf("store.WriteState: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := b.store.ReadState(key)
|
||||
switch {
|
||||
case errors.Is(err, ipn.ErrStateNotExist):
|
||||
prefs := ipn.NewPrefs()
|
||||
prefs.WantRunning = false
|
||||
b.logf("using backend prefs; created empty state for %q: %s", key, prefs.Pretty())
|
||||
b.prefs = prefs.View()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
return nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("backend prefs: store.ReadState(%q): %v", key, err)
|
||||
}
|
||||
prefs, err = ipn.PrefsFromBytes(bs)
|
||||
if err != nil {
|
||||
b.logf("using backend prefs for %q", key)
|
||||
return fmt.Errorf("PrefsFromBytes: %v", err)
|
||||
}
|
||||
|
||||
// Ignore any old stored preferences for https://login.tailscale.com
|
||||
// as the control server that would override the new default of
|
||||
// controlplane.tailscale.com.
|
||||
// This makes sure that mobile clients go through the new
|
||||
// frontends where we're (2021-10-02) doing battery
|
||||
// optimization work ahead of turning down the old backends.
|
||||
if prefs != nil && prefs.ControlURL != "" &&
|
||||
prefs.ControlURL != ipn.DefaultControlURL &&
|
||||
ipn.IsLoginServerSynonym(prefs.ControlURL) {
|
||||
prefs.ControlURL = ""
|
||||
}
|
||||
|
||||
b.logf("using backend prefs for %q: %s", key, prefs.Pretty())
|
||||
b.prefs = prefs.View()
|
||||
|
||||
b.setAtomicValuesFromPrefs(b.prefs)
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.setAtomicValuesFromPrefs(b.pm.CurrentPrefs())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2010,8 +1915,8 @@ func (b *LocalBackend) setTCPPortsIntercepted(ports []uint16) {
|
||||
b.shouldInterceptTCPPortAtomic.Store(f)
|
||||
}
|
||||
|
||||
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
|
||||
// from the prefs p, which may be nil.
|
||||
// setAtomicValuesFromPrefs populates sshAtomicBool, containsViaIPFuncAtomic
|
||||
// and shouldInterceptTCPPortAtomic from the prefs p, which may be !Valid().
|
||||
func (b *LocalBackend) setAtomicValuesFromPrefs(p ipn.PrefsView) {
|
||||
b.sshAtomicBool.Store(p.Valid() && p.RunSSH() && envknob.CanSSHD())
|
||||
|
||||
@@ -2020,6 +1925,7 @@ func (b *LocalBackend) setAtomicValuesFromPrefs(p ipn.PrefsView) {
|
||||
b.setTCPPortsIntercepted(nil)
|
||||
} else {
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(p.AdvertiseRoutes().Filter(tsaddr.IsViaPrefix)))
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2175,15 +2081,16 @@ func (b *LocalBackend) shouldUploadServices() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if !b.prefs.Valid() || b.netMap == nil {
|
||||
p := b.pm.CurrentPrefs()
|
||||
if !p.Valid() || b.netMap == nil {
|
||||
return false // default to safest setting
|
||||
}
|
||||
return !b.prefs.ShieldsUp() && b.netMap.CollectServices
|
||||
return !p.ShieldsUp() && b.netMap.CollectServices
|
||||
}
|
||||
|
||||
func (b *LocalBackend) SetCurrentUserID(uid string) {
|
||||
b.mu.Lock()
|
||||
b.userID = uid
|
||||
b.pm.SetCurrentUser(uid)
|
||||
b.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -2248,7 +2155,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
|
||||
}
|
||||
|
||||
func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage string) {
|
||||
if !b.prefs.Valid() || !b.prefs.RunSSH() {
|
||||
if p := b.pm.CurrentPrefs(); !p.Valid() || !p.RunSSH() {
|
||||
return ""
|
||||
}
|
||||
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
|
||||
@@ -2274,10 +2181,11 @@ func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage
|
||||
}
|
||||
|
||||
func (b *LocalBackend) isDefaultServerLocked() bool {
|
||||
if !b.prefs.Valid() {
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
if !prefs.Valid() {
|
||||
return true // assume true until set otherwise
|
||||
}
|
||||
return b.prefs.ControlURLOrDefault() == ipn.DefaultControlURL
|
||||
return prefs.ControlURLOrDefault() == ipn.DefaultControlURL
|
||||
}
|
||||
|
||||
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
|
||||
@@ -2287,8 +2195,8 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
|
||||
b.egg = true
|
||||
go b.doSetHostinfoFilterServices(b.hostinfo.Clone())
|
||||
}
|
||||
p0 := b.prefs
|
||||
p1 := b.prefs.AsStruct()
|
||||
p0 := b.pm.CurrentPrefs()
|
||||
p1 := b.pm.CurrentPrefs().AsStruct()
|
||||
p1.ApplyEdits(mp)
|
||||
if err := b.checkPrefsLocked(p1); err != nil {
|
||||
b.mu.Unlock()
|
||||
@@ -2330,66 +2238,61 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
|
||||
// It returns a readonly copy of the new prefs.
|
||||
func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn.PrefsView {
|
||||
netMap := b.netMap
|
||||
stateKey := b.stateKey
|
||||
oldp := b.prefs
|
||||
newp.Persist = oldp.Persist() // caller isn't allowed to override this
|
||||
b.setAtomicValuesFromPrefs(newp.View())
|
||||
|
||||
oldp := b.pm.CurrentPrefs()
|
||||
if oldp.Valid() {
|
||||
newp.Persist = oldp.Persist().Clone() // caller isn't allowed to override this
|
||||
}
|
||||
// findExitNodeIDLocked returns whether it updated b.prefs, but
|
||||
// everything in this function treats b.prefs as completely new
|
||||
// anyway. No-op if no exit node resolution is needed.
|
||||
findExitNodeIDLocked(newp, netMap)
|
||||
b.prefs = newp.View()
|
||||
b.setAtomicValuesFromPrefs(b.prefs)
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.inServerMode = b.prefs.ForceDaemon()
|
||||
// We do this to avoid holding the lock while doing everything else.
|
||||
b.inServerMode = newp.ForceDaemon
|
||||
|
||||
oldHi := b.hostinfo
|
||||
newHi := oldHi.Clone()
|
||||
b.applyPrefsToHostinfo(newHi, b.prefs)
|
||||
b.applyPrefsToHostinfo(newHi, newp.View())
|
||||
b.hostinfo = newHi
|
||||
hostInfoChanged := !oldHi.Equal(newHi)
|
||||
userID := b.userID
|
||||
cc := b.cc
|
||||
|
||||
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
|
||||
if caller == "SetPrefs" {
|
||||
b.logf("SetPrefs: %v", b.prefs.Pretty())
|
||||
b.logf("SetPrefs: %v", newp.Pretty())
|
||||
}
|
||||
b.updateFilterLocked(netMap, b.prefs)
|
||||
b.updateFilterLocked(netMap, newp.View())
|
||||
|
||||
if oldp.ShouldSSHBeRunning() && !b.prefs.ShouldSSHBeRunning() {
|
||||
if oldp.ShouldSSHBeRunning() && !newp.ShouldSSHBeRunning() {
|
||||
if b.sshServer != nil {
|
||||
go b.sshServer.Shutdown()
|
||||
b.sshServer = nil
|
||||
}
|
||||
}
|
||||
prefs := b.prefs // We can grab the view before unlocking. It can't be mutated.
|
||||
b.mu.Unlock()
|
||||
|
||||
if stateKey != "" {
|
||||
if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
|
||||
b.logf("failed to save new controlclient state: %v", err)
|
||||
}
|
||||
}
|
||||
b.writeServerModeStartState(userID, prefs)
|
||||
|
||||
if netMap != nil {
|
||||
if login := netMap.UserProfiles[netMap.User].LoginName; login != "" {
|
||||
if prefs.Persist() == nil {
|
||||
up := netMap.UserProfiles[netMap.User]
|
||||
if login := up.LoginName; login != "" {
|
||||
if newp.Persist == nil {
|
||||
b.logf("active login: %s", login)
|
||||
} else if prefs.Persist().LoginName != login {
|
||||
// Corp issue 461: sometimes the wrong prefs are
|
||||
// logged; the frontend isn't always getting
|
||||
// notified (to update its prefs/persist) on
|
||||
// account switch. Log this while we figure it
|
||||
// out.
|
||||
b.logf("active login: %q ([unexpected] corp#461, not %q)", prefs.Persist().LoginName, login)
|
||||
} else {
|
||||
if newp.Persist.LoginName != login {
|
||||
b.logf("active login: %q (changed from %q)", login, newp.Persist.LoginName)
|
||||
newp.Persist.LoginName = login
|
||||
}
|
||||
newp.Persist.UserProfile = up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oldp.ShieldsUp() != prefs.ShieldsUp() || hostInfoChanged {
|
||||
prefs := newp.View()
|
||||
if err := b.pm.SetPrefs(prefs); err != nil {
|
||||
b.logf("failed to save new controlclient state: %v", err)
|
||||
}
|
||||
b.lastProfileID = b.pm.CurrentProfile().ID
|
||||
b.mu.Unlock()
|
||||
|
||||
if oldp.ShieldsUp() != newp.ShieldsUp || hostInfoChanged {
|
||||
b.doSetHostinfoFilterServices(newHi)
|
||||
}
|
||||
|
||||
@@ -2397,12 +2300,12 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
|
||||
b.e.SetDERPMap(netMap.DERPMap)
|
||||
}
|
||||
|
||||
if !oldp.WantRunning() && prefs.WantRunning() {
|
||||
if !oldp.WantRunning() && newp.WantRunning {
|
||||
b.logf("transitioning to running; doing Login...")
|
||||
cc.Login(nil, controlclient.LoginDefault)
|
||||
}
|
||||
|
||||
if oldp.WantRunning() != prefs.WantRunning() {
|
||||
if oldp.WantRunning() != newp.WantRunning {
|
||||
b.stateMachine()
|
||||
} else {
|
||||
b.authReconfig()
|
||||
@@ -2536,7 +2439,7 @@ func (b *LocalBackend) blockEngineUpdates(block bool) {
|
||||
func (b *LocalBackend) authReconfig() {
|
||||
b.mu.Lock()
|
||||
blocked := b.blocked
|
||||
prefs := b.prefs
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
nm := b.netMap
|
||||
hasPAC := b.prevIfState.HasPAC()
|
||||
disableSubnetsIfPAC := nm != nil && nm.Debug != nil && nm.Debug.DisableSubnetsIfPAC.EqualBool(true)
|
||||
@@ -3139,9 +3042,15 @@ func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs ipn.Pref
|
||||
// happen".
|
||||
func (b *LocalBackend) enterState(newState ipn.State) {
|
||||
b.mu.Lock()
|
||||
b.enterStateLockedOnEntry(newState)
|
||||
}
|
||||
|
||||
// enterStateLockedOnEntry is like enterState but requires b.mu be held to call
|
||||
// it, but it unlocks b.mu when done.
|
||||
func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State) {
|
||||
oldState := b.state
|
||||
b.state = newState
|
||||
prefs := b.prefs
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
netMap := b.netMap
|
||||
activeLogin := b.activeLogin
|
||||
authURL := b.authURL
|
||||
@@ -3157,12 +3066,12 @@ func (b *LocalBackend) enterState(newState ipn.State) {
|
||||
|
||||
// prefs may change irrespective of state; WantRunning should be explicitly
|
||||
// set before potential early return even if the state is unchanged.
|
||||
health.SetIPNState(newState.String(), prefs.WantRunning())
|
||||
health.SetIPNState(newState.String(), prefs.Valid() && prefs.WantRunning())
|
||||
if oldState == newState {
|
||||
return
|
||||
}
|
||||
b.logf("Switching ipn state %v -> %v (WantRunning=%v, nm=%v)",
|
||||
oldState, newState, prefs.WantRunning, netMap != nil)
|
||||
oldState, newState, prefs.WantRunning(), netMap != nil)
|
||||
b.send(ipn.Notify{State: &newState})
|
||||
|
||||
switch newState {
|
||||
@@ -3189,6 +3098,8 @@ func (b *LocalBackend) enterState(newState ipn.State) {
|
||||
addrs = append(addrs, addr.Addr().String())
|
||||
}
|
||||
systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
|
||||
case ipn.NoState:
|
||||
// Do nothing.
|
||||
default:
|
||||
b.logf("[unexpected] unknown newState %#v", newState)
|
||||
}
|
||||
@@ -3199,8 +3110,8 @@ func (b *LocalBackend) hasNodeKey() bool {
|
||||
// we can't use b.Prefs(), because it strips the keys, oops!
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
return b.prefs.Valid() && b.prefs.Persist() != nil && !b.prefs.Persist().PrivateNodeKey.IsZero()
|
||||
p := b.pm.CurrentPrefs()
|
||||
return p.Valid() && p.Persist() != nil && !p.Persist().PrivateNodeKey.IsZero()
|
||||
}
|
||||
|
||||
// nextState returns the state the backend seems to be in, based on
|
||||
@@ -3209,15 +3120,20 @@ func (b *LocalBackend) nextState() ipn.State {
|
||||
b.mu.Lock()
|
||||
b.assertClientLocked()
|
||||
var (
|
||||
cc = b.cc
|
||||
netMap = b.netMap
|
||||
state = b.state
|
||||
blocked = b.blocked
|
||||
wantRunning = b.prefs.WantRunning()
|
||||
loggedOut = b.prefs.LoggedOut()
|
||||
st = b.engineStatus
|
||||
keyExpired = b.keyExpired
|
||||
cc = b.cc
|
||||
netMap = b.netMap
|
||||
state = b.state
|
||||
blocked = b.blocked
|
||||
st = b.engineStatus
|
||||
keyExpired = b.keyExpired
|
||||
|
||||
wantRunning = false
|
||||
loggedOut = false
|
||||
)
|
||||
if p := b.pm.CurrentPrefs(); p.Valid() {
|
||||
wantRunning = p.WantRunning()
|
||||
loggedOut = p.LoggedOut()
|
||||
}
|
||||
b.mu.Unlock()
|
||||
|
||||
switch {
|
||||
@@ -3328,15 +3244,13 @@ func (b *LocalBackend) ResetForClientDisconnect() {
|
||||
go b.cc.Shutdown()
|
||||
b.cc = nil
|
||||
}
|
||||
b.stateKey = ""
|
||||
b.userID = ""
|
||||
b.setNetMapLocked(nil)
|
||||
b.prefs = new(ipn.Prefs).View()
|
||||
b.pm.Reset()
|
||||
b.keyExpired = false
|
||||
b.authURL = ""
|
||||
b.authURLSticky = ""
|
||||
b.activeLogin = ""
|
||||
b.setAtomicValuesFromPrefs(b.prefs)
|
||||
b.setAtomicValuesFromPrefs(ipn.PrefsView{})
|
||||
b.setTCPPortsIntercepted(nil)
|
||||
}
|
||||
|
||||
@@ -3434,10 +3348,19 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
b.dialer.SetNetMap(nm)
|
||||
var login string
|
||||
if nm != nil {
|
||||
login = nm.UserProfiles[nm.User].LoginName
|
||||
up := nm.UserProfiles[nm.User]
|
||||
login = up.LoginName
|
||||
if login == "" {
|
||||
login = "<missing-profile>"
|
||||
}
|
||||
if cp := b.pm.CurrentProfile(); cp.ID != "" && cp.UserProfile.ID == 0 {
|
||||
// Migration to profiles: we didn't use to persist
|
||||
// the UserProfile, so if we don't have one, fill it
|
||||
// in from the NetworkMap.
|
||||
prefs := b.pm.CurrentPrefs().AsStruct()
|
||||
prefs.Persist.UserProfile = up
|
||||
b.pm.SetPrefs(prefs.View())
|
||||
}
|
||||
}
|
||||
b.netMap = nm
|
||||
if login != b.activeLogin {
|
||||
@@ -3459,7 +3382,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
}
|
||||
b.capFileSharing = fs
|
||||
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
|
||||
if nm == nil {
|
||||
b.nodeByAddr = nil
|
||||
return
|
||||
@@ -3498,17 +3421,16 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
// the ports that tailscaled should handle as a function of b.netMap and b.prefs.
|
||||
//
|
||||
// b.mu must be held.
|
||||
func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked() {
|
||||
func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.PrefsView) {
|
||||
handlePorts := make([]uint16, 0, 4)
|
||||
|
||||
prefs := b.prefs
|
||||
if prefs.Valid() && prefs.RunSSH() && envknob.CanSSHD() {
|
||||
handlePorts = append(handlePorts, 22)
|
||||
}
|
||||
|
||||
nm := b.netMap
|
||||
if nm != nil && nm.SelfNode != nil {
|
||||
profileID := fmt.Sprintf("node-%s", nm.SelfNode.StableID) // TODO(maisem,bradfitz): something else?
|
||||
profileID := b.pm.CurrentProfile().ID
|
||||
confKey := ipn.ServeConfigKey(profileID)
|
||||
if confj, err := b.store.ReadState(confKey); err == nil {
|
||||
if !b.lastServeConfJSON.Equal(mem.B(confj)) {
|
||||
@@ -3546,7 +3468,7 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
|
||||
if nm.SelfNode == nil {
|
||||
return errors.New("netMap SelfNode is nil")
|
||||
}
|
||||
profileID := fmt.Sprintf("node-%s", nm.SelfNode.StableID) // TODO(maisem,bradfitz): something else?
|
||||
profileID := b.pm.CurrentProfile().ID
|
||||
confKey := ipn.ServeConfigKey(profileID)
|
||||
|
||||
var bs []byte
|
||||
@@ -3561,7 +3483,7 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
|
||||
return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
|
||||
}
|
||||
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3579,10 +3501,11 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
|
||||
func (b *LocalBackend) operatorUserName() string {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if !b.prefs.Valid() {
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
if !prefs.Valid() {
|
||||
return ""
|
||||
}
|
||||
return b.prefs.OperatorUser()
|
||||
return prefs.OperatorUser()
|
||||
}
|
||||
|
||||
// OperatorUserID returns the current pref's OperatorUser's ID (in
|
||||
@@ -3605,8 +3528,8 @@ func (b *LocalBackend) OperatorUserID() string {
|
||||
// in the test harness.
|
||||
func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeKey key.NodePublic) {
|
||||
b.mu.Lock()
|
||||
prefs := b.prefs
|
||||
machinePrivKey := b.machinePrivKey
|
||||
prefs := b.pm.CurrentPrefs()
|
||||
b.mu.Unlock()
|
||||
|
||||
if !prefs.Valid() || machinePrivKey.IsZero() {
|
||||
@@ -3723,8 +3646,8 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
|
||||
|
||||
b.mu.Lock()
|
||||
cc := b.ccAuto
|
||||
if b.prefs.Valid() {
|
||||
req.NodeKey = b.prefs.Persist().PublicNodeKey()
|
||||
if prefs := b.pm.CurrentPrefs(); prefs.Valid() {
|
||||
req.NodeKey = prefs.Persist().PrivateNodeKey.Public()
|
||||
}
|
||||
b.mu.Unlock()
|
||||
if cc == nil {
|
||||
@@ -3836,11 +3759,11 @@ func (b *LocalBackend) DERPMap() *tailcfg.DERPMap {
|
||||
func (b *LocalBackend) OfferingExitNode() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if !b.prefs.Valid() {
|
||||
if !b.pm.CurrentPrefs().Valid() {
|
||||
return false
|
||||
}
|
||||
var def4, def6 bool
|
||||
ar := b.prefs.AdvertiseRoutes()
|
||||
ar := b.pm.CurrentPrefs().AdvertiseRoutes()
|
||||
for i := 0; i < ar.Len(); i++ {
|
||||
r := ar.At(i)
|
||||
if r.Bits() != 0 {
|
||||
@@ -4027,7 +3950,8 @@ func (b *LocalBackend) DoNoiseRequest(req *http.Request) (*http.Response, error)
|
||||
func (b *LocalBackend) tailscaleSSHEnabled() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.prefs.Valid() && b.prefs.RunSSH()
|
||||
p := b.pm.CurrentPrefs()
|
||||
return p.Valid() && p.RunSSH()
|
||||
}
|
||||
|
||||
func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
|
||||
@@ -4121,7 +4045,7 @@ func (b *LocalBackend) SetDevStateStore(key, value string) error {
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked()
|
||||
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -4132,3 +4056,75 @@ func (b *LocalBackend) SetDevStateStore(key, value string) error {
|
||||
func (b *LocalBackend) ShouldInterceptTCPPort(port uint16) bool {
|
||||
return b.shouldInterceptTCPPortAtomic.Load()(port)
|
||||
}
|
||||
|
||||
// SwitchProfile switches to the profile with the given id.
|
||||
// It will restart the backend on success.
|
||||
// If the profile is not known, it returns an errProfileNotFound.
|
||||
func (b *LocalBackend) SwitchProfile(profile ipn.ProfileID) error {
|
||||
if b.CurrentProfile().ID == profile {
|
||||
return nil
|
||||
}
|
||||
b.mu.Lock()
|
||||
if err := b.pm.SwitchProfile(profile); err != nil {
|
||||
b.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
return b.resetForProfileChangeLockedOnEntry()
|
||||
}
|
||||
|
||||
// resetForProfileChangeLockedOnEntry resets the backend for a profile change.
|
||||
func (b *LocalBackend) resetForProfileChangeLockedOnEntry() error {
|
||||
b.setNetMapLocked(nil) // Reset netmap.
|
||||
// Reset the NetworkMap in the engine
|
||||
b.e.SetNetworkMap(new(netmap.NetworkMap))
|
||||
b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
|
||||
return b.Start(ipn.Options{})
|
||||
}
|
||||
|
||||
// DeleteProfile deletes a profile with the given ID.
|
||||
// If the profile is not known, it is a no-op.
|
||||
func (b *LocalBackend) DeleteProfile(p ipn.ProfileID) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
needToRestart := b.pm.CurrentProfile().ID == p
|
||||
if err := b.pm.DeleteProfile(p); err != nil {
|
||||
if err == errProfileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !needToRestart {
|
||||
return nil
|
||||
}
|
||||
return b.resetForProfileChangeLockedOnEntry()
|
||||
}
|
||||
|
||||
// CurrentProfile returns the current LoginProfile.
|
||||
// The value may be zero if the profile is not persisted.
|
||||
func (b *LocalBackend) CurrentProfile() ipn.LoginProfile {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.pm.CurrentProfile()
|
||||
}
|
||||
|
||||
// NewProfile creates and switches to the new profile.
|
||||
func (b *LocalBackend) NewProfile() error {
|
||||
b.mu.Lock()
|
||||
b.pm.NewProfile()
|
||||
return b.resetForProfileChangeLockedOnEntry()
|
||||
}
|
||||
|
||||
// ListProfiles returns a list of all LoginProfiles.
|
||||
func (b *LocalBackend) ListProfiles() []ipn.LoginProfile {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.pm.Profiles()
|
||||
}
|
||||
|
||||
// CurrentUser returns the current server mode user ID. It is only non-empty on
|
||||
// Windows where we have a multi-user system.
|
||||
func (b *LocalBackend) CurrentUser() string {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.pm.CurrentUser()
|
||||
}
|
||||
|
Reference in New Issue
Block a user