Add always-on VPN support for platform VPNs
This commit allows Platform VPNs to be started as part of always-on
mode.
Test: FrameworksNetTests passing, new tests added in subsequent CL
Test: Manually tested.
Change-Id: I5eda88e5b406a0e425eb7424665cf702e0979324
Merged-In: I5eda88e5b406a0e425eb7424665cf702e0979324
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5f032fc..52a2ca9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4783,7 +4783,7 @@
return false;
}
- return vpn.startAlwaysOnVpn();
+ return vpn.startAlwaysOnVpn(mKeyStore);
}
}
@@ -4798,7 +4798,7 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- return vpn.isAlwaysOnPackageSupported(packageName);
+ return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
}
}
@@ -4819,11 +4819,11 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+ if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
return false;
}
if (!startAlwaysOnVpn(userId)) {
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
return false;
}
}
@@ -5009,7 +5009,7 @@
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
mVpns.put(userId, userVpn);
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -5080,7 +5080,7 @@
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+ userId);
- vpn.startAlwaysOnVpn();
+ vpn.startAlwaysOnVpn(mKeyStore);
}
}
}
@@ -5102,7 +5102,7 @@
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+ userId);
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
}
}
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8ed497b..77147c8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -204,6 +204,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.security.KeyStore;
import android.system.Os;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
@@ -1019,7 +1020,7 @@
public MockVpn(int userId) {
super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
- userId);
+ userId, mock(KeyStore.class));
}
public void setNetworkAgent(TestNetworkAgentWrapper agent) {
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index ac1c518..0e3b797 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -72,6 +72,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.ArrayMap;
@@ -260,17 +261,17 @@
assertFalse(vpn.getLockdown());
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertTrue(vpn.getLockdown());
// Remove always-on configuration.
- assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+ assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
}
@@ -284,11 +285,11 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,7 +298,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -316,7 +317,8 @@
final UidRange user = UidRange.createForUser(primaryUser.id);
// Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
@@ -325,7 +327,8 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
// Change whitelisted app to PKGS[3].
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
}));
@@ -337,7 +340,8 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
// Change the VPN app.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -350,7 +354,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
// Remove the whitelist.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -363,7 +367,8 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0]);
// Add the whitelist.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
}));
@@ -375,12 +380,13 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
// Try whitelisting a package with a comma, should be rejected.
- assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+ assertFalse(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
// Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
// Whitelisted package should change from PGKS[1] to PKGS[2].
- assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
- Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -405,7 +411,7 @@
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -499,22 +505,22 @@
.thenReturn(Collections.singletonList(resInfo));
// null package name should return false
- assertFalse(vpn.isAlwaysOnPackageSupported(null));
+ assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
// Pre-N apps are not supported
appInfo.targetSdkVersion = VERSION_CODES.M;
- assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// N+ apps are supported by default
appInfo.targetSdkVersion = VERSION_CODES.N;
- assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// Apps that opt out explicitly are not supported
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
Bundle metaData = new Bundle();
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
svcInfo.metaData = metaData;
- assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
}
@Test
@@ -531,7 +537,7 @@
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
- vpn.setAlwaysOnPackage(PKGS[0], false, null);
+ vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
order.verify(mNotificationManager)
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
@@ -545,7 +551,7 @@
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
- vpn.setAlwaysOnPackage(null, false, null);
+ vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
@@ -920,12 +926,48 @@
eq(AppOpsManager.MODE_IGNORED));
}
+ private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+ assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+
+ verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+ verify(mAppOps).setMode(
+ eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+ eq(AppOpsManager.MODE_ALLOWED));
+
+ verify(mSystemServices).settingsSecurePutStringForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
+ verify(mSystemServices).settingsSecurePutIntForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+ eq(primaryUser.id));
+ verify(mSystemServices).settingsSecurePutStringForUser(
+ eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
+ }
+
+ @Test
+ public void testSetAndStartAlwaysOnVpn() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+ setMockedUsers(primaryUser);
+
+ // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+ final int uid = Process.myUid() + 1;
+ when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+ .thenReturn(uid);
+ when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ setAndVerifyAlwaysOnPackage(vpn, uid, false);
+ assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+
+ // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+ // a subsequent CL.
+ }
+
/**
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
return new Vpn(Looper.myLooper(), mContext, mNetService,
- userId, mSystemServices, mIkev2SessionCreator);
+ userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {