ipn/ipnlocal: use ipn.PrefsView

Signed-off-by: Maisem Ali <maisem@tailscale.com>
andrew/monitor-link-change
Maisem Ali 2 years ago committed by Maisem Ali
parent 20324eeebc
commit 0957bc5af2

@ -474,8 +474,8 @@ func tailscaleUp(ctx context.Context, prefs *ipn.Prefs, forceReauth bool) (authU
authURL = *url authURL = *url
cancel() cancel()
} }
if !forceReauth && n.Prefs != nil { if !forceReauth && n.Prefs.Valid() {
p1, p2 := *n.Prefs, *prefs p1, p2 := n.Prefs.AsStruct(), *prefs
p1.Persist = nil p1.Persist = nil
p2.Persist = nil p2.Persist = nil
if p1.Equals(&p2) { if p1.Equals(&p2) {

@ -67,7 +67,7 @@ type Notify struct {
LoginFinished *empty.Message // non-nil when/if the login process succeeded LoginFinished *empty.Message // non-nil when/if the login process succeeded
State *State // if non-nil, the new or current IPN state State *State // if non-nil, the new or current IPN state
Prefs *Prefs // if non-nil, the new or current preferences Prefs PrefsView // if Valid, the new or current preferences
NetMap *netmap.NetworkMap // if non-nil, the new or current netmap NetMap *netmap.NetworkMap // if non-nil, the new or current netmap
Engine *EngineStatus // if non-nil, the new or current wireguard stats Engine *EngineStatus // if non-nil, the new or current wireguard stats
BrowseToURL *string // if non-nil, UI should open a browser right now BrowseToURL *string // if non-nil, UI should open a browser right now
@ -106,7 +106,7 @@ func (n Notify) String() string {
if n.State != nil { if n.State != nil {
fmt.Fprintf(&sb, "state=%v ", *n.State) fmt.Fprintf(&sb, "state=%v ", *n.State)
} }
if n.Prefs != nil { if n.Prefs.Valid() {
fmt.Fprintf(&sb, "%v ", n.Prefs.Pretty()) fmt.Fprintf(&sb, "%v ", n.Prefs.Pretty())
} }
if n.NetMap != nil { if n.NetMap != nil {

@ -22,7 +22,7 @@ func (b *FakeBackend) Start(opts Options) error {
} }
nl := NeedsLogin nl := NeedsLogin
if b.notify != nil { if b.notify != nil {
b.notify(Notify{Prefs: opts.Prefs}) b.notify(Notify{Prefs: opts.Prefs.View()})
b.notify(Notify{State: &nl}) b.notify(Notify{State: &nl})
} }
return nil return nil
@ -83,7 +83,7 @@ func (b *FakeBackend) SetPrefs(new *Prefs) {
} }
if b.notify != nil { if b.notify != nil {
b.notify(Notify{Prefs: new.Clone()}) b.notify(Notify{Prefs: new.View()})
} }
if new.WantRunning && !b.live { if new.WantRunning && !b.live {
b.newState(Starting) b.newState(Starting)

@ -314,7 +314,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
verOS = "linux" verOS = "linux"
} }
var log tstest.MemLogger var log tstest.MemLogger
got := dnsConfigForNetmap(tt.nm, tt.prefs, log.Logf, verOS) got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS)
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
gotj, _ := json.MarshalIndent(got, "", "\t") gotj, _ := json.MarshalIndent(got, "", "\t")
wantj, _ := json.MarshalIndent(tt.want, "", "\t") wantj, _ := json.MarshalIndent(tt.want, "", "\t")

@ -147,7 +147,7 @@ type LocalBackend struct {
ccAuto *controlclient.Auto // if cc is of type *controlclient.Auto ccAuto *controlclient.Auto // if cc is of type *controlclient.Auto
stateKey ipn.StateKey // computed in part from user-provided value stateKey ipn.StateKey // computed in part from user-provided value
userID string // current controlling user ID (for Windows, primarily) userID string // current controlling user ID (for Windows, primarily)
prefs *ipn.Prefs prefs ipn.PrefsView // may not be Valid.
inServerMode bool inServerMode bool
machinePrivKey key.MachinePrivate machinePrivKey key.MachinePrivate
nlPrivKey key.NLPrivate nlPrivKey key.NLPrivate
@ -496,7 +496,7 @@ func (b *LocalBackend) Shutdown() {
func (b *LocalBackend) Prefs() *ipn.Prefs { func (b *LocalBackend) Prefs() *ipn.Prefs {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
p := b.prefs.Clone() p := b.prefs.AsStruct()
if p != nil && p.Persist != nil { if p != nil && p.Persist != nil {
p.Persist.LegacyFrontendPrivateMachineKey = key.MachinePrivate{} p.Persist.LegacyFrontendPrivateMachineKey = key.MachinePrivate{}
p.Persist.PrivateNodeKey = key.NodePrivate{} p.Persist.PrivateNodeKey = key.NodePrivate{}
@ -559,14 +559,14 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix() s.CurrentTailnet.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied s.CurrentTailnet.MagicDNSEnabled = b.netMap.DNS.Proxied
s.CurrentTailnet.Name = b.netMap.Domain s.CurrentTailnet.Name = b.netMap.Domain
if b.prefs != nil && !b.prefs.ExitNodeID.IsZero() { if b.prefs.Valid() && !b.prefs.ExitNodeID().IsZero() {
if exitPeer, ok := b.netMap.PeerWithStableID(b.prefs.ExitNodeID); ok { if exitPeer, ok := b.netMap.PeerWithStableID(b.prefs.ExitNodeID()); ok {
var online = false var online = false
if exitPeer.Online != nil { if exitPeer.Online != nil {
online = *exitPeer.Online online = *exitPeer.Online
} }
s.ExitNodeStatus = &ipnstate.ExitNodeStatus{ s.ExitNodeStatus = &ipnstate.ExitNodeStatus{
ID: b.prefs.ExitNodeID, ID: b.prefs.ExitNodeID(),
Online: online, Online: online,
TailscaleIPs: exitPeer.Addresses, TailscaleIPs: exitPeer.Addresses,
} }
@ -648,7 +648,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
LastSeen: lastSeen, LastSeen: lastSeen,
Online: p.Online != nil && *p.Online, Online: p.Online != nil && *p.Online,
ShareeNode: p.Hostinfo.ShareeNode(), ShareeNode: p.Hostinfo.ShareeNode(),
ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID, ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID(),
ExitNodeOption: exitNodeOption, ExitNodeOption: exitNodeOption,
SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(), SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(),
}) })
@ -777,7 +777,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.e.SetNetworkMap(new(netmap.NetworkMap)) b.e.SetNetworkMap(new(netmap.NetworkMap))
} }
prefs := b.prefs prefs := b.prefs.AsStruct()
stateKey := b.stateKey stateKey := b.stateKey
netMap := b.netMap netMap := b.netMap
interact := b.interact interact := b.interact
@ -792,9 +792,9 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
prefsChanged = true prefsChanged = true
} }
if st.Persist != nil { if st.Persist != nil {
if !b.prefs.Persist.Equals(st.Persist) { if !prefs.Persist.Equals(st.Persist) {
prefsChanged = true prefsChanged = true
b.prefs.Persist = st.Persist.Clone() prefs.Persist = st.Persist.Clone()
} }
} }
if st.NetMap != nil { if st.NetMap != nil {
@ -807,7 +807,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
if !envknob.TKASkipSignatureCheck() { if !envknob.TKASkipSignatureCheck() {
b.tkaFilterNetmapLocked(st.NetMap) b.tkaFilterNetmapLocked(st.NetMap)
} }
if b.findExitNodeIDLocked(st.NetMap) { if findExitNodeIDLocked(prefs, st.NetMap) {
prefsChanged = true prefsChanged = true
} }
b.setNetMapLocked(st.NetMap) b.setNetMapLocked(st.NetMap)
@ -820,18 +820,18 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// Interactive login finished successfully (URL visited). // Interactive login finished successfully (URL visited).
// After an interactive login, the user always wants // After an interactive login, the user always wants
// WantRunning. // WantRunning.
if !b.prefs.WantRunning || b.prefs.LoggedOut { if !prefs.WantRunning || prefs.LoggedOut {
prefsChanged = true prefsChanged = true
} }
b.prefs.WantRunning = true prefs.WantRunning = true
b.prefs.LoggedOut = false prefs.LoggedOut = false
} }
// Prefs will be written out; this is not safe unless locked or cloned. // Prefs will be written out; this is not safe unless locked or cloned.
if prefsChanged { if prefsChanged {
prefs = b.prefs.Clone() b.prefs = prefs.View()
} }
if st.NetMap != nil { if st.NetMap != nil {
b.updateFilterLocked(st.NetMap, prefs) b.updateFilterLocked(st.NetMap, b.prefs)
} }
b.mu.Unlock() b.mu.Unlock()
@ -842,7 +842,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.logf("Failed to save new controlclient state: %v", err) b.logf("Failed to save new controlclient state: %v", err)
} }
} }
b.send(ipn.Notify{Prefs: prefs}) b.send(ipn.Notify{Prefs: prefs.View()})
} }
if st.NetMap != nil { if st.NetMap != nil {
if netMap != nil { if netMap != nil {
@ -874,9 +874,9 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.authReconfig() b.authReconfig()
} }
// findExitNodeIDLocked updates b.prefs to reference an exit node by ID, // findExitNodeIDLocked updates prefs to reference an exit node by ID, rather
// rather than by IP. It returns whether prefs was mutated. // than by IP. It returns whether prefs was mutated.
func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged bool) { func findExitNodeIDLocked(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool) {
if nm == nil { if nm == nil {
// No netmap, can't resolve anything. // No netmap, can't resolve anything.
return false return false
@ -884,30 +884,30 @@ func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged
// If we have a desired IP on file, try to find the corresponding // If we have a desired IP on file, try to find the corresponding
// node. // node.
if !b.prefs.ExitNodeIP.IsValid() { if !prefs.ExitNodeIP.IsValid() {
return false return false
} }
// IP takes precedence over ID, so if both are set, clear ID. // IP takes precedence over ID, so if both are set, clear ID.
if b.prefs.ExitNodeID != "" { if prefs.ExitNodeID != "" {
b.prefs.ExitNodeID = "" prefs.ExitNodeID = ""
prefsChanged = true prefsChanged = true
} }
for _, peer := range nm.Peers { for _, peer := range nm.Peers {
for _, addr := range peer.Addresses { for _, addr := range peer.Addresses {
if !addr.IsSingleIP() || addr.Addr() != b.prefs.ExitNodeIP { if !addr.IsSingleIP() || addr.Addr() != prefs.ExitNodeIP {
continue continue
} }
// Found the node being referenced, upgrade prefs to // Found the node being referenced, upgrade prefs to
// reference it directly for next time. // reference it directly for next time.
b.prefs.ExitNodeID = peer.StableID prefs.ExitNodeID = peer.StableID
b.prefs.ExitNodeIP = netip.Addr{} prefs.ExitNodeIP = netip.Addr{}
return true return true
} }
} }
return false return prefsChanged
} }
// setWgengineStatus is the callback by the wireguard engine whenever it posts a new status. // setWgengineStatus is the callback by the wireguard engine whenever it posts a new status.
@ -1114,8 +1114,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
if opts.UpdatePrefs != nil { if opts.UpdatePrefs != nil {
newPrefs := opts.UpdatePrefs newPrefs := opts.UpdatePrefs
newPrefs.Persist = b.prefs.Persist newPrefs.Persist = b.prefs.Persist()
b.prefs = newPrefs b.prefs = newPrefs.View()
if opts.StateKey != "" { if opts.StateKey != "" {
if err := b.store.WriteState(opts.StateKey, b.prefs.ToBytes()); err != nil { if err := b.store.WriteState(opts.StateKey, b.prefs.ToBytes()); err != nil {
@ -1125,7 +1125,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.setAtomicValuesFromPrefs(b.prefs) b.setAtomicValuesFromPrefs(b.prefs)
} }
wantRunning := b.prefs.WantRunning wantRunning := b.prefs.WantRunning()
if wantRunning { if wantRunning {
if err := b.initMachineKeyLocked(); err != nil { if err := b.initMachineKeyLocked(); err != nil {
return fmt.Errorf("initMachineKeyLocked: %w", err) return fmt.Errorf("initMachineKeyLocked: %w", err)
@ -1135,9 +1135,9 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
return fmt.Errorf("initNLKeyLocked: %w", err) return fmt.Errorf("initNLKeyLocked: %w", err)
} }
loggedOut := b.prefs.LoggedOut loggedOut := b.prefs.LoggedOut()
b.inServerMode = b.prefs.ForceDaemon b.inServerMode = b.prefs.ForceDaemon()
b.serverURL = b.prefs.ControlURLOrDefault() b.serverURL = b.prefs.ControlURLOrDefault()
if b.inServerMode || runtime.GOOS == "windows" { if b.inServerMode || runtime.GOOS == "windows" {
b.logf("Start: serverMode=%v", b.inServerMode) b.logf("Start: serverMode=%v", b.inServerMode)
@ -1145,8 +1145,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.applyPrefsToHostinfo(hostinfo, b.prefs) b.applyPrefsToHostinfo(hostinfo, b.prefs)
b.setNetMapLocked(nil) b.setNetMapLocked(nil)
persistv := b.prefs.Persist persistv := b.prefs.Persist()
b.updateFilterLocked(nil, nil) b.updateFilterLocked(nil, ipn.PrefsView{})
b.mu.Unlock() b.mu.Unlock()
if b.portpoll != nil { if b.portpoll != nil {
@ -1230,7 +1230,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.e.SetNetInfoCallback(b.setNetInfo) b.e.SetNetInfoCallback(b.setNetInfo)
b.mu.Lock() b.mu.Lock()
prefs := b.prefs.Clone() prefs := b.prefs
b.mu.Unlock() b.mu.Unlock()
blid := b.backendLogID blid := b.backendLogID
@ -1252,7 +1252,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
// given netMap and user preferences. // given netMap and user preferences.
// //
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs *ipn.Prefs) { func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs ipn.PrefsView) {
// NOTE(danderson): keep change detection as the first thing in // NOTE(danderson): keep change detection as the first thing in
// this function. Don't try to optimize by returning early, more // this function. Don't try to optimize by returning early, more
// likely than not you'll just end up breaking the change // likely than not you'll just end up breaking the change
@ -1264,7 +1264,7 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs *ipn.
packetFilter []filter.Match packetFilter []filter.Match
localNetsB netipx.IPSetBuilder localNetsB netipx.IPSetBuilder
logNetsB netipx.IPSetBuilder logNetsB netipx.IPSetBuilder
shieldsUp = prefs == nil || prefs.ShieldsUp // Be conservative when not ready shieldsUp = !prefs.Valid() || prefs.ShieldsUp() // Be conservative when not ready
) )
// Log traffic for Tailscale IPs. // Log traffic for Tailscale IPs.
logNetsB.AddPrefix(tsaddr.CGNATRange()) logNetsB.AddPrefix(tsaddr.CGNATRange())
@ -1277,8 +1277,10 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs *ipn.
} }
packetFilter = netMap.PacketFilter packetFilter = netMap.PacketFilter
} }
if prefs != nil { if prefs.Valid() {
for _, r := range prefs.AdvertiseRoutes { ar := prefs.AdvertiseRoutes()
for i := 0; i < ar.Len(); i++ {
r := ar.At(i)
if r.Bits() == 0 { if r.Bits() == 0 {
// When offering a default route to the world, we // When offering a default route to the world, we
// filter out locally reachable LANs, so that the // filter out locally reachable LANs, so that the
@ -1687,8 +1689,8 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
} }
var legacyMachineKey key.MachinePrivate var legacyMachineKey key.MachinePrivate
if b.prefs.Persist != nil { if b.prefs.Persist() != nil {
legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey legacyMachineKey = b.prefs.Persist().LegacyFrontendPrivateMachineKey
} }
keyText, err := b.store.ReadState(ipn.MachineKeyStateKey) keyText, err := b.store.ReadState(ipn.MachineKeyStateKey)
@ -1736,6 +1738,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
// initNLKeyLocked is called to initialize b.nlPrivKey. // initNLKeyLocked is called to initialize b.nlPrivKey.
// //
// b.prefs must already be initialized. // b.prefs must already be initialized.
//
// b.stateKey should be set too, but just for nicer log messages. // b.stateKey should be set too, but just for nicer log messages.
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) initNLKeyLocked() (err error) { func (b *LocalBackend) initNLKeyLocked() (err error) {
@ -1776,12 +1779,12 @@ func (b *LocalBackend) initNLKeyLocked() (err error) {
// user and prefs. If userID is blank or prefs is blank, no work is done. // user and prefs. If userID is blank or prefs is blank, no work is done.
// //
// b.mu may either be held or not. // b.mu may either be held or not.
func (b *LocalBackend) writeServerModeStartState(userID string, prefs *ipn.Prefs) { func (b *LocalBackend) writeServerModeStartState(userID string, prefs ipn.PrefsView) {
if userID == "" || prefs == nil { if userID == "" || !prefs.Valid() {
return return
} }
if prefs.ForceDaemon { if prefs.ForceDaemon() {
stateKey := ipn.StateKey("user-" + userID) stateKey := ipn.StateKey("user-" + userID)
if err := b.store.WriteState(ipn.ServerModeStartKey, []byte(stateKey)); err != nil { if err := b.store.WriteState(ipn.ServerModeStartKey, []byte(stateKey)); err != nil {
b.logf("WriteState error: %v", err) b.logf("WriteState error: %v", err)
@ -1826,7 +1829,7 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
// optional/legacy machine key then it's used as the // optional/legacy machine key then it's used as the
// value instead of making up a new one. // value instead of making up a new one.
b.logf("using frontend prefs: %s", prefs.Pretty()) b.logf("using frontend prefs: %s", prefs.Pretty())
b.prefs = prefs.Clone() b.prefs = prefs.Clone().View()
b.writeServerModeStartState(b.userID, b.prefs) b.writeServerModeStartState(b.userID, b.prefs)
return nil return nil
} }
@ -1843,14 +1846,15 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
bs, err := b.store.ReadState(key) bs, err := b.store.ReadState(key)
switch { switch {
case errors.Is(err, ipn.ErrStateNotExist): case errors.Is(err, ipn.ErrStateNotExist):
b.prefs = ipn.NewPrefs() prefs := ipn.NewPrefs()
b.prefs.WantRunning = false prefs.WantRunning = false
b.logf("using backend prefs; created empty state for %q: %s", key, b.prefs.Pretty()) b.logf("using backend prefs; created empty state for %q: %s", key, prefs.Pretty())
b.prefs = prefs.View()
return nil return nil
case err != nil: case err != nil:
return fmt.Errorf("backend prefs: store.ReadState(%q): %v", key, err) return fmt.Errorf("backend prefs: store.ReadState(%q): %v", key, err)
} }
b.prefs, err = ipn.PrefsFromBytes(bs) prefs, err = ipn.PrefsFromBytes(bs)
if err != nil { if err != nil {
b.logf("using backend prefs for %q", key) b.logf("using backend prefs for %q", key)
return fmt.Errorf("PrefsFromBytes: %v", err) return fmt.Errorf("PrefsFromBytes: %v", err)
@ -1862,13 +1866,14 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
// This makes sure that mobile clients go through the new // This makes sure that mobile clients go through the new
// frontends where we're (2021-10-02) doing battery // frontends where we're (2021-10-02) doing battery
// optimization work ahead of turning down the old backends. // optimization work ahead of turning down the old backends.
if b.prefs != nil && b.prefs.ControlURL != "" && if prefs != nil && prefs.ControlURL != "" &&
b.prefs.ControlURL != ipn.DefaultControlURL && prefs.ControlURL != ipn.DefaultControlURL &&
ipn.IsLoginServerSynonym(b.prefs.ControlURL) { ipn.IsLoginServerSynonym(prefs.ControlURL) {
b.prefs.ControlURL = "" prefs.ControlURL = ""
} }
b.logf("using backend prefs for %q: %s", key, b.prefs.Pretty()) b.logf("using backend prefs for %q: %s", key, prefs.Pretty())
b.prefs = prefs.View()
b.setAtomicValuesFromPrefs(b.prefs) b.setAtomicValuesFromPrefs(b.prefs)
@ -1877,13 +1882,13 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic // setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
// from the prefs p, which may be nil. // from the prefs p, which may be nil.
func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) { func (b *LocalBackend) setAtomicValuesFromPrefs(p ipn.PrefsView) {
b.sshAtomicBool.Store(p != nil && p.RunSSH && envknob.CanSSHD()) b.sshAtomicBool.Store(p.Valid() && p.RunSSH() && envknob.CanSSHD())
if p == nil { if !p.Valid() {
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil)) b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil))
} else { } else {
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(tsaddr.FilterPrefixesCopy(p.AdvertiseRoutes, tsaddr.IsViaPrefix))) b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(p.AdvertiseRoutes().Filter(tsaddr.IsViaPrefix)))
} }
} }
@ -2039,10 +2044,10 @@ func (b *LocalBackend) shouldUploadServices() bool {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.prefs == nil || b.netMap == nil { if !b.prefs.Valid() || b.netMap == nil {
return false // default to safest setting return false // default to safest setting
} }
return !b.prefs.ShieldsUp && b.netMap.CollectServices return !b.prefs.ShieldsUp() && b.netMap.CollectServices
} }
func (b *LocalBackend) SetCurrentUserID(uid string) { func (b *LocalBackend) SetCurrentUserID(uid string) {
@ -2111,7 +2116,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
} }
func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage string) { func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage string) {
if b.prefs == nil || !b.prefs.RunSSH { if !b.prefs.Valid() || !b.prefs.RunSSH() {
return "" return ""
} }
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" { if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
@ -2137,35 +2142,35 @@ func (b *LocalBackend) sshOnButUnusableHealthCheckMessageLocked() (healthMessage
} }
func (b *LocalBackend) isDefaultServerLocked() bool { func (b *LocalBackend) isDefaultServerLocked() bool {
if b.prefs == nil { if !b.prefs.Valid() {
return true // assume true until set otherwise return true // assume true until set otherwise
} }
return b.prefs.ControlURLOrDefault() == ipn.DefaultControlURL return b.prefs.ControlURLOrDefault() == ipn.DefaultControlURL
} }
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
b.mu.Lock() b.mu.Lock()
if mp.EggSet { if mp.EggSet {
mp.EggSet = false mp.EggSet = false
b.egg = true b.egg = true
go b.doSetHostinfoFilterServices(b.hostinfo.Clone()) go b.doSetHostinfoFilterServices(b.hostinfo.Clone())
} }
p0 := b.prefs.Clone() p0 := b.prefs
p1 := b.prefs.Clone() p1 := b.prefs.AsStruct()
p1.ApplyEdits(mp) p1.ApplyEdits(mp)
if err := b.checkPrefsLocked(p1); err != nil { if err := b.checkPrefsLocked(p1); err != nil {
b.mu.Unlock() b.mu.Unlock()
b.logf("EditPrefs check error: %v", err) b.logf("EditPrefs check error: %v", err)
return nil, err return ipn.PrefsView{}, err
} }
if p1.RunSSH && !envknob.CanSSHD() { if p1.RunSSH && !envknob.CanSSHD() {
b.mu.Unlock() b.mu.Unlock()
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error") b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
return nil, errors.New("Tailscale SSH server administratively disabled.") return ipn.PrefsView{}, errors.New("Tailscale SSH server administratively disabled.")
} }
if p1.Equals(p0) { if p1.View().Equals(p0) {
b.mu.Unlock() b.mu.Unlock()
return p1, nil return p1.View(), nil
} }
b.logf("EditPrefs: %v", mp.Pretty()) b.logf("EditPrefs: %v", mp.Pretty())
b.setPrefsLockedOnEntry("EditPrefs", p1) // does a b.mu.Unlock b.setPrefsLockedOnEntry("EditPrefs", p1) // does a b.mu.Unlock
@ -2173,7 +2178,7 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
// Note: don't perform any actions for the new prefs here. Not // Note: don't perform any actions for the new prefs here. Not
// every prefs change goes through EditPrefs. Put your actions // every prefs change goes through EditPrefs. Put your actions
// in setPrefsLocksOnEntry instead. // in setPrefsLocksOnEntry instead.
return p1, nil return p1.View(), nil
} }
// SetPrefs saves new user preferences and propagates them throughout // SetPrefs saves new user preferences and propagates them throughout
@ -2191,23 +2196,22 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) { func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
netMap := b.netMap netMap := b.netMap
stateKey := b.stateKey stateKey := b.stateKey
b.setAtomicValuesFromPrefs(newp)
oldp := b.prefs oldp := b.prefs
newp.Persist = oldp.Persist // caller isn't allowed to override this newp.Persist = oldp.Persist() // caller isn't allowed to override this
b.prefs = newp
// findExitNodeIDLocked returns whether it updated b.prefs, but // findExitNodeIDLocked returns whether it updated b.prefs, but
// everything in this function treats b.prefs as completely new // everything in this function treats b.prefs as completely new
// anyway. No-op if no exit node resolution is needed. // anyway. No-op if no exit node resolution is needed.
b.findExitNodeIDLocked(netMap) findExitNodeIDLocked(newp, netMap)
b.inServerMode = newp.ForceDaemon b.prefs = newp.View()
b.setAtomicValuesFromPrefs(b.prefs)
b.inServerMode = b.prefs.ForceDaemon()
// We do this to avoid holding the lock while doing everything else. // We do this to avoid holding the lock while doing everything else.
newp = b.prefs.Clone()
oldHi := b.hostinfo oldHi := b.hostinfo
newHi := oldHi.Clone() newHi := oldHi.Clone()
b.applyPrefsToHostinfo(newHi, newp) b.applyPrefsToHostinfo(newHi, b.prefs)
b.hostinfo = newHi b.hostinfo = newHi
hostInfoChanged := !oldHi.Equal(newHi) hostInfoChanged := !oldHi.Equal(newHi)
userID := b.userID userID := b.userID
@ -2215,41 +2219,42 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
// [GRINDER STATS LINE] - please don't remove (used for log parsing) // [GRINDER STATS LINE] - please don't remove (used for log parsing)
if caller == "SetPrefs" { if caller == "SetPrefs" {
b.logf("SetPrefs: %v", newp.Pretty()) b.logf("SetPrefs: %v", b.prefs.Pretty())
} }
b.updateFilterLocked(netMap, newp) b.updateFilterLocked(netMap, b.prefs)
if oldp.ShouldSSHBeRunning() && !newp.ShouldSSHBeRunning() { if oldp.ShouldSSHBeRunning() && !b.prefs.ShouldSSHBeRunning() {
if b.sshServer != nil { if b.sshServer != nil {
go b.sshServer.Shutdown() go b.sshServer.Shutdown()
b.sshServer = nil b.sshServer = nil
} }
} }
prefs := b.prefs // We can grab the view before unlocking. It can't be mutated.
b.mu.Unlock() b.mu.Unlock()
if stateKey != "" { if stateKey != "" {
if err := b.store.WriteState(stateKey, newp.ToBytes()); err != nil { if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
b.logf("failed to save new controlclient state: %v", err) b.logf("failed to save new controlclient state: %v", err)
} }
} }
b.writeServerModeStartState(userID, newp) b.writeServerModeStartState(userID, prefs)
if netMap != nil { if netMap != nil {
if login := netMap.UserProfiles[netMap.User].LoginName; login != "" { if login := netMap.UserProfiles[netMap.User].LoginName; login != "" {
if newp.Persist == nil { if prefs.Persist() == nil {
b.logf("active login: %s", login) b.logf("active login: %s", login)
} else if newp.Persist.LoginName != login { } else if prefs.Persist().LoginName != login {
// Corp issue 461: sometimes the wrong prefs are // Corp issue 461: sometimes the wrong prefs are
// logged; the frontend isn't always getting // logged; the frontend isn't always getting
// notified (to update its prefs/persist) on // notified (to update its prefs/persist) on
// account switch. Log this while we figure it // account switch. Log this while we figure it
// out. // out.
b.logf("active login: %q ([unexpected] corp#461, not %q)", newp.Persist.LoginName, login) b.logf("active login: %q ([unexpected] corp#461, not %q)", prefs.Persist().LoginName, login)
} }
} }
} }
if oldp.ShieldsUp != newp.ShieldsUp || hostInfoChanged { if oldp.ShieldsUp() != prefs.ShieldsUp() || hostInfoChanged {
b.doSetHostinfoFilterServices(newHi) b.doSetHostinfoFilterServices(newHi)
} }
@ -2257,18 +2262,18 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
b.e.SetDERPMap(netMap.DERPMap) b.e.SetDERPMap(netMap.DERPMap)
} }
if !oldp.WantRunning && newp.WantRunning { if !oldp.WantRunning() && prefs.WantRunning() {
b.logf("transitioning to running; doing Login...") b.logf("transitioning to running; doing Login...")
cc.Login(nil, controlclient.LoginDefault) cc.Login(nil, controlclient.LoginDefault)
} }
if oldp.WantRunning != newp.WantRunning { if oldp.WantRunning() != prefs.WantRunning() {
b.stateMachine() b.stateMachine()
} else { } else {
b.authReconfig() b.authReconfig()
} }
b.send(ipn.Notify{Prefs: newp}) b.send(ipn.Notify{Prefs: prefs})
} }
// GetPeerAPIPort returns the port number for the peerapi server // GetPeerAPIPort returns the port number for the peerapi server
@ -2409,16 +2414,16 @@ func (b *LocalBackend) authReconfig() {
b.logf("[v1] authReconfig: netmap not yet valid. Skipping.") b.logf("[v1] authReconfig: netmap not yet valid. Skipping.")
return return
} }
if !prefs.WantRunning { if !prefs.WantRunning() {
b.logf("[v1] authReconfig: skipping because !WantRunning.") b.logf("[v1] authReconfig: skipping because !WantRunning.")
return return
} }
var flags netmap.WGConfigFlags var flags netmap.WGConfigFlags
if prefs.RouteAll { if prefs.RouteAll() {
flags |= netmap.AllowSubnetRoutes flags |= netmap.AllowSubnetRoutes
} }
if prefs.AllowSingleHosts { if prefs.AllowSingleHosts() {
flags |= netmap.AllowSingleHosts flags |= netmap.AllowSingleHosts
} }
if hasPAC && disableSubnetsIfPAC { if hasPAC && disableSubnetsIfPAC {
@ -2431,13 +2436,13 @@ func (b *LocalBackend) authReconfig() {
// Keep the dialer updated about whether we're supposed to use // Keep the dialer updated about whether we're supposed to use
// an exit node's DNS server (so SOCKS5/HTTP outgoing dials // an exit node's DNS server (so SOCKS5/HTTP outgoing dials
// can use it for name resolution) // can use it for name resolution)
if dohURL, ok := exitNodeCanProxyDNS(nm, prefs.ExitNodeID); ok { if dohURL, ok := exitNodeCanProxyDNS(nm, prefs.ExitNodeID()); ok {
b.dialer.SetExitDNSDoH(dohURL) b.dialer.SetExitDNSDoH(dohURL)
} else { } else {
b.dialer.SetExitDNSDoH("") b.dialer.SetExitDNSDoH("")
} }
cfg, err := nmcfg.WGCfg(nm, b.logf, flags, prefs.ExitNodeID) cfg, err := nmcfg.WGCfg(nm, b.logf, flags, prefs.ExitNodeID())
if err != nil { if err != nil {
b.logf("wgcfg: %v", err) b.logf("wgcfg: %v", err)
return return
@ -2493,7 +2498,7 @@ func shouldUseOneCGNATRoute(nm *netmap.NetworkMap, logf logger.Logf, versionOS s
// //
// The versionOS is a Tailscale-style version ("iOS", "macOS") and not // The versionOS is a Tailscale-style version ("iOS", "macOS") and not
// a runtime.GOOS. // a runtime.GOOS.
func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Logf, versionOS string) *dns.Config { func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs ipn.PrefsView, logf logger.Logf, versionOS string) *dns.Config {
dcfg := &dns.Config{ dcfg := &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netip.Addr{}, Hosts: map[dnsname.FQDN][]netip.Addr{},
@ -2565,7 +2570,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
dcfg.Hosts[fqdn] = append(dcfg.Hosts[fqdn], ip) dcfg.Hosts[fqdn] = append(dcfg.Hosts[fqdn], ip)
} }
if !prefs.CorpDNS { if !prefs.CorpDNS() {
return dcfg return dcfg
} }
@ -2590,7 +2595,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
// If we're using an exit node and that exit node is new enough (1.19.x+) // If we're using an exit node and that exit node is new enough (1.19.x+)
// to run a DoH DNS proxy, then send all our DNS traffic through it. // to run a DoH DNS proxy, then send all our DNS traffic through it.
if dohURL, ok := exitNodeCanProxyDNS(nm, prefs.ExitNodeID); ok { if dohURL, ok := exitNodeCanProxyDNS(nm, prefs.ExitNodeID()); ok {
addDefault([]*dnstype.Resolver{{Addr: dohURL}}) addDefault([]*dnstype.Resolver{{Addr: dohURL}})
return dcfg return dcfg
} }
@ -2624,7 +2629,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
switch { switch {
case len(dcfg.DefaultResolvers) != 0: case len(dcfg.DefaultResolvers) != 0:
// Default resolvers already set. // Default resolvers already set.
case !prefs.ExitNodeID.IsZero(): case !prefs.ExitNodeID().IsZero():
// When using exit nodes, it's very likely the LAN // When using exit nodes, it's very likely the LAN
// resolvers will become unreachable. So, force use of the // resolvers will become unreachable. So, force use of the
// fallback resolvers until we implement DNS forwarding to // fallback resolvers until we implement DNS forwarding to
@ -2890,16 +2895,16 @@ func ipPrefixLess(ri, rj netip.Prefix) bool {
} }
// routerConfig produces a router.Config from a wireguard config and IPN prefs. // routerConfig produces a router.Config from a wireguard config and IPN prefs.
func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNATRoute bool) *router.Config { func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs ipn.PrefsView, oneCGNATRoute bool) *router.Config {
singleRouteThreshold := 10_000 singleRouteThreshold := 10_000
if oneCGNATRoute { if oneCGNATRoute {
singleRouteThreshold = 1 singleRouteThreshold = 1
} }
rs := &router.Config{ rs := &router.Config{
LocalAddrs: unmapIPPrefixes(cfg.Addresses), LocalAddrs: unmapIPPrefixes(cfg.Addresses),
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes), SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes().AsSlice()),
SNATSubnetRoutes: !prefs.NoSNAT, SNATSubnetRoutes: !prefs.NoSNAT(),
NetfilterMode: prefs.NetfilterMode, NetfilterMode: prefs.NetfilterMode(),
Routes: peerRoutes(cfg.Peers, singleRouteThreshold), Routes: peerRoutes(cfg.Peers, singleRouteThreshold),
} }
@ -2914,7 +2919,7 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNA
// likely to break some functionality, but if the user expressed a // likely to break some functionality, but if the user expressed a
// preference for routing remotely, we want to avoid leaking // preference for routing remotely, we want to avoid leaking
// traffic at the expense of functionality. // traffic at the expense of functionality.
if prefs.ExitNodeID != "" || prefs.ExitNodeIP.IsValid() { if prefs.ExitNodeID() != "" || prefs.ExitNodeIP().IsValid() {
var default4, default6 bool var default4, default6 bool
for _, route := range rs.Routes { for _, route := range rs.Routes {
switch route { switch route {
@ -2939,7 +2944,7 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNA
} }
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" { if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks
if prefs.ExitNodeAllowLANAccess { if prefs.ExitNodeAllowLANAccess() {
rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...) rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...)
} else { } else {
// Explicitly add routes to the local network so that we do not // Explicitly add routes to the local network so that we do not
@ -2971,16 +2976,16 @@ func unmapIPPrefixes(ippsList ...[]netip.Prefix) (ret []netip.Prefix) {
} }
// Warning: b.mu might be held. Currently (2022-02-17) both callers hold it. // Warning: b.mu might be held. Currently (2022-02-17) both callers hold it.
func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Prefs) { func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs ipn.PrefsView) {
if h := prefs.Hostname; h != "" { if h := prefs.Hostname(); h != "" {
hi.Hostname = h hi.Hostname = h
} }
hi.RoutableIPs = append(prefs.AdvertiseRoutes[:0:0], prefs.AdvertiseRoutes...) hi.RoutableIPs = prefs.AdvertiseRoutes().AsSlice()
hi.RequestTags = append(prefs.AdvertiseTags[:0:0], prefs.AdvertiseTags...) hi.RequestTags = prefs.AdvertiseTags().AsSlice()
hi.ShieldsUp = prefs.ShieldsUp hi.ShieldsUp = prefs.ShieldsUp()
var sshHostKeys []string var sshHostKeys []string
if prefs.RunSSH && envknob.CanSSHD() { if prefs.RunSSH() && envknob.CanSSHD() {
// TODO(bradfitz): this is called with b.mu held. Not ideal. // TODO(bradfitz): this is called with b.mu held. Not ideal.
// If the filesystem gets wedged or something we could block for // If the filesystem gets wedged or something we could block for
// a long time. But probably fine. // a long time. But probably fine.
@ -3016,7 +3021,7 @@ func (b *LocalBackend) enterState(newState ipn.State) {
// prefs may change irrespective of state; WantRunning should be explicitly // prefs may change irrespective of state; WantRunning should be explicitly
// set before potential early return even if the state is unchanged. // set before potential early return even if the state is unchanged.
health.SetIPNState(newState.String(), prefs.WantRunning) health.SetIPNState(newState.String(), prefs.WantRunning())
if oldState == newState { if oldState == newState {
return return
} }
@ -3057,10 +3062,9 @@ func (b *LocalBackend) enterState(newState ipn.State) {
func (b *LocalBackend) hasNodeKey() bool { func (b *LocalBackend) hasNodeKey() bool {
// we can't use b.Prefs(), because it strips the keys, oops! // we can't use b.Prefs(), because it strips the keys, oops!
b.mu.Lock() b.mu.Lock()
p := b.prefs defer b.mu.Unlock()
b.mu.Unlock()
return p.Persist != nil && !p.Persist.PrivateNodeKey.IsZero() return b.prefs.Valid() && b.prefs.Persist() != nil && !b.prefs.Persist().PrivateNodeKey.IsZero()
} }
// nextState returns the state the backend seems to be in, based on // nextState returns the state the backend seems to be in, based on
@ -3073,8 +3077,8 @@ func (b *LocalBackend) nextState() ipn.State {
netMap = b.netMap netMap = b.netMap
state = b.state state = b.state
blocked = b.blocked blocked = b.blocked
wantRunning = b.prefs.WantRunning wantRunning = b.prefs.WantRunning()
loggedOut = b.prefs.LoggedOut loggedOut = b.prefs.LoggedOut()
st = b.engineStatus st = b.engineStatus
keyExpired = b.keyExpired keyExpired = b.keyExpired
) )
@ -3191,12 +3195,12 @@ func (b *LocalBackend) ResetForClientDisconnect() {
b.stateKey = "" b.stateKey = ""
b.userID = "" b.userID = ""
b.setNetMapLocked(nil) b.setNetMapLocked(nil)
b.prefs = new(ipn.Prefs) b.prefs = new(ipn.Prefs).View()
b.keyExpired = false b.keyExpired = false
b.authURL = "" b.authURL = ""
b.authURLSticky = "" b.authURLSticky = ""
b.activeLogin = "" b.activeLogin = ""
b.setAtomicValuesFromPrefs(nil) b.setAtomicValuesFromPrefs(b.prefs)
} }
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() } func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() }
@ -3357,22 +3361,16 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
func (b *LocalBackend) operatorUserName() string { func (b *LocalBackend) operatorUserName() string {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.prefs == nil { if !b.prefs.Valid() {
return "" return ""
} }
return b.prefs.OperatorUser return b.prefs.OperatorUser()
} }
// OperatorUserID returns the current pref's OperatorUser's ID (in // OperatorUserID returns the current pref's OperatorUser's ID (in
// os/user.User.Uid string form), or the empty string if none. // os/user.User.Uid string form), or the empty string if none.
func (b *LocalBackend) OperatorUserID() string { func (b *LocalBackend) OperatorUserID() string {
b.mu.Lock() opUserName := b.operatorUserName()
if b.prefs == nil {
b.mu.Unlock()
return ""
}
opUserName := b.prefs.OperatorUser
b.mu.Unlock()
if opUserName == "" { if opUserName == "" {
return "" return ""
} }
@ -3393,12 +3391,12 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeK
machinePrivKey := b.machinePrivKey machinePrivKey := b.machinePrivKey
b.mu.Unlock() b.mu.Unlock()
if prefs == nil || machinePrivKey.IsZero() { if !prefs.Valid() || machinePrivKey.IsZero() {
return return
} }
mk := machinePrivKey.Public() mk := machinePrivKey.Public()
nk := prefs.Persist.PrivateNodeKey.Public() nk := prefs.Persist().PrivateNodeKey.Public()
return mk, nk return mk, nk
} }
@ -3507,8 +3505,8 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
b.mu.Lock() b.mu.Lock()
cc := b.ccAuto cc := b.ccAuto
if prefs := b.prefs; prefs != nil { if b.prefs.Valid() {
req.NodeKey = prefs.Persist.PrivateNodeKey.Public() req.NodeKey = b.prefs.Persist().PrivateNodeKey.Public()
} }
b.mu.Unlock() b.mu.Unlock()
if cc == nil { if cc == nil {
@ -3620,11 +3618,13 @@ func (b *LocalBackend) DERPMap() *tailcfg.DERPMap {
func (b *LocalBackend) OfferingExitNode() bool { func (b *LocalBackend) OfferingExitNode() bool {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.prefs == nil { if !b.prefs.Valid() {
return false return false
} }
var def4, def6 bool var def4, def6 bool
for _, r := range b.prefs.AdvertiseRoutes { ar := b.prefs.AdvertiseRoutes()
for i := 0; i < ar.Len(); i++ {
r := ar.At(i)
if r.Bits() != 0 { if r.Bits() != 0 {
continue continue
} }
@ -3767,10 +3767,7 @@ func (b *LocalBackend) DoNoiseRequest(req *http.Request) (*http.Response, error)
func (b *LocalBackend) tailscaleSSHEnabled() bool { func (b *LocalBackend) tailscaleSSHEnabled() bool {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if b.prefs == nil { return b.prefs.Valid() && b.prefs.RunSSH()
return false
}
return b.prefs.RunSSH
} }
func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) { func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {

@ -62,7 +62,7 @@ func TestLocalLogLines(t *testing.T) {
defer lb.Shutdown() defer lb.Shutdown()
// custom adjustments for required non-nil fields // custom adjustments for required non-nil fields
lb.prefs = ipn.NewPrefs() lb.prefs = ipn.NewPrefs().View()
lb.hostinfo = &tailcfg.Hostinfo{} lb.hostinfo = &tailcfg.Hostinfo{}
// hacky manual override of the usual log-on-change behaviour of keylogf // hacky manual override of the usual log-on-change behaviour of keylogf
lb.keyLogf = logListen.Logf lb.keyLogf = logListen.Logf

@ -100,7 +100,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap) error {
b.mu.Lock() // take mu to protect access to synchronized fields. b.mu.Lock() // take mu to protect access to synchronized fields.
defer b.mu.Unlock() defer b.mu.Unlock()
ourNodeKey := b.prefs.Persist.PrivateNodeKey.Public() ourNodeKey := b.prefs.Persist().PrivateNodeKey.Public()
isEnabled := b.tka != nil isEnabled := b.tka != nil
wantEnabled := nm.TKAEnabled wantEnabled := nm.TKAEnabled
@ -341,8 +341,8 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
var ourNodeKey key.NodePublic var ourNodeKey key.NodePublic
b.mu.Lock() b.mu.Lock()
if b.prefs != nil { if b.prefs.Valid() {
ourNodeKey = b.prefs.Persist.PrivateNodeKey.Public() ourNodeKey = b.prefs.Persist().PrivateNodeKey.Public()
} }
b.mu.Unlock() b.mu.Unlock()
if ourNodeKey.IsZero() { if ourNodeKey.IsZero() {
@ -453,7 +453,7 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
return nil return nil
} }
ourNodeKey := b.prefs.Persist.PrivateNodeKey.Public() ourNodeKey := b.prefs.Persist().PrivateNodeKey.Public()
b.mu.Unlock() b.mu.Unlock()
resp, err := b.tkaDoSyncSend(ourNodeKey, aums, true) resp, err := b.tkaDoSyncSend(ourNodeKey, aums, true)
b.mu.Lock() b.mu.Lock()

@ -122,9 +122,9 @@ func TestTKAEnablementFlow(t *testing.T) {
cc: cc, cc: cc,
ccAuto: cc, ccAuto: cc,
logf: t.Logf, logf: t.Logf,
prefs: &ipn.Prefs{ prefs: (&ipn.Prefs{
Persist: &persist.Persist{PrivateNodeKey: nodePriv}, Persist: &persist.Persist{PrivateNodeKey: nodePriv},
}, }).View(),
} }
err = b.tkaSyncIfNeeded(&netmap.NetworkMap{ err = b.tkaSyncIfNeeded(&netmap.NetworkMap{
@ -219,9 +219,9 @@ func TestTKADisablementFlow(t *testing.T) {
authority: authority, authority: authority,
storage: chonk, storage: chonk,
}, },
prefs: &ipn.Prefs{ prefs: (&ipn.Prefs{
Persist: &persist.Persist{PrivateNodeKey: nodePriv}, Persist: &persist.Persist{PrivateNodeKey: nodePriv},
}, }).View(),
} }
// Test that the wrong disablement secret does not shut down the authority. // Test that the wrong disablement secret does not shut down the authority.
@ -456,9 +456,9 @@ func TestTKASync(t *testing.T) {
authority: nodeAuthority, authority: nodeAuthority,
storage: nodeStorage, storage: nodeStorage,
}, },
prefs: &ipn.Prefs{ prefs: (&ipn.Prefs{
Persist: &persist.Persist{PrivateNodeKey: nodePriv}, Persist: &persist.Persist{PrivateNodeKey: nodePriv},
}, }).View(),
} }
// Finally, lets trigger a sync. // Finally, lets trigger a sync.

@ -593,12 +593,12 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
if h.ps.b.OfferingExitNode() { if h.ps.b.OfferingExitNode() {
t.Fatal("unexpectedly offering exit node") t.Fatal("unexpectedly offering exit node")
} }
h.ps.b.prefs = &ipn.Prefs{ h.ps.b.prefs = (&ipn.Prefs{
AdvertiseRoutes: []netip.Prefix{ AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
} }).View()
if !h.ps.b.OfferingExitNode() { if !h.ps.b.OfferingExitNode() {
t.Fatal("unexpectedly not offering exit node") t.Fatal("unexpectedly not offering exit node")
} }

@ -315,7 +315,7 @@ func TestStateMachine(t *testing.T) {
return return
} }
if n.State != nil || if n.State != nil ||
n.Prefs != nil || n.Prefs.Valid() ||
n.BrowseToURL != nil || n.BrowseToURL != nil ||
n.LoginFinished != nil { n.LoginFinished != nil {
logf("\n%v\n\n", n) logf("\n%v\n\n", n)
@ -344,12 +344,12 @@ func TestStateMachine(t *testing.T) {
cc.assertCalls() cc.assertCalls()
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[1].State, qt.IsNotNil) c.Assert(nn[1].State, qt.IsNotNil)
prefs := *nn[0].Prefs prefs := nn[0].Prefs
// Note: a totally fresh system has Prefs.LoggedOut=false by // Note: a totally fresh system has Prefs.LoggedOut=false by
// default. We are logged out, but not because the user asked // default. We are logged out, but not because the user asked
// for it, so it doesn't count as Prefs.LoggedOut==true. // for it, so it doesn't count as Prefs.LoggedOut==true.
c.Assert(nn[0].Prefs.LoggedOut, qt.IsFalse) c.Assert(prefs.LoggedOut(), qt.IsFalse)
c.Assert(prefs.WantRunning, qt.IsFalse) c.Assert(prefs.WantRunning(), qt.IsFalse)
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State) c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State)
c.Assert(ipn.NeedsLogin, qt.Equals, b.State()) c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
} }
@ -369,8 +369,8 @@ func TestStateMachine(t *testing.T) {
cc.assertCalls() cc.assertCalls()
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[1].State, qt.IsNotNil) c.Assert(nn[1].State, qt.IsNotNil)
c.Assert(nn[0].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(nn[0].Prefs.WantRunning, qt.IsFalse) c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State) c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State)
c.Assert(ipn.NeedsLogin, qt.Equals, b.State()) c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
} }
@ -406,8 +406,8 @@ func TestStateMachine(t *testing.T) {
nn := notifies.drain(1) nn := notifies.drain(1)
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[0].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(nn[0].Prefs.WantRunning, qt.IsFalse) c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
} }
// Now we'll try an interactive login. // Now we'll try an interactive login.
@ -476,7 +476,7 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[0].LoginFinished, qt.IsNotNil) c.Assert(nn[0].LoginFinished, qt.IsNotNil)
c.Assert(nn[1].Prefs, qt.IsNotNil) c.Assert(nn[1].Prefs, qt.IsNotNil)
c.Assert(nn[2].State, qt.IsNotNil) c.Assert(nn[2].State, qt.IsNotNil)
c.Assert(nn[1].Prefs.Persist.LoginName, qt.Equals, "user1") c.Assert(nn[1].Prefs.Persist().LoginName, qt.Equals, "user1")
c.Assert(ipn.NeedsMachineAuth, qt.Equals, *nn[2].State) c.Assert(ipn.NeedsMachineAuth, qt.Equals, *nn[2].State)
} }
@ -576,8 +576,8 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[0].State, qt.IsNotNil) c.Assert(nn[0].State, qt.IsNotNil)
c.Assert(nn[1].Prefs, qt.IsNotNil) c.Assert(nn[1].Prefs, qt.IsNotNil)
c.Assert(ipn.Stopped, qt.Equals, *nn[0].State) c.Assert(ipn.Stopped, qt.Equals, *nn[0].State)
c.Assert(nn[1].Prefs.LoggedOut, qt.IsTrue) c.Assert(nn[1].Prefs.LoggedOut(), qt.IsTrue)
c.Assert(nn[1].Prefs.WantRunning, qt.IsFalse) c.Assert(nn[1].Prefs.WantRunning(), qt.IsFalse)
c.Assert(ipn.Stopped, qt.Equals, b.State()) c.Assert(ipn.Stopped, qt.Equals, b.State())
c.Assert(store.sawWrite(), qt.IsTrue) c.Assert(store.sawWrite(), qt.IsTrue)
} }
@ -672,8 +672,8 @@ func TestStateMachine(t *testing.T) {
cc.assertCalls() cc.assertCalls()
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[1].State, qt.IsNotNil) c.Assert(nn[1].State, qt.IsNotNil)
c.Assert(nn[0].Prefs.LoggedOut, qt.IsTrue) c.Assert(nn[0].Prefs.LoggedOut(), qt.IsTrue)
c.Assert(nn[0].Prefs.WantRunning, qt.IsFalse) c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State) c.Assert(ipn.NeedsLogin, qt.Equals, *nn[1].State)
c.Assert(ipn.NeedsLogin, qt.Equals, b.State()) c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
} }
@ -696,9 +696,9 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[1].Prefs, qt.IsNotNil) c.Assert(nn[1].Prefs, qt.IsNotNil)
c.Assert(nn[2].State, qt.IsNotNil) c.Assert(nn[2].State, qt.IsNotNil)
// Prefs after finishing the login, so LoginName updated. // Prefs after finishing the login, so LoginName updated.
c.Assert(nn[1].Prefs.Persist.LoginName, qt.Equals, "user2") c.Assert(nn[1].Prefs.Persist().LoginName, qt.Equals, "user2")
c.Assert(nn[1].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[1].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(nn[1].Prefs.WantRunning, qt.IsTrue) c.Assert(nn[1].Prefs.WantRunning(), qt.IsTrue)
c.Assert(ipn.Starting, qt.Equals, *nn[2].State) c.Assert(ipn.Starting, qt.Equals, *nn[2].State)
} }
@ -716,7 +716,7 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[0].State, qt.IsNotNil) c.Assert(nn[0].State, qt.IsNotNil)
c.Assert(nn[1].Prefs, qt.IsNotNil) c.Assert(nn[1].Prefs, qt.IsNotNil)
c.Assert(ipn.Stopped, qt.Equals, *nn[0].State) c.Assert(ipn.Stopped, qt.Equals, *nn[0].State)
c.Assert(nn[1].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[1].Prefs.LoggedOut(), qt.IsFalse)
} }
// One more restart, this time with a valid key, but WantRunning=false. // One more restart, this time with a valid key, but WantRunning=false.
@ -734,8 +734,8 @@ func TestStateMachine(t *testing.T) {
cc.assertCalls("Shutdown", "unpause", "New", "Login", "unpause") cc.assertCalls("Shutdown", "unpause", "New", "Login", "unpause")
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[1].State, qt.IsNotNil) c.Assert(nn[1].State, qt.IsNotNil)
c.Assert(nn[0].Prefs.WantRunning, qt.IsFalse) c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
c.Assert(nn[0].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(ipn.Stopped, qt.Equals, *nn[1].State) c.Assert(ipn.Stopped, qt.Equals, *nn[1].State)
} }
@ -834,9 +834,9 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[1].Prefs, qt.IsNotNil) c.Assert(nn[1].Prefs, qt.IsNotNil)
c.Assert(nn[2].State, qt.IsNotNil) c.Assert(nn[2].State, qt.IsNotNil)
// Prefs after finishing the login, so LoginName updated. // Prefs after finishing the login, so LoginName updated.
c.Assert(nn[1].Prefs.Persist.LoginName, qt.Equals, "user3") c.Assert(nn[1].Prefs.Persist().LoginName, qt.Equals, "user3")
c.Assert(nn[1].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[1].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(nn[1].Prefs.WantRunning, qt.IsTrue) c.Assert(nn[1].Prefs.WantRunning(), qt.IsTrue)
c.Assert(ipn.Starting, qt.Equals, *nn[2].State) c.Assert(ipn.Starting, qt.Equals, *nn[2].State)
} }
@ -853,8 +853,8 @@ func TestStateMachine(t *testing.T) {
nn := notifies.drain(1) nn := notifies.drain(1)
cc.assertCalls() cc.assertCalls()
c.Assert(nn[0].Prefs, qt.IsNotNil) c.Assert(nn[0].Prefs, qt.IsNotNil)
c.Assert(nn[0].Prefs.LoggedOut, qt.IsFalse) c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
c.Assert(nn[0].Prefs.WantRunning, qt.IsTrue) c.Assert(nn[0].Prefs.WantRunning(), qt.IsTrue)
c.Assert(ipn.NoState, qt.Equals, b.State()) c.Assert(ipn.NoState, qt.Equals, b.State())
} }

@ -505,7 +505,7 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
http.Error(w, "prefs access denied", http.StatusForbidden) http.Error(w, "prefs access denied", http.StatusForbidden)
return return
} }
var prefs *ipn.Prefs var prefs ipn.PrefsView
switch r.Method { switch r.Method {
case "PATCH": case "PATCH":
if !h.PermitWrite { if !h.PermitWrite {
@ -526,7 +526,7 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
return return
} }
case "GET", "HEAD": case "GET", "HEAD":
prefs = h.b.Prefs() prefs = h.b.Prefs().View()
default: default:
http.Error(w, "unsupported method", http.StatusMethodNotAllowed) http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
return return

Loading…
Cancel
Save