Skip to content

Commit 3d1ad78

Browse files
davfsaserein-213
authored andcommitted
android: add selection of included/excluded apps in split tunneling (tailscale#621)
* Add ability to select included/excluded apps from App Split Tunneling Updates tailscale/tailscale#14660 Signed-off-by: davfsa <davfsa@gmail.com> * Update app split tunneling description string Signed-off-by: davfsa <davfsa@gmail.com> * Address review comments Signed-off-by: davfsa <davfsa@gmail.com> * fix: update uses of excludedPackageNames Signed-off-by: davfsa <davfsa@gmail.com> * chore: pr review Do not add self package by default Signed-off-by: davfsa <davfsa@gmail.com> * feat: improve switch dialogs + add lazy app list loading Signed-off-by: davfsa <davfsa@gmail.com> --------- Signed-off-by: davfsa <davfsa@gmail.com>
1 parent 405417a commit 3d1ad78

7 files changed

Lines changed: 311 additions & 94 deletions

File tree

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

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,15 @@ open class UninitializedApp : Application() {
499499
// Key for shared preference that tracks whether or not we're able to start
500500
// the VPN (i.e. we're logged in and machine is authorized).
501501
private const val ABLE_TO_START_VPN_KEY = "ableToStartVPN"
502-
private const val DISALLOWED_APPS_KEY = "disallowedApps"
502+
503+
// The value is 'disallowedApps' as it used to represent
504+
// only disallowed applications. This has been changed
505+
// and allowing/disallowing is based on ALLOW_SELECTED_APPS_KEY
506+
//
507+
// The value is kept the same to not reset everyone's configuration
508+
private const val SELECTED_APPS_KEY = "disallowedApps"
509+
private const val ALLOW_SELECTED_APPS_KEY = "allowSelectedApps"
510+
503511
// File for shared preferences that are not encrypted.
504512
private const val UNENCRYPTED_PREFERENCES = "unencrypted"
505513
private lateinit var appInstance: UninitializedApp
@@ -664,25 +672,34 @@ open class UninitializedApp : Application() {
664672
return builder.build()
665673
}
666674

667-
fun updateUserDisallowedPackageNames(packageNames: List<String>) {
675+
fun updateUserSelectedPackages(packageNames: List<String>) {
668676
if (packageNames.any { it.isEmpty() }) {
669-
TSLog.e(TAG, "updateUserDisallowedPackageNames called with empty packageName(s)")
677+
TSLog.e(TAG, "updateUserSelectedPackage called with empty packageName(s)")
670678
return
671679
}
672-
getUnencryptedPrefs().edit().putStringSet(DISALLOWED_APPS_KEY, packageNames.toSet()).apply()
680+
681+
getUnencryptedPrefs().edit().putStringSet(SELECTED_APPS_KEY, packageNames.toSet()).apply()
682+
673683
this.restartVPN()
674684
}
675685

676-
fun disallowedPackageNames(): List<String> {
677-
val mdmDisallowed =
678-
MDMSettings.excludedPackages.flow.value.value?.split(",")?.map { it.trim() } ?: emptyList()
679-
if (mdmDisallowed.isNotEmpty()) {
680-
TSLog.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
681-
return builtInDisallowedPackageNames + mdmDisallowed
682-
}
683-
val userDisallowed =
684-
getUnencryptedPrefs().getStringSet(DISALLOWED_APPS_KEY, emptySet())?.toList() ?: emptyList()
685-
return builtInDisallowedPackageNames + userDisallowed
686+
fun switchUserSelectedPackages() {
687+
getUnencryptedPrefs()
688+
.edit()
689+
.putBoolean(ALLOW_SELECTED_APPS_KEY, !allowSelectedPackages())
690+
.apply()
691+
getUnencryptedPrefs().edit().putStringSet(SELECTED_APPS_KEY, setOf()).apply()
692+
693+
this.restartVPN()
694+
}
695+
696+
fun selectedPackageNames(): List<String> {
697+
return getUnencryptedPrefs().getStringSet(SELECTED_APPS_KEY, emptySet())?.toList()
698+
?: emptyList()
699+
}
700+
701+
fun allowSelectedPackages(): Boolean {
702+
return getUnencryptedPrefs().getBoolean(ALLOW_SELECTED_APPS_KEY, false)
686703
}
687704

688705
fun getAppScopedViewModel(): AppViewModel {

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

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,19 @@ open class IPNService : VpnService(), libtailscale.IPNService {
141141
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
142142
}
143143

144+
private fun allowApp(b: Builder, name: String) {
145+
try {
146+
b.addAllowedApplication(name)
147+
} catch (e: PackageManager.NameNotFoundException) {
148+
TSLog.e(TAG, "Failed to add allowed application: $e")
149+
}
150+
}
151+
144152
private fun disallowApp(b: Builder, name: String) {
145153
try {
146154
b.addDisallowedApplication(name)
147155
} catch (e: PackageManager.NameNotFoundException) {
148-
TSLog.d(TAG, "Failed to add disallowed application: $e")
156+
TSLog.e(TAG, "Failed to add disallowed application: $e")
149157
}
150158
}
151159

@@ -160,23 +168,45 @@ open class IPNService : VpnService(), libtailscale.IPNService {
160168
}
161169
b.setUnderlyingNetworks(null) // Use all available networks.
162170

163-
val includedPackages: List<String> =
171+
val mdmAllowed =
164172
MDMSettings.includedPackages.flow.value.value?.split(",")?.map { it.trim() } ?: emptyList()
165-
if (includedPackages.isNotEmpty()) {
166-
// If an admin defined a list of packages that are exclusively allowed to be used via
167-
// Tailscale,
168-
// then only allow those apps.
169-
for (packageName in includedPackages) {
173+
val mdmDisallowed =
174+
MDMSettings.excludedPackages.flow.value.value?.split(",")?.map { it.trim() } ?: emptyList()
175+
176+
var packagesList: List<String>
177+
var allowPackages: Boolean
178+
if (mdmAllowed.isNotEmpty()) {
179+
// An admin defined a list of packages that are exclusively allowed to be used via
180+
// Tailscale, so only allow those.
181+
packagesList = mdmAllowed
182+
allowPackages = true
183+
TSLog.d(TAG, "Included application packages were set via MDM: $mdmAllowed")
184+
} else if (mdmDisallowed.isNotEmpty()) {
185+
// An admin defined a list of packages that are excluded from accessing Tailscale,
186+
// so ignore user definitions and only exclude those
187+
packagesList = mdmDisallowed
188+
allowPackages = false
189+
TSLog.d(TAG, "Excluded application packages were set via MDM: $mdmDisallowed")
190+
} else {
191+
// Otherwise, prevent user manually disallowed apps from getting their traffic + DNS routed
192+
// via Tailscale
193+
packagesList = UninitializedApp.get().selectedPackageNames()
194+
allowPackages = UninitializedApp.get().allowSelectedPackages()
195+
TSLog.d(TAG, "Application packages were set by user: $packagesList")
196+
}
197+
198+
if (allowPackages) {
199+
for (packageName in packagesList) {
170200
TSLog.d(TAG, "Including app: $packageName")
171-
b.addAllowedApplication(packageName)
201+
allowApp(b, packageName)
172202
}
173203
} else {
174-
// Otherwise, prevent certain apps from getting their traffic + DNS routed via Tailscale:
175-
// - any app that the user manually disallowed in the GUI
176-
// - any app that we disallowed via hard-coding
177-
for (disallowedPackageName in UninitializedApp.get().disallowedPackageNames()) {
178-
TSLog.d(TAG, "Disallowing app: $disallowedPackageName")
179-
disallowApp(b, disallowedPackageName)
204+
// Make sure to also exclude hard-coded apps that are known to cause issues
205+
packagesList += UninitializedApp.get().builtInDisallowedPackageNames
206+
207+
for (packageName in packagesList) {
208+
TSLog.d(TAG, "Disallowing app: $packageName")
209+
disallowApp(b, packageName)
180210
}
181211
}
182212

android/src/main/java/com/tailscale/ipn/ui/util/InstalledAppsManager.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package com.tailscale.ipn.ui.util
66
import android.Manifest
77
import android.content.pm.ApplicationInfo
88
import android.content.pm.PackageManager
9+
import com.tailscale.ipn.BuildConfig
910

1011
data class InstalledApp(val name: String, val packageName: String)
1112

@@ -26,7 +27,7 @@ class InstalledAppsManager(
2627
}
2728

2829
private val appIsIncluded: (ApplicationInfo) -> Boolean = { app ->
29-
app.packageName != "com.tailscale.ipn" &&
30+
app.packageName != BuildConfig.APPLICATION_ID &&
3031
// Only show apps that can access the Internet
3132
packageManager.checkPermission(Manifest.permission.INTERNET, app.packageName) ==
3233
PackageManager.PERMISSION_GRANTED

android/src/main/java/com/tailscale/ipn/ui/view/SettingsView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fun SettingsView(
9292
Lists.ItemDivider()
9393
Setting.Text(
9494
R.string.split_tunneling,
95-
subtitle = stringResource(R.string.exclude_certain_apps_from_using_tailscale),
95+
subtitle = stringResource(R.string.filter_apps_allowed_to_access_tailscale),
9696
onClick = settingsNav.onNavigateToSplitTunneling)
9797

9898
if (showTailnetLock.value == ShowHide.Show) {

0 commit comments

Comments
 (0)