Merge changes Ia68f482a,I4911e214,Ied379654,I66d18512,Ie8e1bd63 am: 45ec3191a7 am: 246444fef7 am: ed9730e6f9

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1581293

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I698b6b082b121c004b2c8b05ef423da1bd8b871b
This commit is contained in:
Lorenzo Colitti
2021-02-15 01:32:50 +00:00
committed by Automerger Merge Worker
17 changed files with 1573 additions and 1017 deletions

View File

@@ -128,11 +128,13 @@ import android.net.EthernetManager;
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
import android.net.IVpnManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
import android.net.TetheringManager;
import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -384,6 +386,15 @@ public final class SystemServiceRegistry {
ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE));
}});
registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
new CachedServiceFetcher<VpnManager>() {
@Override
public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
IVpnManager service = IVpnManager.Stub.asInterface(b);
return new VpnManager(ctx, service);
}});
registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class,
new CachedServiceFetcher<VcnManager>() {
@Override

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.net.Network;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
/**
* Interface that manages VPNs.
*/
/** {@hide} */
interface IVpnManager {
/** VpnService APIs */
boolean prepareVpn(String oldPackage, String newPackage, int userId);
void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
ParcelFileDescriptor establishVpn(in VpnConfig config);
boolean addVpnAddress(String address, int prefixLength);
boolean removeVpnAddress(String address, int prefixLength);
boolean setUnderlyingNetworksForVpn(in Network[] networks);
/** VpnManager APIs */
boolean provisionVpnProfile(in VpnProfile profile, String packageName);
void deleteVpnProfile(String packageName);
void startVpnProfile(String packageName);
void stopVpnProfile(String packageName);
/** Always-on VPN APIs */
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
in List<String> lockdownAllowlist);
String getAlwaysOnVpnPackage(int userId);
boolean isVpnLockdownEnabled(int userId);
List<String> getVpnLockdownAllowlist(int userId);
boolean isCallerCurrentAlwaysOnVpnApp();
boolean isCallerCurrentAlwaysOnVpnLockdownApp();
/** Legacy VPN APIs */
void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo(int userId);
boolean updateLockdownVpn();
/** General system APIs */
VpnConfig getVpnConfig(int userId);
void factoryReset();
}

View File

@@ -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,

View File

@@ -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
* <ul>
* <li>target {@link VERSION_CODES#N API 24} or above, and
* <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
* meta-data field.
* </ul>
*
* @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.
*
* <p>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<String> 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<String> getVpnLockdownWhitelist(int userId) {
try {
return mService.getVpnLockdownWhitelist(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return getVpnManager().getVpnLockdownAllowlist(userId);
}
/**
@@ -1220,6 +1170,45 @@ public class ConnectivityManager {
}
}
/**
* Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
* LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
* but is still supported for backwards compatibility.
* <p>
* This type of VPN is assumed always to use the system default network, and must always declare
* exactly one underlying network, which is the network that was the default when the VPN
* connected.
* <p>
* Calling this method with {@code true} enables legacy behaviour, specifically:
* <ul>
* <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated
* {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the
* {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN
* connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network
* underlying the VPN.</li>
* <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state
* similarly replaced by the VPN network state.</li>
* <li>Information on current network interfaces passed to NetworkStatsService will not
* include any VPN interfaces.</li>
* </ul>
*
* @param enabled whether legacy lockdown VPN is enabled or disabled
*
* TODO: @SystemApi(client = MODULE_LIBRARIES)
*
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
public void setLegacyLockdownVpnEnabled(boolean enabled) {
try {
mService.setLegacyLockdownVpnEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
@@ -3180,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
*
* <p>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();
}
/**
@@ -4557,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();
}
@@ -4850,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 */

View File

@@ -43,9 +43,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
@@ -123,35 +120,8 @@ 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<String> lockdownWhitelist);
String getAlwaysOnVpnPackage(int userId);
boolean isVpnLockdownEnabled(int userId);
List<String> getVpnLockdownWhitelist(int userId);
void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
void setLegacyLockdownVpnEnabled(boolean enabled);
void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
@@ -200,10 +170,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 +189,6 @@ interface IConnectivityManager
byte[] getNetworkWatchlistConfigHash();
int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
boolean isCallerCurrentAlwaysOnVpnApp();
boolean isCallerCurrentAlwaysOnVpnLockdownApp();
void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
in NetworkRequest request, String callingPackageName);

View File

@@ -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
@@ -76,13 +78,19 @@ public class VpnManager {
@Deprecated
public static final int TYPE_VPN_LEGACY = 3;
/**
* Channel for VPN notifications.
* @hide
*/
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
/** @hide */
@IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
@Retention(RetentionPolicy.SOURCE)
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();
@@ -101,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");
}
/**
@@ -194,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},
@@ -239,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
* <ul>
* <li>target {@link VERSION_CODES#N API 24} or above, and
* <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
* meta-data field.
* </ul>
*
* @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.
*
* <p>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<String> 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<String> 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

View File

@@ -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
}

View File

@@ -228,8 +228,5 @@ filegroup {
"java/com/android/server/connectivity/QosCallbackAgentConnection.java",
"java/com/android/server/connectivity/QosCallbackTracker.java",
"java/com/android/server/connectivity/TcpKeepaliveController.java",
"java/com/android/server/connectivity/Vpn.java",
"java/com/android/server/connectivity/VpnIkev2Utils.java",
"java/com/android/server/net/LockdownVpnTracker.java",
],
}

View File

@@ -0,0 +1,918 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import static android.Manifest.permission.NETWORK_STACK;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IVpnManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkStack;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnService;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.INetworkManagementService;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.Vpn;
import com.android.server.net.LockdownVpnTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs.
* @hide
*/
public class VpnManagerService extends IVpnManager.Stub {
private static final String TAG = VpnManagerService.class.getSimpleName();
@VisibleForTesting
protected final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
private final Context mUserAllContext;
private final Dependencies mDeps;
private final ConnectivityManager mCm;
private final KeyStore mKeyStore;
private final INetworkManagementService mNMS;
private final INetd mNetd;
private final UserManager mUserManager;
@VisibleForTesting
@GuardedBy("mVpns")
protected final SparseArray<Vpn> mVpns = new SparseArray<>();
// TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
// a direct call to LockdownVpnTracker.isEnabled().
@GuardedBy("mVpns")
private boolean mLockdownEnabled;
@GuardedBy("mVpns")
private LockdownVpnTracker mLockdownTracker;
/**
* Dependencies of VpnManager, for injection in tests.
*/
@VisibleForTesting
public static class Dependencies {
/** Returns the calling UID of an IPC. */
public int getCallingUid() {
return Binder.getCallingUid();
}
/** Creates a HandlerThread to be used by this class. */
public HandlerThread makeHandlerThread() {
return new HandlerThread("VpnManagerService");
}
/** Returns the KeyStore instance to be used by this class. */
public KeyStore getKeyStore() {
return KeyStore.getInstance();
}
public INetd getNetd() {
return NetdService.getInstance();
}
public INetworkManagementService getINetworkManagementService() {
return INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
}
}
public VpnManagerService(Context context, Dependencies deps) {
mContext = context;
mDeps = deps;
mHandlerThread = mDeps.makeHandlerThread();
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
mKeyStore = mDeps.getKeyStore();
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
mCm = mContext.getSystemService(ConnectivityManager.class);
mNMS = mDeps.getINetworkManagementService();
mNetd = mDeps.getNetd();
mUserManager = mContext.getSystemService(UserManager.class);
registerReceivers();
log("VpnManagerService starting up");
}
/** Creates a new VpnManagerService */
public static VpnManagerService create(Context context) {
return new VpnManagerService(context, new Dependencies());
}
/** Informs the service that the system is ready. */
public void systemReady() {
// Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
// for user to unlock device too.
updateLockdownVpn();
}
@Override
/** Dumps service state. */
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("VPNs:");
pw.increaseIndent();
synchronized (mVpns) {
for (int i = 0; i < mVpns.size(); i++) {
pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
}
pw.decreaseIndent();
}
}
/**
* 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 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 VpnManager. 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);
}
}
@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;
}
/**
* Stores the given VPN profile based on the provisioning package name.
*
* <p>If there is already a VPN profile stored for the provisioning package, this call will
* overwrite the profile.
*
* <p>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
*
* <p>If there are no profiles for the given package, this method will silently succeed.
*
* <p>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
*
* <p>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.
*
* <p>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 = mCm.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();
}
}
/**
* 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;
}
}
}
private boolean isLockdownVpnEnabled() {
return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
}
@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
mLockdownEnabled = isLockdownVpnEnabled();
if (!mLockdownEnabled) {
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.
*
* <p>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<String> lockdownAllowlist) {
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, lockdownAllowlist, 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<String> getVpnLockdownAllowlist(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();
}
}
@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 void registerReceivers() {
// Set up the listener for user state for creating user VPNs.
// 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.registerReceiver(
mIntentReceiver,
intentFilter,
null /* broadcastPermission */,
mHandler);
mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
mUserPresentReceiver,
new IntentFilter(Intent.ACTION_USER_PRESENT),
null /* broadcastPermission */,
mHandler /* 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);
}
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnHandlerThread();
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)) {
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 {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnHandlerThread();
// 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 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);
}
}
@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();
}
}
private void onUserAdded(int 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) {
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();
}
}
@Override
public void factoryReset() {
enforceSettingsPermission();
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)
|| mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
return;
}
// Remove always-on package
final int userId = UserHandle.getCallingUserId();
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);
}
}
}
}
private void ensureRunningOnHandlerThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
"Not running on VpnManagerService thread: "
+ Thread.currentThread().getName());
}
}
private void enforceControlAlwaysOnVpnPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
"VpnManagerService");
}
/**
* 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,
"VpnManagerService");
}
private void enforceSettingsPermission() {
enforceAnyPermissionOf(mContext,
android.Manifest.permission.NETWORK_SETTINGS,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private static void log(String s) {
Log.d(TAG, s);
}
private static void logw(String s) {
Log.w(TAG, s);
}
private static void loge(String s) {
Log.e(TAG, s);
}
}

View File

@@ -79,7 +79,6 @@ public class NetworkNotificationManager {
// server.
public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS";
public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS";
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
// The context is for the current user (system server)
private final Context mContext;

View File

@@ -21,10 +21,10 @@ import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
import android.Manifest;
import android.annotation.NonNull;
@@ -172,6 +172,12 @@ public class Vpn {
*/
@VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB
/**
* Network score that VPNs will announce to ConnectivityService.
* TODO: remove when the network scoring refactor lands.
*/
private static final int VPN_DEFAULT_SCORE = 101;
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -496,6 +502,11 @@ public class Vpn {
updateAlwaysOnNotification(detailedState);
}
private void resetNetworkCapabilities() {
mNetworkCapabilities.setUids(null);
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
}
/**
* Chooses whether to force all connections to go though VPN.
*
@@ -520,6 +531,11 @@ public class Vpn {
}
}
/** Returns the package name that is currently prepared. */
public String getPackage() {
return mPackage;
}
/**
* Check whether to prevent all traffic outside of a VPN even when the VPN is not connected.
*
@@ -930,8 +946,7 @@ public class Vpn {
agentDisconnect();
jniReset(mInterface);
mInterface = null;
mNetworkCapabilities.setUids(null);
mNetworkCapabilities.setTransportInfo(null);
resetNetworkCapabilities();
}
// Revoke the connection or stop the VpnRunner.
@@ -1229,8 +1244,7 @@ public class Vpn {
}
mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
@@ -1744,8 +1758,7 @@ public class Vpn {
private void cleanupVpnStateLocked() {
mStatusIntent = null;
mNetworkCapabilities.setUids(null);
mNetworkCapabilities.setTransportInfo(null);
resetNetworkCapabilities();
mConfig = null;
mInterface = null;

View File

@@ -17,9 +17,10 @@
package com.android.server.net;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static android.provider.Settings.ACTION_VPN_SETTINGS;
import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,22 +29,22 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
import android.os.Handler;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.server.ConnectivityService;
import com.android.server.EventLogTags;
import com.android.server.connectivity.Vpn;
@@ -51,9 +52,8 @@ import java.util.List;
import java.util.Objects;
/**
* State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
* connected and kicks off VPN connection, managing any required {@code netd}
* firewall rules.
* State tracker for legacy lockdown VPN. Watches for physical networks to be
* connected and kicks off VPN connection.
*/
public class LockdownVpnTracker {
private static final String TAG = "LockdownVpnTracker";
@@ -64,7 +64,7 @@ public class LockdownVpnTracker {
public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
@NonNull private final Context mContext;
@NonNull private final ConnectivityService mConnService;
@NonNull private final ConnectivityManager mCm;
@NonNull private final NotificationManager mNotificationManager;
@NonNull private final Handler mHandler;
@NonNull private final Vpn mVpn;
@@ -76,19 +76,73 @@ public class LockdownVpnTracker {
@NonNull private final PendingIntent mConfigIntent;
@NonNull private final PendingIntent mResetIntent;
@NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback();
@NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback();
private class NetworkCallback extends ConnectivityManager.NetworkCallback {
private Network mNetwork = null;
private LinkProperties mLinkProperties = null;
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
boolean networkChanged = false;
if (!network.equals(mNetwork)) {
// The default network just changed.
mNetwork = network;
networkChanged = true;
}
mLinkProperties = lp;
// Backwards compatibility: previously, LockdownVpnTracker only responded to connects
// and disconnects, not LinkProperties changes on existing networks.
if (networkChanged) {
synchronized (mStateLock) {
handleStateChangedLocked();
}
}
}
public void onLost(Network network) {
// The default network has gone down.
mNetwork = null;
mLinkProperties = null;
synchronized (mStateLock) {
handleStateChangedLocked();
}
}
public Network getNetwork() {
return mNetwork;
}
public LinkProperties getLinkProperties() {
return mLinkProperties;
}
}
private class VpnNetworkCallback extends NetworkCallback {
@Override
public void onAvailable(Network network) {
synchronized (mStateLock) {
handleStateChangedLocked();
}
}
@Override
public void onLost(Network network) {
onAvailable(network);
}
}
@Nullable
private String mAcceptedEgressIface;
private int mErrorCount;
public LockdownVpnTracker(@NonNull Context context,
@NonNull ConnectivityService connService,
@NonNull Handler handler,
@NonNull KeyStore keyStore,
@NonNull Vpn vpn,
@NonNull VpnProfile profile) {
mContext = Objects.requireNonNull(context);
mConnService = Objects.requireNonNull(connService);
mCm = mContext.getSystemService(ConnectivityManager.class);
mHandler = Objects.requireNonNull(handler);
mVpn = Objects.requireNonNull(vpn);
mProfile = Objects.requireNonNull(profile);
@@ -110,16 +164,15 @@ public class LockdownVpnTracker {
* connection when ready, or setting firewall rules once VPN is connected.
*/
private void handleStateChangedLocked() {
final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
final LinkProperties egressProp = mConnService.getActiveLinkProperties();
final Network network = mDefaultNetworkCallback.getNetwork();
final NetworkInfo egressInfo = mCm.getNetworkInfo(network); // Only for logging
final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
// Restart VPN when egress network disconnected or changed
final boolean egressDisconnected = egressInfo == null
|| State.DISCONNECTED.equals(egressInfo.getState());
final boolean egressDisconnected = (network == null);
final boolean egressChanged = egressProp == null
|| !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
@@ -137,35 +190,53 @@ public class LockdownVpnTracker {
hideNotification();
return;
}
if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
EventLogTags.writeLockdownVpnError(egressType);
}
if (mErrorCount > MAX_ERROR_COUNT) {
// Cannot happen because ConnectivityService never sees a NetworkInfo in state FAILED.
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
return;
}
} else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
if (mProfile.isValidLockdownProfile()) {
Log.d(TAG, "Active network connected; starting VPN");
EventLogTags.writeLockdownVpnConnecting(egressType);
showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
mAcceptedEgressIface = egressProp.getInterfaceName();
try {
// Use the privileged method because Lockdown VPN is initiated by the system, so
// no additional permission checks are necessary.
mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
} catch (IllegalStateException e) {
mAcceptedEgressIface = null;
Log.e(TAG, "Failed to start VPN", e);
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
}
} else {
// At this point, |network| is known to be non-null.
if (!vpnInfo.isConnectedOrConnecting()) {
if (!mProfile.isValidLockdownProfile()) {
Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
return;
}
Log.d(TAG, "Active network connected; starting VPN");
EventLogTags.writeLockdownVpnConnecting(egressType);
showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
mAcceptedEgressIface = egressIface;
try {
// Use the privileged method because Lockdown VPN is initiated by the system, so
// no additional permission checks are necessary.
//
// Pass in the underlying network here because the legacy VPN is, in fact, tightly
// coupled to a given underlying network and cannot provide mobility. This makes
// things marginally more correct in two ways:
//
// 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra
// CONNECTED broadcast for the underlying network type. The underlying type comes
// from here. LTT *could* assume that the underlying network is the default
// network, but that might introduce a race condition if, say, the VPN starts
// connecting on cell, but when the connection succeeds and the agent is
// registered, the default network is now wifi.
// 2. If no underlying network is passed in, then CS will assume the underlying
// network is the system default. So, if the VPN is up and underlying network
// (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
// changed to match the new default network (e.g., cell).
mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
} catch (IllegalStateException e) {
mAcceptedEgressIface = null;
Log.e(TAG, "Failed to start VPN", e);
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
}
} else if (vpnInfo.isConnected() && vpnConfig != null) {
final String iface = vpnConfig.interfaze;
final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
@@ -174,10 +245,6 @@ public class LockdownVpnTracker {
+ ", sourceAddr=" + sourceAddrs.toString());
EventLogTags.writeLockdownVpnConnected(egressType);
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
final NetworkInfo clone = new NetworkInfo(egressInfo);
augmentNetworkInfo(clone);
mConnService.sendConnectedBroadcast(clone);
}
}
@@ -192,7 +259,15 @@ public class LockdownVpnTracker {
mVpn.setEnableTeardown(false);
mVpn.setLockdown(true);
mCm.setLegacyLockdownVpnEnabled(true);
handleStateChangedLocked();
mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
final NetworkRequest vpnRequest = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(TRANSPORT_VPN)
.build();
mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler);
}
public void shutdown() {
@@ -209,16 +284,18 @@ public class LockdownVpnTracker {
mVpn.stopVpnRunnerPrivileged();
mVpn.setLockdown(false);
mCm.setLegacyLockdownVpnEnabled(false);
hideNotification();
mVpn.setEnableTeardown(true);
mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
mCm.unregisterNetworkCallback(mVpnNetworkCallback);
}
/**
* Reset VPN lockdown tracker. Called by ConnectivityService when receiving
* {@link #ACTION_LOCKDOWN_RESET} pending intent.
*/
@GuardedBy("mConnService.mVpns")
public void reset() {
Log.d(TAG, "reset()");
synchronized (mStateLock) {
@@ -229,28 +306,6 @@ public class LockdownVpnTracker {
}
}
public void onNetworkInfoChanged() {
synchronized (mStateLock) {
handleStateChangedLocked();
}
}
public void onVpnStateChanged(NetworkInfo info) {
if (info.getDetailedState() == DetailedState.FAILED) {
mErrorCount++;
}
synchronized (mStateLock) {
handleStateChangedLocked();
}
}
public void augmentNetworkInfo(NetworkInfo info) {
if (info.isConnected()) {
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
}
}
private void showNotification(int titleRes, int iconRes) {
final Notification.Builder builder =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN)

View File

@@ -1295,6 +1295,7 @@ public final class SystemServer implements Dumpable {
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
IpSecService ipSecService = null;
VpnManagerService vpnManager = null;
VcnManagementService vcnManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
@@ -1883,6 +1884,15 @@ public final class SystemServer implements Dumpable {
networkPolicy.bindConnectivityManager(connectivity);
t.traceEnd();
t.traceBegin("StartVpnManagerService");
try {
vpnManager = VpnManagerService.create(context);
ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
} catch (Throwable e) {
reportWtf("starting VPN Manager Service", e);
}
t.traceEnd();
t.traceBegin("StartVcnManagementService");
try {
vcnManagement = VcnManagementService.create(context);
@@ -2611,6 +2621,7 @@ public final class SystemServer implements Dumpable {
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
final IpSecService ipSecServiceF = ipSecService;
final VpnManagerService vpnManagerF = vpnManager;
final VcnManagementService vcnManagementF = vcnManagement;
final WindowManagerService windowManagerF = wm;
final ConnectivityManager connectivityF = (ConnectivityManager)
@@ -2725,6 +2736,15 @@ public final class SystemServer implements Dumpable {
reportWtf("making Connectivity Service ready", e);
}
t.traceEnd();
t.traceBegin("MakeVpnManagerServiceReady");
try {
if (vpnManagerF != null) {
vpnManagerF.systemReady();
}
} catch (Throwable e) {
reportWtf("making VpnManagerService ready", e);
}
t.traceEnd();
t.traceBegin("MakeVcnManagementServiceReady");
try {
if (vcnManagementF != null) {

View File

@@ -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 {

View File

@@ -17,7 +17,6 @@
package android.net;
import static com.android.testutils.ParcelUtils.assertParcelSane;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -36,7 +35,6 @@ public class VpnTransportInfoTest {
public void testParceling() {
VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
assertParcelSane(v, 1 /* fieldCount */);
assertParcelingIsLossless(v);
}
@Test

View File

@@ -200,6 +200,7 @@ import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
@@ -376,6 +377,7 @@ public class ConnectivityServiceTest {
private MockContext mServiceContext;
private HandlerThread mCsHandlerThread;
private HandlerThread mVMSHandlerThread;
private ConnectivityService.Dependencies mDeps;
private ConnectivityService mService;
private WrappedConnectivityManager mCm;
@@ -390,6 +392,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;
@@ -1262,24 +1265,57 @@ 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) {
assertNotNull(nc);
final TransportInfo ti = nc.getTransportInfo();
assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti,
ti instanceof VpnTransportInfo);
assertEquals(type, ((VpnTransportInfo) ti).type);
}
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,
@@ -1403,6 +1439,7 @@ public class ConnectivityServiceTest {
initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
mCsHandlerThread = new HandlerThread("TestConnectivityService");
mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
mDeps = makeDependencies();
returnRealCallingUid();
mService = new ConnectivityService(mServiceContext,
@@ -1425,6 +1462,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);
@@ -1452,7 +1491,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));
@@ -3873,6 +3911,24 @@ public class ConnectivityServiceTest {
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
@Test
public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false /* validated */);
final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
final TestNetworkCallback callback = new TestNetworkCallback();
assertThrows(SecurityException.class,
() -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
callback.assertNoCallback();
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
PERMISSION_GRANTED);
mCm.registerSystemDefaultNetworkCallback(callback, handler);
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
mCm.unregisterNetworkCallback(callback);
}
private void setCaptivePortalMode(int mode) {
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
@@ -6484,6 +6540,8 @@ public class ConnectivityServiceTest {
assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
}
private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
@@ -6523,6 +6581,7 @@ public class ConnectivityServiceTest {
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
// A VPN without underlying networks is not suspended.
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
final int userId = UserHandle.getUserId(Process.myUid());
assertDefaultNetworkCapabilities(userId /* no networks */);
@@ -6686,6 +6745,7 @@ public class ConnectivityServiceTest {
// By default, VPN is set to track default network (i.e. its underlying networks is null).
// In case of no default network, VPN is considered metered.
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
// Connect to Cell; Cell is the default network.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -6743,6 +6803,7 @@ public class ConnectivityServiceTest {
NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertNotNull("nc=" + nc, nc.getUids());
assertEquals(nc.getUids(), uidRangesForUid(uid));
assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
// Set an underlying network and expect to see the VPN transports change.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6825,8 +6886,8 @@ public class ConnectivityServiceTest {
// Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
final ArrayList<String> 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.
@@ -6856,7 +6917,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();
}
@@ -7232,7 +7294,8 @@ public class ConnectivityServiceTest {
final int uid = Process.myUid();
final int userId = UserHandle.getUserId(uid);
final ArrayList<String> 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);
@@ -7254,7 +7317,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();
@@ -7267,7 +7330,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();
@@ -7300,11 +7364,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);
@@ -7317,7 +7382,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();
@@ -7329,7 +7394,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();
@@ -7340,7 +7406,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();
@@ -7352,7 +7418,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();
@@ -7398,11 +7465,14 @@ public class ConnectivityServiceTest {
when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
}
private void establishLegacyLockdownVpn() throws Exception {
private void establishLegacyLockdownVpn(Network underlying) throws Exception {
// The legacy lockdown VPN only supports userId 0, and must have an underlying network.
assertNotNull(underlying);
mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
// The legacy lockdown VPN only supports userId 0.
final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
mMockVpn.registerAgent(ranges);
mMockVpn.setUnderlyingNetworks(new Network[]{underlying});
mMockVpn.connect(true);
}
@@ -7410,6 +7480,9 @@ public class ConnectivityServiceTest {
public void testLegacyLockdownVpn() throws Exception {
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
// For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
mServiceContext.setPermission(
Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7418,6 +7491,10 @@ public class ConnectivityServiceTest {
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultCallback);
final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
new Handler(ConnectivityThread.getInstanceLooper()));
// Pretend lockdown VPN was configured.
setupLegacyLockdownVpn();
@@ -7447,6 +7524,7 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
waitForIdle();
assertNull(mMockVpn.getAgent());
@@ -7458,6 +7536,8 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.sendLinkProperties(cellLp);
callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
waitForIdle();
assertNull(mMockVpn.getAgent());
@@ -7467,6 +7547,7 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
b1.expectBroadcast();
// When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
@@ -7476,6 +7557,7 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
b1.expectBroadcast();
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7498,9 +7580,10 @@ public class ConnectivityServiceTest {
mMockVpn.expectStartLegacyVpnRunner();
b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
establishLegacyLockdownVpn();
establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
callback.expectAvailableThenValidatedCallbacks(mMockVpn);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
systemDefaultCallback.assertNoCallback();
NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
b1.expectBroadcast();
b2.expectBroadcast();
@@ -7512,9 +7595,7 @@ public class ConnectivityServiceTest {
assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo();
assertNotNull(ti);
assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type);
assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY);
// Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
final LinkProperties wifiLp = new LinkProperties();
@@ -7542,11 +7623,10 @@ public class ConnectivityServiceTest {
// fact that a VPN is connected should only result in the VPN itself being unblocked, not
// any other network. Bug in isUidBlockedByVpn?
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
callback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// While the VPN is reconnecting on the new network, everything is blocked.
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7557,9 +7637,10 @@ public class ConnectivityServiceTest {
// The VPN comes up again on wifi.
b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
establishLegacyLockdownVpn();
establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
callback.expectAvailableThenValidatedCallbacks(mMockVpn);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
systemDefaultCallback.assertNoCallback();
b1.expectBroadcast();
b2.expectBroadcast();
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7573,14 +7654,10 @@ public class ConnectivityServiceTest {
assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect cell. Nothing much happens since it's not the default network.
// Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
// NetworkInfo is updated. This is probably a bug.
// TODO: consider fixing this.
b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
mCellNetworkAgent.disconnect();
b1.expectBroadcast();
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultCallback.assertNoCallback();
systemDefaultCallback.assertNoCallback();
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -7590,6 +7667,7 @@ public class ConnectivityServiceTest {
b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
b1.expectBroadcast();
callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);