diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311..92a792b784 100644 --- a/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // TODO: move outside of the connectivity JAR - SystemServiceRegistry.registerContextAwareService( - Context.VPN_MANAGEMENT_SERVICE, - VpnManager.class, - (context) -> { - final ConnectivityManager cm = context.getSystemService( - ConnectivityManager.class); - return cm.createVpnManager(); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, ConnectivityDiagnosticsManager.class, diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8437798b7b..25b6460ddf 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -824,6 +824,7 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; + /** * A kludge to facilitate static access where a Context pointer isn't available, like in the * case of the static set/getProcessDefaultNetwork methods and from the Network class. @@ -1069,106 +1070,55 @@ public class ConnectivityManager { } /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - * - * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ + @Deprecated public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); } /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - *

The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); } - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); } /** - * @return whether always-on VPN is in lockdown mode. - * + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + return getVpnManager().isVpnLockdownEnabled(userId); } /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public List getVpnLockdownWhitelist(int userId) { - try { - return mService.getVpnLockdownWhitelist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getVpnLockdownAllowlist(userId); } /** @@ -3219,20 +3169,13 @@ public class ConnectivityManager { } /** - * If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - * @return a boolean with {@code} indicating success - * - *

This method can only be called by the system UID - * {@hide} + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide */ + @Deprecated public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().updateLockdownVpn(); } /** @@ -4596,6 +4539,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4889,9 +4834,13 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** @hide */ - public VpnManager createVpnManager() { - return new VpnManager(mContext, mService); + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); } /** @hide */ diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index ab134eb6d2..77aee5ec6f 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -42,9 +42,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -122,34 +119,6 @@ interface IConnectivityManager ProxyInfo getProxyForNetwork(in Network nework); - boolean prepareVpn(String oldPackage, String newPackage, int userId); - - void setVpnPackageAuthorization(String packageName, int userId, int vpnType); - - ParcelFileDescriptor establishVpn(in VpnConfig config); - - boolean provisionVpnProfile(in VpnProfile profile, String packageName); - - void deleteVpnProfile(String packageName); - - void startVpnProfile(String packageName); - - void stopVpnProfile(String packageName); - - VpnConfig getVpnConfig(int userId); - - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void startLegacyVpn(in VpnProfile profile); - - LegacyVpnInfo getLegacyVpnInfo(int userId); - - boolean updateLockdownVpn(); - boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, - in List lockdownWhitelist); - String getAlwaysOnVpnPackage(int userId); - boolean isVpnLockdownEnabled(int userId); - List getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); void setLegacyLockdownVpnEnabled(boolean enabled); @@ -200,10 +169,6 @@ interface IConnectivityManager int getRestoreDefaultNetworkDelay(int networkType); - boolean addVpnAddress(String address, int prefixLength); - boolean removeVpnAddress(String address, int prefixLength); - boolean setUnderlyingNetworksForVpn(in Network[] networks); - void factoryReset(); void startNattKeepalive(in Network network, int intervalSeconds, @@ -223,8 +188,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index a65769cc72..f472ed4381 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; +import java.util.List; /** * This class provides an interface for apps to manage platform VPN profiles @@ -88,7 +90,7 @@ public class VpnManager { public @interface VpnType {} @NonNull private final Context mContext; - @NonNull private final IConnectivityManager mService; + @NonNull private final IVpnManager mService; private static Intent getIntentForConfirmation() { final Intent intent = new Intent(); @@ -107,9 +109,9 @@ public class VpnManager { * * @hide */ - public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IConnectivityManager"); + mService = checkNotNull(service, "missing IVpnManager"); } /** @@ -200,6 +202,19 @@ public class VpnManager { } } + /** + * Resets all VPN settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Prepare for a VPN application. * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, @@ -245,6 +260,108 @@ public class VpnManager { } } + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List getVpnLockdownAllowlist(int userId) { + try { + return mService.getVpnLockdownAllowlist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return the legacy VPN information for the specified user ID. * @hide diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java index 8e90a119fe..e43b0b6fa6 100644 --- a/framework/src/android/net/VpnService.java +++ b/framework/src/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "android.net.VpnService.SUPPORTS_ALWAYS_ON"; /** - * Use IConnectivityManager since those methods are hidden and not - * available in ConnectivityManager. + * Use IVpnManager since those methods are hidden and not available in VpnManager. */ - private static IConnectivityManager getService() { - return IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private static IVpnManager getService() { + return IVpnManager.Stub.asInterface( + ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); } /** @@ -226,15 +225,15 @@ public class VpnService extends Service { @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_VPN) public static void prepareAndAuthorize(Context context) { - IConnectivityManager cm = getService(); + IVpnManager vm = getService(); String packageName = context.getPackageName(); try { // Only prepare if we're not already prepared. int userId = context.getUserId(); - if (!cm.prepareVpn(packageName, null, userId)) { - cm.prepareVpn(null, packageName, userId); + if (!vm.prepareVpn(packageName, null, userId)) { + vm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 84aaca0ae2..a3a17b3504 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; @@ -138,7 +137,6 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; -import android.net.VpnService; import android.net.VpnTransportInfo; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; @@ -170,8 +168,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.security.Credentials; -import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -187,9 +183,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; @@ -214,9 +207,7 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; -import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -309,18 +300,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final PerUidCounter mNetworkRequestCounter; - private KeyStore mKeyStore; - - @VisibleForTesting - @GuardedBy("mVpns") - protected final SparseArray mVpns = new SparseArray<>(); - - // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by - // a direct call to LockdownVpnTracker.isEnabled(). - @GuardedBy("mVpns") private volatile boolean mLockdownEnabled; - @GuardedBy("mVpns") - private LockdownVpnTracker mLockdownTracker; /** * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal @@ -997,13 +977,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return NetworkStackClient.getInstance(); } - /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - /** * @see ProxyTracker */ @@ -1113,7 +1086,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler); mNetd = netd; - mKeyStore = mDeps.getKeyStore(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); @@ -1202,43 +1174,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNetd); - // Set up the listener for user state for creating user VPNs. + // Listen for user add/removes to inform PermissionMonitor. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( - mUserPresentReceiver, - new IntentFilter(Intent.ACTION_USER_PRESENT), - null /* broadcastPermission */, - null /* scheduler */); - - // Listen to package add and removal events for all users. - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - - // Listen to lockdown VPN reset. - intentFilter = new IntentFilter(); - intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - mUserAllContext.registerReceiver( - mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, + null /* broadcastPermission */, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1416,9 +1360,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (mLockdownEnabled) return null; - } + if (mLockdownEnabled) return null; final NetworkAgentInfo nai = getVpnForUid(uid); if (nai != null) return nai.declaredUnderlyingNetworks; return null; @@ -2185,22 +2127,6 @@ public class ConnectivityService extends IConnectivityManager.Stub isBackgroundRestricted); } - /** - * Require that the caller is either in the same user or has appropriate permission to interact - * across users. - * - * @param userId Target user for whatever operation the current IPC is supposed to perform. - */ - private void enforceCrossUserPermission(int userId) { - if (userId == UserHandle.getCallingUserId()) { - // Not a cross-user call. - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "ConnectivityService"); - } - private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { @@ -2281,12 +2207,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } - private void enforceControlAlwaysOnVpnPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, - "ConnectivityService"); - } - private void enforceNetworkStackOrSettingsPermission() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -2452,10 +2372,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait - // for user to unlock device too. - updateLockdownVpn(); - // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); } @@ -4739,183 +4655,6 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.e(TAG, s, t); } - /** - * Prepare for a VPN application. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - @Override - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - throwIfLockdownEnabled(); - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); - } else { - return false; - } - } - } - - /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. This - * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} - * class. If the caller is not {@code userId}, {@link - * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param packageName The package for which authorization state should change. - * @param userId User for whom {@code packageName} is installed. - * @param authorized {@code true} if this app should be able to start a VPN connection without - * explicit user approval, {@code false} if not. - * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN - * permissions should be granted. When unauthorizing an app, {@link - * VpnManager.TYPE_VPN_NONE} should be used. - * @hide - */ - @Override - public void setVpnPackageAuthorization( - String packageName, int userId, @VpnManager.VpnType int vpnType) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - vpn.setPackageAuthorization(packageName, vpnType); - } - } - } - - /** - * Configure a TUN interface and return its file descriptor. Parameters - * are encoded and opaque to this class. This method is used by VpnBuilder - * and not available in ConnectivityManager. Permissions are checked in - * Vpn class. - * @hide - */ - @Override - public ParcelFileDescriptor establishVpn(VpnConfig config) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).establish(config); - } - } - - /** - * Stores the given VPN profile based on the provisioning package name. - * - *

If there is already a VPN profile stored for the provisioning package, this call will - * overwrite the profile. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @return {@code true} if user consent has already been granted, {@code false} otherwise. - * @hide - */ - @Override - public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); - } - } - - /** - * Deletes the stored VPN profile for the provisioning package - * - *

If there are no profiles for the given package, this method will silently succeed. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void deleteVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); - } - } - - /** - * Starts the VPN based on the stored profile for the given package - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @throws IllegalArgumentException if no profile was found for the given package name. - * @hide - */ - @Override - public void startVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startVpnProfile(packageName, mKeyStore); - } - } - - /** - * Stops the Platform VPN if the provided package is running one. - * - *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed - * exclusively by the Settings app, and passed into the platform at startup time. - * - * @hide - */ - @Override - public void stopVpnProfile(@NonNull String packageName) { - final int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - mVpns.get(user).stopVpnProfile(packageName); - } - } - - /** - * Start legacy VPN, controlling native daemons as needed. Creates a - * secondary thread to perform connection work, returning quickly. - */ - @Override - public void startLegacyVpn(VpnProfile profile) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final LinkProperties egress = getActiveLinkProperties(); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); - } - } - - /** - * Return the information of the ongoing legacy VPN. This method is used - * by VpnSettings and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - */ - @Override - public LegacyVpnInfo getLegacyVpnInfo(int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); - } - } - /** * Return the information of all ongoing VPNs. * @@ -4925,10 +4664,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private UnderlyingNetworkInfo[] getAllVpnInfo() { ensureRunningOnConnectivityServiceThread(); - synchronized (mVpns) { - if (mLockdownEnabled) { - return new UnderlyingNetworkInfo[0]; - } + if (mLockdownEnabled) { + return new UnderlyingNetworkInfo[0]; } List infoList = new ArrayList<>(); for (NetworkAgentInfo nai : mNetworkAgentInfos) { @@ -4984,25 +4721,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties.getInterfaceName(), interfaces); } - /** - * Returns the information of the ongoing VPN for {@code userId}. This method is used by - * VpnDialogs and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide - */ - @Override - public VpnConfig getVpnConfig(int userId) { - enforceCrossUserPermission(userId); - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.getVpnConfig(); - } else { - return null; - } - } - } - // TODO This needs to be the default network that applies to the NAI. private Network[] underlyingNetworksOrDefault(final int ownerUid, Network[] underlyingNetworks) { @@ -5096,11 +4814,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.post(() -> mLockdownEnabled = enabled); } - // TODO: remove when the VPN code moves out. - private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); - } - private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { return mLockdownEnabled && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY @@ -5143,193 +4856,6 @@ public class ConnectivityService extends IConnectivityManager.Stub : DetailedState.CONNECTED; } - @Override - public boolean updateLockdownVpn() { - // Allow the system UID for the system server and for Settings. - // Also, for unit tests, allow the process that ConnectivityService is running in. - if (mDeps.getCallingUid() != Process.SYSTEM_UID - && Binder.getCallingPid() != Process.myPid()) { - logw("Lockdown VPN only available to system process or AID_SYSTEM"); - return false; - } - - synchronized (mVpns) { - // Tear down existing lockdown if profile was removed - if (!isLockdownVpnEnabled()) { - setLockdownTracker(null); - return true; - } - - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); - } - - return true; - } - - /** - * Internally set new {@link LockdownVpnTracker}, shutting down any existing - * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. - */ - @GuardedBy("mVpns") - private void setLockdownTracker(LockdownVpnTracker tracker) { - // Shutdown any existing tracker - final LockdownVpnTracker existing = mLockdownTracker; - // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the - // necessary onBlockedStatusChanged callbacks. - mLockdownTracker = null; - if (existing != null) { - existing.shutdown(); - } - - if (tracker != null) { - mLockdownTracker = tracker; - mLockdownTracker.init(); - } - } - - /** - * Throws if there is any currently running, always-on Legacy VPN. - * - *

