From b84176e9f9ff93ba6340190d4f8bbc50a34bc990 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Sat, 10 Apr 2021 00:01:14 +0900 Subject: [PATCH] Test ConnectivityManager module-lib APIs for VPNs and blocking. This CL adds tests for: - ConnectivityManager#setsetLegacyLockdownVpnEnabled - ConnectivityManager#setRequireVpnForUids - NetworkCallback#onBlockedStatusChanged(Network, int) Bug: 165835257 Test: atest CtsNetTestCases:android.net.cts.ConnectivityManagerTest Change-Id: Ie9b73ec2a1634f3b3a3eb4d21acbe0803b77c70d --- .../net/cts/ConnectivityManagerTest.java | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index 18f05888b4..d67eb236f1 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -28,6 +28,8 @@ import static android.content.pm.PackageManager.FEATURE_WIFI; import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE_CBS; @@ -114,6 +116,7 @@ import android.os.MessageQueue; import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.VintfRuntimeInfo; import android.platform.test.annotations.AppModeFull; import android.provider.Settings; @@ -122,6 +125,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Pair; +import android.util.Range; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -166,6 +170,7 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -192,6 +197,7 @@ public class ConnectivityManagerTest { private static final int MIN_KEEPALIVE_INTERVAL = 10; private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000; + private static final int NO_CALLBACK_TIMEOUT_MS = 100; private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20; private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500; // device could have only one interface: data, wifi. @@ -224,6 +230,9 @@ public class ConnectivityManagerTest { private UiAutomation mUiAutomation; private CtsNetUtils mCtsNetUtils; + // Used for cleanup purposes. + private final List> mVpnRequiredUidRanges = new ArrayList<>(); + @Before public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); @@ -304,6 +313,12 @@ public class ConnectivityManagerTest { mCtsNetUtils.disconnectFromCell(); } + if (shouldTestSApis()) { + runWithShellPermissionIdentity( + () -> mCmShim.setRequireVpnForUids(false, mVpnRequiredUidRanges), + NETWORK_SETTINGS); + } + // All tests in this class require a working Internet connection as they start. Make // sure there is still one as they end that's ready to use for the next test to use. final TestNetworkCallback callback = new TestNetworkCallback(); @@ -1705,7 +1720,7 @@ public class ConnectivityManagerTest { */ @Test @IgnoreUpTo(Build.VERSION_CODES.R) - public void testRequestBackgroundNetwork() throws Exception { + public void testRequestBackgroundNetwork() { // Create a tun interface. Use the returned interface name as the specifier to create // a test network request. final TestNetworkManager tnm = runWithShellPermissionIdentity(() -> @@ -1778,6 +1793,111 @@ public class ConnectivityManagerTest { } } + private class DetailedBlockedStatusCallback extends TestableNetworkCallback { + public void expectAvailableCallbacks(Network network) { + super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */, + BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS); + } + public void expectBlockedStatusCallback(Network network, int blockedStatus) { + super.expectBlockedStatusCallback(blockedStatus, network, NETWORK_CALLBACK_TIMEOUT_MS); + } + public void onBlockedStatusChanged(Network network, int blockedReasons) { + getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons)); + } + } + + private void setRequireVpnForUids(boolean requireVpn, Collection> ranges) + throws Exception { + mCmShim.setRequireVpnForUids(requireVpn, ranges); + for (Range range : ranges) { + if (requireVpn) { + mVpnRequiredUidRanges.add(range); + } else { + assertTrue(mVpnRequiredUidRanges.remove(range)); + } + } + } + + private void doTestBlockedStatusCallback() throws Exception { + final DetailedBlockedStatusCallback myUidCallback = new DetailedBlockedStatusCallback(); + final DetailedBlockedStatusCallback otherUidCallback = new DetailedBlockedStatusCallback(); + + final int myUid = Process.myUid(); + final int otherUid = UserHandle.of(5).getUid(Process.FIRST_APPLICATION_UID); + final Handler handler = new Handler(Looper.getMainLooper()); + mCm.registerDefaultNetworkCallback(myUidCallback, handler); + mCmShim.registerDefaultNetworkCallbackAsUid(otherUid, otherUidCallback, handler); + + final Network defaultNetwork = mCm.getActiveNetwork(); + final List allCallbacks = + List.of(myUidCallback, otherUidCallback); + for (DetailedBlockedStatusCallback callback : allCallbacks) { + callback.expectAvailableCallbacks(defaultNetwork); + } + + final Range myUidRange = new Range<>(myUid, myUid); + final Range otherUidRange = new Range<>(otherUid, otherUid); + + setRequireVpnForUids(true, List.of(myUidRange)); + myUidCallback.expectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_LOCKDOWN_VPN); + otherUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + + setRequireVpnForUids(true, List.of(myUidRange, otherUidRange)); + myUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + otherUidCallback.expectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_LOCKDOWN_VPN); + + // setRequireVpnForUids does no deduplication or refcounting. Removing myUidRange does not + // unblock myUid because it was added to the blocked ranges twice. + setRequireVpnForUids(false, List.of(myUidRange)); + myUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + otherUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + + setRequireVpnForUids(false, List.of(myUidRange, otherUidRange)); + myUidCallback.expectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE); + otherUidCallback.expectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE); + + myUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + otherUidCallback.assertNoCallback(NO_CALLBACK_TIMEOUT_MS); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testBlockedStatusCallback() { + runWithShellPermissionIdentity(() -> doTestBlockedStatusCallback(), NETWORK_SETTINGS); + } + + private void doTestLegacyLockdownEnabled() throws Exception { + NetworkInfo info = mCm.getActiveNetworkInfo(); + assertNotNull(info); + assertEquals(DetailedState.CONNECTED, info.getDetailedState()); + + try { + mCmShim.setLegacyLockdownVpnEnabled(true); + + // setLegacyLockdownVpnEnabled is asynchronous and only takes effect when the + // ConnectivityService handler thread processes it. Ensure it has taken effect by doing + // something that blocks until the handler thread is idle. + final TestableNetworkCallback callback = new TestableNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + waitForAvailable(callback); + mCm.unregisterNetworkCallback(callback); + + // Test one of the effects of setLegacyLockdownVpnEnabled: the fact that any NetworkInfo + // in state CONNECTED is degraded to CONNECTING if the legacy VPN is not connected. + info = mCm.getActiveNetworkInfo(); + assertNotNull(info); + assertEquals(DetailedState.CONNECTING, info.getDetailedState()); + } finally { + mCmShim.setLegacyLockdownVpnEnabled(false); + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testLegacyLockdownEnabled() { + runWithShellPermissionIdentity(() -> doTestLegacyLockdownEnabled(), NETWORK_SETTINGS); + } + /** * Whether to test S+ APIs. This requires a) that the test be running on an S+ device, and * b) that the code be compiled against shims new enough to access these APIs.