Skip to content

Commit a70c5a8

Browse files
authored
android: start IPNService foreground before browser opens in login (#747)
When the user taps on the log in button, the browser opens, and the app is backgrounded without a foreground service running. This can lead to network access restriction, causing the control client register request to fail with "software caused connection abort" / "context deadline exceeded". This fix: -Starts IPNService as a foreground service before the browser opens wat the start of login Updates tailscale/tailscale#18304 Signed-off-by: kari-ts <kari@tailscale.com>
1 parent 7c16d1d commit a70c5a8

3 files changed

Lines changed: 30 additions & 0 deletions

File tree

android/src/main/java/com/tailscale/ipn/App.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,23 @@ open class UninitializedApp : Application() {
513513
return getSharedPreferences(UNENCRYPTED_PREFERENCES, MODE_PRIVATE)
514514
}
515515

516+
/**
517+
* Starts IPNService as a foreground service without creating a VPN tunnel. This prevents Android
518+
* from freezing the process and restricting network access during interactive login while the
519+
* user completes auth in the browser.
520+
*/
521+
fun startForegroundForLogin() {
522+
val intent =
523+
Intent(this, IPNService::class.java).apply {
524+
action = IPNService.ACTION_START_FOREGROUND_ONLY
525+
}
526+
try {
527+
startForegroundService(intent)
528+
} catch (e: Exception) {
529+
TSLog.e(TAG, "startForegroundForLogin hit exception: $e")
530+
}
531+
}
532+
516533
fun startVPN() {
517534
val intent = Intent(this, IPNService::class.java).apply { action = IPNService.ACTION_START_VPN }
518535
// FLAG_UPDATE_CURRENT ensures that if the intent is already pending, the existing intent will

android/src/main/java/com/tailscale/ipn/IPNService.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ open class IPNService : VpnService(), libtailscale.IPNService {
5353
}
5454
START_NOT_STICKY
5555
}
56+
ACTION_START_FOREGROUND_ONLY -> {
57+
// Start the foreground service notification without creating a VPN tunnel.
58+
// This is used during interactive login so that Android does not freeze the process
59+
// or restrict network access while the user completes auth in the browser.
60+
showForegroundNotification()
61+
START_NOT_STICKY
62+
}
5663
ACTION_START_VPN -> {
5764
showForegroundNotification()
5865
app.setWantRunning(true)
@@ -221,5 +228,6 @@ open class IPNService : VpnService(), libtailscale.IPNService {
221228
const val ACTION_START_VPN = "com.tailscale.ipn.START_VPN"
222229
const val ACTION_STOP_VPN = "com.tailscale.ipn.STOP_VPN"
223230
const val ACTION_RESTART_VPN = "com.tailscale.ipn.RESTART_VPN"
231+
const val ACTION_START_FOREGROUND_ONLY = "com.tailscale.ipn.START_FOREGROUND_ONLY"
224232
}
225233
}

android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ open class IpnViewModel : ViewModel() {
163163
authKey: String? = null,
164164
completionHandler: (Result<Unit>) -> Unit = {}
165165
) {
166+
// Start the IPNService foreground notification so that Android
167+
// does not freeze the process or cut network access while the user is in the browser
168+
// completing auth. The foreground service transitions to a full VPN service later when
169+
// startVPN() is called after the backend reaches Running state.
170+
UninitializedApp.get().startForegroundForLogin()
166171
val client = Client(viewModelScope)
167172

168173
val finalMaskedPrefs = maskedPrefs?.deepCopy() ?: Ipn.MaskedPrefs()

0 commit comments

Comments
 (0)