Skip to content

Commit 0cfd643

Browse files
committed
ipn/ipnlocal: update profileManager to use SwitchToProfile when switching to the initial profile
This further minimizes the number of places where the profile manager updates the current profile and prefs. We also document a scenario where an implicit profile switch can occur. We should be able to address it after (partially?) inverting the dependency between LocalBackend and profileManager, so that profileManager notifies LocalBackend of profile changes instead of the other way around. Updates tailscale/corp#28014 Updates tailscale#12614 Signed-off-by: Nick Khyl <[email protected]>
1 parent f468919 commit 0cfd643

File tree

1 file changed

+40
-53
lines changed

1 file changed

+40
-53
lines changed

ipn/ipnlocal/profiles.go

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ type profileManager struct {
4040

4141
currentUserID ipn.WindowsUserID
4242
knownProfiles map[ipn.ProfileID]ipn.LoginProfileView // always non-nil
43-
currentProfile ipn.LoginProfileView // always Valid.
44-
prefs ipn.PrefsView // always Valid.
43+
currentProfile ipn.LoginProfileView // always Valid (once [newProfileManager] returns).
44+
prefs ipn.PrefsView // always Valid (once [newProfileManager] returns).
4545

4646
// extHost is the bridge between [profileManager] and the registered [ipnext.Extension]s.
4747
// It may be nil in tests. A nil pointer is a valid, no-op host.
@@ -111,14 +111,17 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) {
111111
//
112112
// It returns the current profile and whether the call resulted in a profile change,
113113
// or an error if the specified profile does not exist or its prefs could not be loaded.
114+
//
115+
// It may be called during [profileManager] initialization before [newProfileManager] returns
116+
// and must check whether pm.currentProfile is Valid before using it.
114117
func (pm *profileManager) SwitchToProfile(profile ipn.LoginProfileView) (cp ipn.LoginProfileView, changed bool, err error) {
115118
prefs := defaultPrefs
116119
switch {
117120
case !profile.Valid():
118121
// Create a new profile that is not associated with any user.
119122
profile = pm.NewProfileForUser("")
120123
case profile == pm.currentProfile,
121-
profile.ID() != "" && profile.ID() == pm.currentProfile.ID(),
124+
profile.ID() != "" && pm.currentProfile.Valid() && profile.ID() == pm.currentProfile.ID(),
122125
profile.ID() == "" && profile.Equals(pm.currentProfile) && prefs.Equals(pm.prefs):
123126
// The profile is already the current profile; no need to switch.
124127
//
@@ -176,7 +179,7 @@ func (pm *profileManager) DefaultUserProfile(uid ipn.WindowsUserID) ipn.LoginPro
176179
if err == ipn.ErrStateNotExist || len(b) == 0 {
177180
if runtime.GOOS == "windows" {
178181
pm.dlogf("DefaultUserProfile: windows: migrating from legacy preferences")
179-
profile, err := pm.migrateFromLegacyPrefs(uid, false)
182+
profile, err := pm.migrateFromLegacyPrefs(uid)
180183
if err == nil {
181184
return profile
182185
}
@@ -328,6 +331,23 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile)
328331
delete(pm.knownProfiles, p.ID())
329332
}
330333
}
334+
// TODO(nickkhyl): Revisit how we handle implicit switching to a different profile,
335+
// which occurs when prefsIn represents a node/user different from that of the
336+
// currentProfile. It happens when a login (either reauth or user-initiated login)
337+
// is completed with a different node/user identity than the one currently in use.
338+
//
339+
// Currently, we overwrite the existing profile prefs with the ones from prefsIn,
340+
// where prefsIn is the previous profile's prefs with an updated Persist, LoggedOut,
341+
// WantRunning and possibly other fields. This may not be the desired behavior.
342+
//
343+
// Additionally, LocalBackend doesn't treat it as a proper profile switch, meaning that
344+
// [LocalBackend.resetForProfileChangeLockedOnEntry] is not called and certain
345+
// node/profile-specific state may not be reset as expected.
346+
//
347+
// However, LocalBackend notifies [ipnext.Extension]s about the profile change,
348+
// so features migrated from LocalBackend to external packages should not be affected.
349+
//
350+
// See tailscale/corp#28014.
331351
pm.currentProfile = cp
332352
cp, err := pm.setProfilePrefs(nil, prefsIn, np)
333353
if err != nil {
@@ -746,28 +766,6 @@ func (pm *profileManager) NewProfileForUser(uid ipn.WindowsUserID) ipn.LoginProf
746766
return (&ipn.LoginProfile{LocalUserID: uid}).View()
747767
}
748768

749-
// newProfileWithPrefs creates a new profile with the specified prefs and assigns
750-
// the specified uid as the profile owner. If switchNow is true, it switches to the
751-
// newly created profile immediately. It returns the newly created profile on success,
752-
// or an error on failure.
753-
func (pm *profileManager) newProfileWithPrefs(uid ipn.WindowsUserID, prefs ipn.PrefsView, switchNow bool) (ipn.LoginProfileView, error) {
754-
metricNewProfile.Add(1)
755-
756-
profile, err := pm.setProfilePrefs(&ipn.LoginProfile{LocalUserID: uid}, prefs, ipn.NetworkProfile{})
757-
if err != nil {
758-
return ipn.LoginProfileView{}, err
759-
}
760-
if switchNow {
761-
pm.currentProfile = profile
762-
pm.prefs = prefs.AsStruct().View()
763-
pm.updateHealth()
764-
if err := pm.setProfileAsUserDefault(profile); err != nil {
765-
return ipn.LoginProfileView{}, err
766-
}
767-
}
768-
return profile, nil
769-
}
770-
771769
// defaultPrefs is the default prefs for a new profile. This initializes before
772770
// even this package's init() so do not rely on other parts of the system being
773771
// fully initialized here (for example, syspolicy will not be available on
@@ -857,27 +855,9 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, ht *healt
857855
health: ht,
858856
}
859857

858+
var initialProfile ipn.LoginProfileView
860859
if stateKey != "" {
861-
for _, v := range knownProfiles {
862-
if v.Key() == stateKey {
863-
pm.currentProfile = v
864-
}
865-
}
866-
if !pm.currentProfile.Valid() {
867-
if suf, ok := strings.CutPrefix(string(stateKey), "user-"); ok {
868-
pm.currentUserID = ipn.WindowsUserID(suf)
869-
}
870-
pm.SwitchToNewProfile()
871-
} else {
872-
pm.currentUserID = pm.currentProfile.LocalUserID()
873-
}
874-
prefs, err := pm.loadSavedPrefs(stateKey)
875-
if err != nil {
876-
return nil, err
877-
}
878-
if err := pm.setProfilePrefsNoPermCheck(pm.currentProfile, prefs); err != nil {
879-
return nil, err
880-
}
860+
initialProfile = pm.findProfileByKey("", stateKey)
881861
// Most platform behavior is controlled by the goos parameter, however
882862
// some behavior is implied by build tag and fails when run on Windows,
883863
// so we explicitly avoid that behavior when running on Windows.
@@ -888,25 +868,32 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, ht *healt
888868
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
889869
// No known profiles, try a migration.
890870
pm.dlogf("no known profiles; trying to migrate from legacy prefs")
891-
if _, err := pm.migrateFromLegacyPrefs(pm.currentUserID, true); err != nil {
892-
return nil, err
871+
if initialProfile, err = pm.migrateFromLegacyPrefs(pm.currentUserID); err != nil {
872+
893873
}
894-
} else {
895-
pm.SwitchToNewProfile()
896874
}
897-
875+
if !initialProfile.Valid() {
876+
var initialUserID ipn.WindowsUserID
877+
if suf, ok := strings.CutPrefix(string(stateKey), "user-"); ok {
878+
initialUserID = ipn.WindowsUserID(suf)
879+
}
880+
initialProfile = pm.NewProfileForUser(initialUserID)
881+
}
882+
if _, _, err := pm.SwitchToProfile(initialProfile); err != nil {
883+
return nil, err
884+
}
898885
return pm, nil
899886
}
900887

901-
func (pm *profileManager) migrateFromLegacyPrefs(uid ipn.WindowsUserID, switchNow bool) (ipn.LoginProfileView, error) {
888+
func (pm *profileManager) migrateFromLegacyPrefs(uid ipn.WindowsUserID) (ipn.LoginProfileView, error) {
902889
metricMigration.Add(1)
903890
sentinel, prefs, err := pm.loadLegacyPrefs(uid)
904891
if err != nil {
905892
metricMigrationError.Add(1)
906893
return ipn.LoginProfileView{}, fmt.Errorf("load legacy prefs: %w", err)
907894
}
908895
pm.dlogf("loaded legacy preferences; sentinel=%q", sentinel)
909-
profile, err := pm.newProfileWithPrefs(uid, prefs, switchNow)
896+
profile, err := pm.setProfilePrefs(&ipn.LoginProfile{LocalUserID: uid}, prefs, ipn.NetworkProfile{})
910897
if err != nil {
911898
metricMigrationError.Add(1)
912899
return ipn.LoginProfileView{}, fmt.Errorf("migrating _daemon profile: %w", err)

0 commit comments

Comments
 (0)