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:
@@ -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
|
||||
|
||||
62
core/java/android/net/IVpnManager.aidl
Normal file
62
core/java/android/net/IVpnManager.aidl
Normal 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();
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
918
services/core/java/com/android/server/VpnManagerService.java
Normal file
918
services/core/java/com/android/server/VpnManagerService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user