Add tests for always-on VPN lockdown mode.

This requires mocking lots of new things that weren't mocked
before but is otherwise fairly straightforward.

A few changes to MockVpn are needed as well:

1. Set the VPN's NetworkInfo to CONNECTED, so methods such as
   isBlockingUid will work. While I'm at it, set the interface on
   the LinkProperties as well to make things a bit more
   realistic.

2. Constructs the VpnConfig when registering the agent, not when
   the MockVpn is created. This is needed because starting and
   stopping lockdown VPN calls prepare, which nulls out mConfig.
   But constructing the VpnConfig when registering the agent is
   more realistic anyway. The production code does that in
   establish, but we can't do that in ConnectivityServiceTest
   because some of the test cases don't call establish and call
   registerAgent directly.

Bug: 173331190
Test: atest FrameworksNetTests
Change-Id: I827543751dbf5e626a24ec02cd6f50b423f5f761
This commit is contained in:
Lorenzo Colitti
2020-11-30 16:02:54 +09:00
parent f278ef3b02
commit 36e91a3a0b
2 changed files with 246 additions and 18 deletions

View File

@@ -39,6 +39,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
@@ -101,6 +102,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -132,6 +135,7 @@ import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -140,6 +144,8 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.LocationManager;
@@ -176,6 +182,7 @@ import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
@@ -332,7 +339,7 @@ public class ConnectivityServiceTest {
private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
private static final String VPN_IFNAME = "tun10042";
private static final String TEST_PACKAGE_NAME = "com.android.test.package";
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
private static final String INTERFACE_NAME = "interface";
@@ -353,6 +360,7 @@ public class ConnectivityServiceTest {
@Mock IIpConnectivityMetrics mIpConnectivityMetrics;
@Mock IpConnectivityMetrics.Logger mMetricsService;
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
@Mock DeviceIdleInternal mDeviceIdleInternal;
@Mock INetworkManagementService mNetworkManagementService;
@Mock INetworkStatsService mStatsService;
@Mock IBatteryStats mBatteryStatsService;
@@ -449,6 +457,15 @@ public class ConnectivityServiceTest {
} catch (InterruptedException e) {}
}
@Override
public ComponentName startService(Intent service) {
final String action = service.getAction();
if (!VpnConfig.SERVICE_INTERFACE.equals(action)) {
fail("Attempt to start unknown service, action=" + action);
}
return new ComponentName(service.getPackage(), "com.android.test.Service");
}
@Override
public Object getSystemService(String name) {
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
@@ -1055,9 +1072,19 @@ public class ConnectivityServiceTest {
private VpnInfo mVpnInfo;
public MockVpn(int userId) {
super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
mMockNetd, userId, mock(KeyStore.class));
mConfig = new VpnConfig();
super(startHandlerThreadAndReturnLooper(), mServiceContext,
new Dependencies() {
@Override
public boolean isCallerSystem() {
return true;
}
@Override
public DeviceIdleInternal getDeviceIdleInternal() {
return mDeviceIdleInternal;
}
},
mNetworkManagementService, mMockNetd, userId, mock(KeyStore.class));
}
public void setUids(Set<UidRange> uids) {
@@ -1086,9 +1113,16 @@ public class ConnectivityServiceTest {
return mVpnType;
}
private LinkProperties makeLinkProperties() {
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(VPN_IFNAME);
return lp;
}
private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
throws Exception {
if (mAgentRegistered) throw new IllegalStateException("already registered");
mConfig = new VpnConfig();
setUids(uids);
if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
mInterface = VPN_IFNAME;
@@ -1101,12 +1135,13 @@ public class ConnectivityServiceTest {
verify(mMockNetd, never())
.networkRemoveUidRanges(eq(mMockVpn.getNetId()), any());
mAgentRegistered = true;
updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
}
private void registerAgent(Set<UidRange> uids) throws Exception {
registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties());
registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties());
}
private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
@@ -1142,12 +1177,12 @@ public class ConnectivityServiceTest {
public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
throws Exception {
final int uid = Process.myUid();
establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
establish(makeLinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
isStrictMode);
}
public void establishForMyUid() throws Exception {
establishForMyUid(new LinkProperties());
establishForMyUid(makeLinkProperties());
}
public void sendLinkProperties(LinkProperties lp) {
@@ -1155,7 +1190,10 @@ public class ConnectivityServiceTest {
}
public void disconnect() {
if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect();
if (mMockNetworkAgent != null) {
mMockNetworkAgent.disconnect();
updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect");
}
mAgentRegistered = false;
}
@@ -1376,13 +1414,13 @@ public class ConnectivityServiceTest {
}
private void mockDefaultPackages() throws Exception {
final String testPackageName = mContext.getPackageName();
final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
testPackageName, PackageManager.GET_PERMISSIONS);
final String myPackageName = mContext.getPackageName();
final PackageInfo myPackageInfo = mContext.getPackageManager().getPackageInfo(
myPackageName, PackageManager.GET_PERMISSIONS);
when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
new String[] {testPackageName});
when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
new String[] {myPackageName});
when(mPackageManager.getPackageInfoAsUser(eq(myPackageName), anyInt(),
eq(UserHandle.getCallingUserId()))).thenReturn(myPackageInfo);
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
Arrays.asList(new PackageInfo[] {
@@ -1390,6 +1428,25 @@ public class ConnectivityServiceTest {
buildPackageInfo(/* SYSTEM */ false, APP2_UID),
buildPackageInfo(/* SYSTEM */ false, VPN_UID)
}));
// Create a fake always-on VPN package.
final int userId = UserHandle.getCallingUserId();
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; // Always-on supported in N+.
when(mPackageManager.getApplicationInfoAsUser(eq(ALWAYS_ON_PACKAGE), anyInt(),
eq(userId))).thenReturn(applicationInfo);
// Minimal mocking to keep Vpn#isAlwaysOnPackageSupported happy.
ResolveInfo rInfo = new ResolveInfo();
rInfo.serviceInfo = new ServiceInfo();
rInfo.serviceInfo.metaData = new Bundle();
final List<ResolveInfo> services = Arrays.asList(new ResolveInfo[]{rInfo});
when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
eq(userId))).thenReturn(services);
when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId))
.thenReturn(Process.myUid());
when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, userId))
.thenReturn(VPN_UID);
}
private void verifyActiveNetwork(int transport) {
@@ -2252,10 +2309,10 @@ public class ConnectivityServiceTest {
}
private void grantUsingBackgroundNetworksPermissionForUid(final int uid) throws Exception {
final String testPackageName = mContext.getPackageName();
when(mPackageManager.getPackageInfo(eq(testPackageName), eq(GET_PERMISSIONS)))
final String myPackageName = mContext.getPackageName();
when(mPackageManager.getPackageInfo(eq(myPackageName), eq(GET_PERMISSIONS)))
.thenReturn(buildPackageInfo(true, uid));
mService.mPermissionMonitor.onPackageAdded(testPackageName, uid);
mService.mPermissionMonitor.onPackageAdded(myPackageName, uid);
}
@Test
@@ -6389,6 +6446,173 @@ public class ConnectivityServiceTest {
mCm.unregisterNetworkCallback(defaultCallback);
}
private void expectNetworkRejectNonSecureVpn(InOrder inOrder, boolean add,
UidRangeParcel... expected) throws Exception {
inOrder.verify(mMockNetd).networkRejectNonSecureVpn(eq(add), aryEq(expected));
}
private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) {
assertNotNull(ni);
assertEquals(type, ni.getType());
assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState());
}
private void assertActiveNetworkInfo(int type, DetailedState state) {
checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state);
}
private void assertNetworkInfo(int type, DetailedState state) {
checkNetworkInfo(mCm.getNetworkInfo(type), type, state);
}
@Test
public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception {
mServiceContext.setPermission(
Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
mServiceContext.setPermission(
Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback callback = new TestNetworkCallback();
final NetworkRequest request = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN)
.build();
mCm.registerNetworkCallback(request, callback);
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultCallback);
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);
UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
InOrder inOrder = inOrder(mMockNetd);
expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
// Connect a network when lockdown is active, expect to see it blocked.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Mobile is BLOCKED even though it's not actually connected.
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown, expect to see the network unblocked.
// There are no callbacks because they are not implemented yet.
mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// 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);
defaultCallback.assertNoCallback();
// The following requires that the UID of this test package is greater than VPN_UID. This
// is always true in practice because a plain AOSP build with no apps installed has almost
// 200 packages installed.
final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// Connect a new network, expect it to be unblocked.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false /* validated */);
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
defaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// Cellular is DISCONNECTED because it's not the default and there are no requests for it.
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
// Everything should now be blocked.
mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
allowList.clear();
mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown. Everything is unblocked.
mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
// Enable and disable an always-on VPN package without lockdown. Expect no changes.
reset(mMockNetd);
mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
callback.assertNoCallback();
defaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
callback.assertNoCallback();
defaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
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);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
mMockVpn.establishForMyUid();
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
assertEquals(null, mCm.getActiveNetworkForUid(VPN_UID)); // BUG?
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
mMockVpn.disconnect();
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
assertNull(mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
}
@Test
public final void testLoseTrusted() throws Exception {
final NetworkRequest trustedRequest = new NetworkRequest.Builder()

View File

@@ -228,7 +228,6 @@ public class VpnTest {
R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
.thenReturn(true);
when(mSystemServices.isCallerSystem()).thenReturn(true);
// Used by {@link Notification.Builder}
ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -1101,6 +1100,11 @@ public class VpnTest {
}
}
@Override
public boolean isCallerSystem() {
return true;
}
@Override
public void startService(final String serviceName) {
mRunningServices.put(serviceName, true);