diff --git a/android_legacy/libs/ipn.aar b/android_legacy/libs/ipn.aar index f5bb72d..4630465 100644 Binary files a/android_legacy/libs/ipn.aar and b/android_legacy/libs/ipn.aar differ diff --git a/cmd/tailscale/backend.go b/cmd/tailscale/backend.go index 9de17c3..90b8abd 100644 --- a/cmd/tailscale/backend.go +++ b/cmd/tailscale/backend.go @@ -68,6 +68,8 @@ const ( logPrefKey = "privatelogid" loginMethodPrefKey = "loginmethod" customLoginServerPrefKey = "customloginserver" + exitNodePrefKey = "exitnode" + exitAllowLANPrefKey = "exitallowlan" ) const ( diff --git a/cmd/tailscale/main.go b/cmd/tailscale/main.go index 522bda9..5360574 100644 --- a/cmd/tailscale/main.go +++ b/cmd/tailscale/main.go @@ -175,6 +175,7 @@ type RouteAllEvent struct { type ConnectEvent struct { Enable bool + SettingsRestoreEvent } type CopyEvent struct { @@ -199,11 +200,14 @@ type SetLoginServerEvent struct { URL string } +type WebAuthEvent struct { + SettingsRestoreEvent +} + // UIEvent types. type ( ReauthEvent struct{} BugEvent struct{} - WebAuthEvent struct{} GoogleAuthEvent struct{} LogoutEvent struct{} OSSLicensesEvent struct{} @@ -211,6 +215,42 @@ type ( ExitAllowLANEvent bool ) +// RestoreEvent represents an event that might restore user settings persisted across sessions. +type RestoreEvent interface { + SetURL(url string) + GetURL() string + SetExitNodeID(exitNodeID tailcfg.StableNodeID) + GetExitNodeID() tailcfg.StableNodeID + SetExitAllowLAN(allowLAN bool) + GetExitAllowLAN() bool +} + +type SettingsRestoreEvent struct { + // Custom server login URL + URL string + ExitNodeID tailcfg.StableNodeID + ExitAllowLAN bool +} + +func (e *SettingsRestoreEvent) SetURL(url string) { + e.URL = url +} +func (e *SettingsRestoreEvent) GetURL() string { + return e.URL +} +func (e *SettingsRestoreEvent) SetExitNodeID(exitNodeID tailcfg.StableNodeID) { + e.ExitNodeID = exitNodeID +} +func (e *SettingsRestoreEvent) GetExitNodeID() tailcfg.StableNodeID { + return e.ExitNodeID +} +func (e *SettingsRestoreEvent) SetExitAllowLAN(allowLAN bool) { + e.ExitAllowLAN = allowLAN +} +func (e *SettingsRestoreEvent) GetExitAllowLAN() bool { + return e.ExitAllowLAN +} + // serverOAuthID is the OAuth ID of the tailscale-android server, used // by GoogleSignInOptions.Builder.requestIdToken. const serverOAuthID = "744055068597-hv4opg0h7vskq1hv37nq3u26t8c15qk0.apps.googleusercontent.com" @@ -472,14 +512,31 @@ func (a *App) runBackend(ctx context.Context) error { go b.backend.SetPrefs(state.Prefs) case ExitAllowLANEvent: state.Prefs.ExitNodeAllowLANAccess = bool(e) + a.store.WriteBool(exitAllowLANPrefKey, true) go b.backend.SetPrefs(state.Prefs) case WebAuthEvent: if !signingIn { - go a.localAPI.Login(ctx, a.backend) + setCustomServer, setExitNode, setAllowLANAccess := a.restoreSettings(&e, state, service) signingIn = true + go func() { + if setCustomServer || setExitNode || setAllowLANAccess { + b.backend.SetPrefs(state.Prefs) + } + if setCustomServer { + // Need to restart to force the login URL to be regenerated + // with the new control URL. Start from a goroutine to avoid + // deadlock. + err := b.backend.Start(ipn.Options{}) + if err != nil { + fatalErr(err) + } + } + b.backend.StartLoginInteractive() + }() } case SetLoginServerEvent: state.Prefs.ControlURL = e.URL + a.store.WriteString(customLoginServerPrefKey, e.URL) b.backend.SetPrefs(state.Prefs) // Need to restart to force the login URL to be regenerated // with the new control URL. Start from a goroutine to avoid @@ -500,10 +557,23 @@ func (a *App) runBackend(ctx context.Context) error { state.Prefs = ipn.NewPrefs() } state.Prefs.WantRunning = e.Enable - go b.backend.SetPrefs(state.Prefs) + setCustomServer, _, _ := a.restoreSettings(&e, state, service) + go func() { + b.backend.SetPrefs(state.Prefs) + if setCustomServer { + // Need to restart to force the login URL to be regenerated + // with the new control URL. Start from a goroutine to avoid + // deadlock. + err := b.backend.Start(ipn.Options{}) + if err != nil { + fatalErr(err) + } + } + }() case RouteAllEvent: state.Prefs.ExitNodeID = e.ID go b.backend.SetPrefs(state.Prefs) + a.store.WriteString(exitNodePrefKey, string(e.ID)) state.updateExitNodes() a.notify(state) if service != 0 { @@ -588,6 +658,29 @@ func (a *App) runBackend(ctx context.Context) error { } } +func (a *App) restoreSettings(e RestoreEvent, state BackendState, service jni.Object) (bool, bool, bool) { + var setCustomServer bool + var setExitNode bool + var setAllowLANAccess bool + if URL := e.GetURL(); URL != "" { + state.Prefs.ControlURL = URL + setCustomServer = true + } + if nodeID := e.GetExitNodeID(); nodeID != "" { + state.Prefs.ExitNodeID = nodeID + state.updateExitNodes() + a.notify(state) + if service != 0 { + a.updateNotification(service, state.State, state.ExitStatus, state.Exit) + } + setExitNode = true + } + if e.GetExitAllowLAN() { + state.Prefs.ExitNodeAllowLANAccess = true + } + return setCustomServer, setExitNode, setAllowLANAccess +} + func (a *App) processWaitingFiles(b *ipnlocal.LocalBackend) error { files, err := b.WaitingFiles() if err != nil { @@ -1290,6 +1383,7 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, act jni.Object, s case ExitAllowLANEvent: requestBackend(e) case WebAuthEvent: + a.decorateEventWithStoredSettings(&e, state) a.store.WriteString(loginMethodPrefKey, loginMethodWeb) requestBackend(e) case SetLoginServerEvent: @@ -1299,11 +1393,7 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, act jni.Object, s a.signOut() requestBackend(e) case ConnectEvent: - if srv, _ := a.store.ReadString(customLoginServerPrefKey, ""); srv != state.backend.Prefs.ControlURL { - requestBackend(SetLoginServerEvent{URL: srv}) - // wait a moment for the backend to restart - <-time.After(200 * time.Millisecond) - } + a.decorateEventWithStoredSettings(&e, state) requestBackend(e) case RouteAllEvent: requestBackend(e) @@ -1323,6 +1413,24 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, act jni.Object, s } } +func (a *App) decorateEventWithStoredSettings(e RestoreEvent, state *clientState) { + srv, _ := a.store.ReadString(customLoginServerPrefKey, "") + if srv != "" && srv != state.backend.Prefs.ControlURL { + e.SetURL(srv) + } + + exitstr, _ := a.store.ReadString(exitNodePrefKey, "") + exitNodeID := tailcfg.StableNodeID(exitstr) + if exitNodeID != "" && exitNodeID != state.backend.Prefs.ExitNodeID { + e.SetExitNodeID(exitNodeID) + } + + allowlan, _ := a.store.ReadBool(exitAllowLANPrefKey, false) + if allowlan { + e.SetExitAllowLAN(allowlan) + } +} + func (a *App) sendFiles(e FileSendEvent, files []File) { go func() { var totalSize int64