The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is - * running across the entire system. Tracking for app-based VPNs is done on a per-user, - * per-package basis in Vpn.java - */ - @GuardedBy("mVpns") - private void throwIfLockdownEnabled() { - if (mLockdownEnabled) { - throw new IllegalStateException("Unavailable in lockdown mode"); - } - } - - /** - * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform - * some setup and then call {@code establish()} to connect. - * - * @return {@code true} if the service was started, the service was already connected, or there - * was no always-on VPN to start. {@code false} otherwise. - */ - private boolean startAlwaysOnVpn(int userId) { - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - // Shouldn't happen as all code paths that point here should have checked the Vpn - // exists already. - Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); - return false; - } - - return vpn.startAlwaysOnVpn(mKeyStore); - } - } - - @Override - public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { - enforceSettingsPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); - } - } - - @Override - public boolean setAlwaysOnVpnPackage( - int userId, String packageName, boolean lockdown, List lockdownWhitelist) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (isLockdownVpnEnabled()) { - return false; - } - - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) { - return false; - } - if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - return false; - } - } - return true; - } - - @Override - public String getAlwaysOnVpnPackage(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getAlwaysOnPackage(); - } - } - - @Override - public boolean isVpnLockdownEnabled(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.getLockdown(); - } - } - - @Override - public List getVpnLockdownWhitelist(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getLockdownAllowlist(); - } - } - @Override public void setProvisioningNotificationVisible(boolean visible, int networkType, String action) { @@ -5362,165 +4888,33 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserStarted(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn != null) { - loge("Starting user already has a VPN"); - return; - } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); - mVpns.put(userId, userVpn); - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } - } - } - - private void onUserStopped(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn == null) { - loge("Stopped user has no VPN"); - return; - } - userVpn.onUserStopped(); - mVpns.delete(userId); - } - } - private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserAdded(userId); - } - } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserRemoved(userId); - } - } - } - - private void onPackageReplaced(String packageName, int uid) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); - return; - } - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { - log("Restarting always-on VPN package " + packageName + " for user " - + userId); - vpn.startAlwaysOnVpn(mKeyStore); - } - } - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); - return; - } - - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { - log("Removing always-on VPN package " + packageName + " for user " - + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - } - } - } - - private void onUserUnlocked(int userId) { - synchronized (mVpns) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); - } - } - } - - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; - - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); - } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; - if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(userId); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(userId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - onUserUnlocked(userId); - } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - onPackageReplaced(packageName, uid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - final boolean isReplacing = intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - onPackageRemoved(packageName, uid, isReplacing); - } else { + } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; - private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Try creating lockdown tracker, since user present usually means - // unlocked keystore. - updateLockdownVpn(); - // Use the same context that registered receiver before to unregister it. Because use - // different context to unregister receiver will cause exception. - context.unregisterReceiver(this); - } - }; - private final HashMap mNetworkProviderInfos = new HashMap<>(); private final HashMap mNetworkRequests = new HashMap<>(); @@ -8260,34 +7654,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @Override - public boolean addVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).addAddress(address, prefixLength); - } - } - - @Override - public boolean removeVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).removeAddress(address, prefixLength); - } - } - - @Override - public boolean setUnderlyingNetworksForVpn(Network[] networks) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final boolean success; - synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); - } - return success; - } - @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); @@ -8367,8 +7733,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8380,44 +7744,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Turn airplane mode off setAirplaneMode(false); - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { - // Remove always-on package - synchronized (mVpns) { - final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); - if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); - } - - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating - // VPN connections in the future without user intervention. - setVpnPackageAuthorization( - vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); - - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); - } - } - } - } - // restore private DNS settings to default mode (opportunistic) if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) { Settings.Global.putString(mContext.getContentResolver(), @@ -8509,25 +7835,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @GuardedBy("mVpns") - private Vpn getVpnIfOwner() { - return getVpnIfOwner(mDeps.getCallingUid()); - } - - // TODO: stop calling into Vpn.java and get this information from data in this class. - @GuardedBy("mVpns") - private Vpn getVpnIfOwner(int uid) { - final int user = UserHandle.getUserId(uid); - - final Vpn vpn = mVpns.get(user); - if (vpn == null) { - return null; - } else { - final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); - return (info == null || info.ownerUid != uid) ? null : vpn; - } - } - private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) { if (vpn == null) return VpnManager.TYPE_VPN_NONE; final TransportInfo ti = vpn.networkCapabilities.getTransportInfo(); @@ -8564,22 +7871,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return uid; } - @Override - public boolean isCallerCurrentAlwaysOnVpnApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getAlwaysOn(); - } - } - - @Override - public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getLockdown(); - } - } - /** * Returns a IBinder to a TestNetworkService. Will be lazily created as needed. * diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java index 95a794235a..c548e30761 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -49,7 +49,7 @@ public class VpnManagerTest { private static final String IDENTITY_STRING = "Identity"; private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); - private IConnectivityManager mMockCs; + private IVpnManager mMockService; private VpnManager mVpnManager; private final MockContext mMockContext = new MockContext() { @@ -61,24 +61,26 @@ public class VpnManagerTest { @Before public void setUp() throws Exception { - mMockCs = mock(IConnectivityManager.class); - mVpnManager = new VpnManager(mMockContext, mMockCs); + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); } @Test public void testProvisionVpnProfilePreconsented() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(true); // Expect there to be no intent returned, as consent has already been granted. assertNull(mVpnManager.provisionVpnProfile(profile)); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testProvisionVpnProfileNeedsConsent() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(false); // Expect intent to be returned, as consent has not already been granted. final Intent intent = mVpnManager.provisionVpnProfile(profile); @@ -88,25 +90,25 @@ public class VpnManagerTest { ComponentName.unflattenFromString( "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); assertEquals(expectedComponentName, intent.getComponent()); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testDeleteProvisionedVpnProfile() throws Exception { mVpnManager.deleteProvisionedVpnProfile(); - verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); } @Test public void testStartProvisionedVpnProfile() throws Exception { mVpnManager.startProvisionedVpnProfile(); - verify(mMockCs).startVpnProfile(eq(PKG_NAME)); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); } @Test public void testStopProvisionedVpnProfile() throws Exception { mVpnManager.stopProvisionedVpnProfile(); - verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); } private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1da4e317a0..917a359523 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -370,6 +370,7 @@ public class ConnectivityServiceTest { private MockContext mServiceContext; private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; private ConnectivityService.Dependencies mDeps; private ConnectivityService mService; private WrappedConnectivityManager mCm; @@ -384,6 +385,7 @@ public class ConnectivityServiceTest { private TestNetIdManager mNetIdManager; private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; + private VpnManagerService mVpnManagerService; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -1256,15 +1258,29 @@ public class ConnectivityServiceTest { r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); } - private void mockVpn(int uid) { - synchronized (mService.mVpns) { - int userId = UserHandle.getUserId(uid); - mMockVpn = new MockVpn(userId); - // This has no effect unless the VPN is actually connected, because things like - // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN - // netId, and check if that network is actually connected. - mService.mVpns.put(userId, mMockVpn); - } + private VpnManagerService makeVpnManagerService() { + final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { + public int getCallingUid() { + return mDeps.getCallingUid(); + } + + public HandlerThread makeHandlerThread() { + return mVMSHandlerThread; + } + + public KeyStore getKeyStore() { + return mKeyStore; + } + + public INetd getNetd() { + return mMockNetd; + } + + public INetworkManagementService getINetworkManagementService() { + return mNetworkManagementService; + } + }; + return new VpnManagerService(mServiceContext, deps); } private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { @@ -1278,11 +1294,21 @@ public class ConnectivityServiceTest { private void processBroadcastForVpn(Intent intent) { // The BroadcastReceiver for this broadcast checks it is being run on the handler thread. - final Handler handler = new Handler(mCsHandlerThread.getLooper()); + final Handler handler = new Handler(mVMSHandlerThread.getLooper()); handler.post(() -> mServiceContext.sendBroadcast(intent)); + HandlerUtils.waitForIdle(handler, TIMEOUT_MS); waitForIdle(); } + private void mockVpn(int uid) { + synchronized (mVpnManagerService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // Every running user always has a Vpn in the mVpns array, even if no VPN is running. + mVpnManagerService.mVpns.put(userId, mMockVpn); + } + } + private void mockUidNetworkingBlocked() { doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, @@ -1406,6 +1432,7 @@ public class ConnectivityServiceTest { initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, @@ -1428,6 +1455,8 @@ public class ConnectivityServiceTest { // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); mService.systemReadyInternal(); + mVpnManagerService = makeVpnManagerService(); + mVpnManagerService.systemReady(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); mQosCallbackTracker = mock(QosCallbackTracker.class); @@ -1455,7 +1484,6 @@ public class ConnectivityServiceTest { doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); - doReturn(mKeyStore).when(deps).getKeyStore(); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); @@ -6766,8 +6794,8 @@ public class ConnectivityServiceTest { // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. final ArrayList allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */, - allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, + true /* lockdown */, allowList); waitForIdle(); assertNull(mCm.getActiveNetworkForUid(uid)); // This is arguably overspecified: a UID that is not running doesn't have an active network. @@ -6797,7 +6825,8 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkForUid(uid)); assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, + allowList); waitForIdle(); } @@ -7173,7 +7202,8 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); @@ -7195,7 +7225,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown, expect to see the network unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7208,7 +7238,8 @@ public class ConnectivityServiceTest { // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. allowList.add(TEST_PACKAGE_NAME); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); callback.assertNoCallback(); defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); @@ -7241,11 +7272,12 @@ public class ConnectivityServiceTest { // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. // Everything should now be blocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); allowList.clear(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); @@ -7258,7 +7290,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown. Everything is unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7270,7 +7302,8 @@ public class ConnectivityServiceTest { // Enable and disable an always-on VPN package without lockdown. Expect no changes. reset(mMockNetd); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, + allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7281,7 +7314,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7293,7 +7326,8 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); // Enable lockdown and connect a VPN. The VPN is not blocked. - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback();