From caf797ef34475f49742e61ac2b0439f0f2c0b6ff Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Wed, 5 Feb 2020 11:02:02 -0800 Subject: [PATCH 1/7] Add CTS tests for ConnectivityDiagnostics callbacks. Verify that the callbacks onConnectivityReport() and onNetworkConnectivityReported() are invoked by the System when expected. ConnectivityDiagnosticsManager provides an API for registering callbacks with the System. These callbacks allow the System to notify registered and permissioned callbacks on Network validation, suspected data stalls, and Network connectivity reported. Bug: 148032944 Test: android.net.cts.ConnectivityDiagnosticsManagerTest Change-Id: I748229d41c16adf1561e03aa597d5aac00f12912 Merged-In: I748229d41c16adf1561e03aa597d5aac00f12912 (cherry picked from commit fa23ec3b252c33e4cdc3e1463d77ba279d7da144) --- .../ConnectivityDiagnosticsManagerTest.java | 232 +++++++++++++++++- 1 file changed, 221 insertions(+), 11 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java index 9d357055d1..6687fdcbfb 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java @@ -17,19 +17,49 @@ package android.net.cts; import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.cts.util.CtsNetUtils.TestNetworkCallback; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; + +import static org.junit.Assert.assertEquals; +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 android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityDiagnosticsManager; +import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.TestNetworkInterface; +import android.net.TestNetworkManager; +import android.os.Binder; import android.os.Build; +import android.os.IBinder; +import android.os.PersistableBundle; +import android.os.Process; +import android.util.Pair; import androidx.test.InstrumentationRegistry; +import com.android.testutils.ArrayTrackRecord; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,33 +69,71 @@ import java.util.concurrent.Executor; @RunWith(DevSdkIgnoreRunner.class) @IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q public class ConnectivityDiagnosticsManagerTest { + private static final int CALLBACK_TIMEOUT_MILLIS = 5000; + private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500; + private static final Executor INLINE_EXECUTOR = x -> x.run(); - private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build(); + + private static final NetworkRequest TEST_NETWORK_REQUEST = + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .removeCapability(NET_CAPABILITY_TRUSTED) + .removeCapability(NET_CAPABILITY_NOT_VPN) + .build(); + + // Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests + // for it. + private static final TestNetworkCallback TEST_NETWORK_CALLBACK = new TestNetworkCallback(); + + private static final IBinder BINDER = new Binder(); private Context mContext; + private ConnectivityManager mConnectivityManager; private ConnectivityDiagnosticsManager mCdm; - private ConnectivityDiagnosticsCallback mCallback; + private Network mTestNetwork; @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getContext(); + mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class); - mCallback = new ConnectivityDiagnosticsCallback() {}; + mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, TEST_NETWORK_CALLBACK); + } + + @After + public void tearDown() throws Exception { + mConnectivityManager.unregisterNetworkCallback(TEST_NETWORK_CALLBACK); + + if (mTestNetwork != null) { + runWithShellPermissionIdentity(() -> { + final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class); + tnm.teardownTestNetwork(mTestNetwork); + }); + } } @Test - public void testRegisterConnectivityDiagnosticsCallback() { - mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback); + public void testRegisterConnectivityDiagnosticsCallback() throws Exception { + mTestNetwork = setUpTestNetwork(); + + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); + + final String interfaceName = + mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName(); + + cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + cb.assertNoCallback(); } @Test public void testRegisterDuplicateConnectivityDiagnosticsCallback() { - mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback); + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); try { - mCdm.registerConnectivityDiagnosticsCallback( - DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); fail("Registering the same callback twice should throw an IllegalArgumentException"); } catch (IllegalArgumentException expected) { } @@ -73,13 +141,155 @@ public class ConnectivityDiagnosticsManagerTest { @Test public void testUnregisterConnectivityDiagnosticsCallback() { - mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback); - mCdm.unregisterConnectivityDiagnosticsCallback(mCallback); + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); + mCdm.unregisterConnectivityDiagnosticsCallback(cb); } @Test public void testUnregisterUnknownConnectivityDiagnosticsCallback() { // Expected to silently ignore the unregister() call - mCdm.unregisterConnectivityDiagnosticsCallback(mCallback); + mCdm.unregisterConnectivityDiagnosticsCallback(new TestConnectivityDiagnosticsCallback()); + } + + @Test + public void testOnConnectivityReportAvailable() throws Exception { + mTestNetwork = setUpTestNetwork(); + + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); + + final String interfaceName = + mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName(); + + cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + cb.assertNoCallback(); + } + + @Test + public void testOnNetworkConnectivityReportedTrue() throws Exception { + verifyOnNetworkConnectivityReported(true /* hasConnectivity */); + } + + @Test + public void testOnNetworkConnectivityReportedFalse() throws Exception { + verifyOnNetworkConnectivityReported(false /* hasConnectivity */); + } + + private void verifyOnNetworkConnectivityReported(boolean hasConnectivity) throws Exception { + mTestNetwork = setUpTestNetwork(); + + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); + + // onConnectivityReportAvailable always invoked when the test network is established + final String interfaceName = + mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName(); + cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + cb.assertNoCallback(); + + mConnectivityManager.reportNetworkConnectivity(mTestNetwork, hasConnectivity); + + cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity); + + // if hasConnectivity does not match the network's known connectivity, it will be + // revalidated which will trigger another onConnectivityReportAvailable callback. + if (!hasConnectivity) { + cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + } + + cb.assertNoCallback(); + } + + @NonNull + private Network waitForConnectivityServiceIdleAndGetNetwork() throws InterruptedException { + // Get a new Network. This requires going through the ConnectivityService thread. Once it + // completes, all previously enqueued messages on the ConnectivityService main Handler have + // completed. + final TestNetworkCallback callback = new TestNetworkCallback(); + mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, callback); + final Network network = callback.waitForAvailable(); + mConnectivityManager.unregisterNetworkCallback(callback); + assertNotNull(network); + return network; + } + + /** + * Registers a test NetworkAgent with ConnectivityService with limited capabilities, which leads + * to the Network being validated. + */ + @NonNull + private Network setUpTestNetwork() throws Exception { + final int[] administratorUids = new int[] {Process.myUid()}; + runWithShellPermissionIdentity( + () -> { + final TestNetworkManager tnm = + mContext.getSystemService(TestNetworkManager.class); + final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]); + tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER); + }); + return waitForConnectivityServiceIdleAndGetNetwork(); + } + + private static class TestConnectivityDiagnosticsCallback + extends ConnectivityDiagnosticsCallback { + private final ArrayTrackRecord.ReadHead mHistory = + new ArrayTrackRecord().newReadHead(); + + @Override + public void onConnectivityReportAvailable(ConnectivityReport report) { + mHistory.add(report); + } + + @Override + public void onDataStallSuspected(DataStallReport report) { + mHistory.add(report); + } + + @Override + public void onNetworkConnectivityReported(Network network, boolean hasConnectivity) { + mHistory.add(new Pair(network, hasConnectivity)); + } + + public void expectOnConnectivityReportAvailable( + @NonNull Network network, @NonNull String interfaceName) { + final ConnectivityReport result = + (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true); + assertEquals(network, result.getNetwork()); + + final NetworkCapabilities nc = result.getNetworkCapabilities(); + assertNotNull(nc); + assertTrue(nc.hasTransport(TRANSPORT_TEST)); + assertNotNull(result.getLinkProperties()); + assertEquals(interfaceName, result.getLinkProperties().getInterfaceName()); + + final PersistableBundle extras = result.getAdditionalInfo(); + assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT)); + final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT); + assertEquals("Network validation result is not 'valid'", + NETWORK_VALIDATION_RESULT_VALID, validationResult); + + assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK)); + final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT); + assertTrue("PROBES_SUCCEEDED mask not in expected range", probesSucceeded >= 0); + + assertTrue(extras.containsKey(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK)); + final int probesAttempted = extras.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK); + assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0); + } + + public void expectOnNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) { + final Pair result = + (Pair) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true); + assertEquals(network, result.first /* network */); + assertEquals(hasConnectivity, result.second /* hasConnectivity */); + } + + public void assertNoCallback() { + // If no more callbacks exist, there should be nothing left in the ReadHead + assertNull("Unexpected event in history", + mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true)); + } } } From a4107118ff89217a2ffad42537797b9248daff89 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Wed, 15 Apr 2020 13:43:45 -0700 Subject: [PATCH 2/7] Add CTS testing for ConnectivityDiagnostics Data Stall callback. Verify that onDataStallSuspected() is invoked by the System when expected. ConnectivityDiagnosticsManager provides an API for registering callbacks with the System. These callbacks allow the System to notify registered and permissioned callbacks on Network validation, suspected data stalls, and Network connectivity reported. Bug: 148032944 Test: atest ConnectivityDiagnosticsManagerTest Change-Id: If6ceae9d2bbcabf88298d2d8c39cad5275fbd1ef Merged-In: If6ceae9d2bbcabf88298d2d8c39cad5275fbd1ef (cherry picked from commit dfcee1ac4a2843884034623601deb78b1a2d84df) --- .../ConnectivityDiagnosticsManagerTest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java index 6687fdcbfb..8cacb4351d 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java @@ -23,6 +23,12 @@ import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_ import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID; import static android.net.ConnectivityDiagnosticsManager.DataStallReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_TCP_METRICS; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE; +import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; import static android.net.NetworkCapabilities.TRANSPORT_TEST; @@ -71,6 +77,10 @@ import java.util.concurrent.Executor; public class ConnectivityDiagnosticsManagerTest { private static final int CALLBACK_TIMEOUT_MILLIS = 5000; private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500; + private static final long TIMESTAMP = 123456789L; + private static final int DNS_CONSECUTIVE_TIMEOUTS = 5; + private static final int COLLECTION_PERIOD_MILLIS = 5000; + private static final int FAIL_RATE_PERCENTAGE = 100; private static final Executor INLINE_EXECUTOR = x -> x.run(); @@ -166,6 +176,46 @@ public class ConnectivityDiagnosticsManagerTest { cb.assertNoCallback(); } + @Test + public void testOnDataStallSuspected_DnsEvents() throws Exception { + final PersistableBundle extras = new PersistableBundle(); + extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, DNS_CONSECUTIVE_TIMEOUTS); + + verifyOnDataStallSuspected(DETECTION_METHOD_DNS_EVENTS, TIMESTAMP, extras); + } + + @Test + public void testOnDataStallSuspected_TcpMetrics() throws Exception { + final PersistableBundle extras = new PersistableBundle(); + extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, COLLECTION_PERIOD_MILLIS); + extras.putInt(KEY_TCP_PACKET_FAIL_RATE, FAIL_RATE_PERCENTAGE); + + verifyOnDataStallSuspected(DETECTION_METHOD_TCP_METRICS, TIMESTAMP, extras); + } + + private void verifyOnDataStallSuspected( + int detectionMethod, long timestampMillis, @NonNull PersistableBundle extras) + throws Exception { + mTestNetwork = setUpTestNetwork(); + + final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); + mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb); + + final String interfaceName = + mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName(); + + cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + + runWithShellPermissionIdentity( + () -> mConnectivityManager.simulateDataStall( + detectionMethod, timestampMillis, mTestNetwork, extras), + android.Manifest.permission.MANAGE_TEST_NETWORKS); + + cb.expectOnDataStallSuspected( + mTestNetwork, interfaceName, detectionMethod, timestampMillis, extras); + cb.assertNoCallback(); + } + @Test public void testOnNetworkConnectivityReportedTrue() throws Exception { verifyOnNetworkConnectivityReported(true /* hasConnectivity */); @@ -278,6 +328,27 @@ public class ConnectivityDiagnosticsManagerTest { assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0); } + public void expectOnDataStallSuspected( + @NonNull Network network, + @NonNull String interfaceName, + int detectionMethod, + long timestampMillis, + @NonNull PersistableBundle extras) { + final DataStallReport result = + (DataStallReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true); + assertEquals(network, result.getNetwork()); + assertEquals(detectionMethod, result.getDetectionMethod()); + assertEquals(timestampMillis, result.getReportTimestamp()); + + final NetworkCapabilities nc = result.getNetworkCapabilities(); + assertNotNull(nc); + assertTrue(nc.hasTransport(TRANSPORT_TEST)); + assertNotNull(result.getLinkProperties()); + assertEquals(interfaceName, result.getLinkProperties().getInterfaceName()); + + assertTrue(persistableBundleEquals(extras, result.getStallDetails())); + } + public void expectOnNetworkConnectivityReported( @NonNull Network network, boolean hasConnectivity) { final Pair result = From 6609fbbfe194cd81c1d409cac13c94b49ee6a902 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Fri, 15 May 2020 11:27:22 -0700 Subject: [PATCH 3/7] Test Data Stall with unknown detection type. This CL adds a CTS test for Data Stall events to ConnectivityDiagnostics. This makes sure that new DataStall detection methods are passed to ConnectivityDiagnostics callbacks with the appropriate detection method bit mask. Bug: 156294356 Bug: 148032944 Test: atest ConnectivityDiagnosticsManagerTest Change-Id: Id6f1bff59b08192f09ebcc4578a3c233fd1c2768 Merged-In: Id6f1bff59b08192f09ebcc4578a3c233fd1c2768 (cherry picked from commit 03278ff4593ba697acc97097d458d752f33228b3) --- .../ConnectivityDiagnosticsManagerTest.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java index 8cacb4351d..0248f971dc 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java @@ -81,6 +81,8 @@ public class ConnectivityDiagnosticsManagerTest { private static final int DNS_CONSECUTIVE_TIMEOUTS = 5; private static final int COLLECTION_PERIOD_MILLIS = 5000; private static final int FAIL_RATE_PERCENTAGE = 100; + private static final int UNKNOWN_DETECTION_METHOD = 4; + private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0; private static final Executor INLINE_EXECUTOR = x -> x.run(); @@ -193,9 +195,28 @@ public class ConnectivityDiagnosticsManagerTest { verifyOnDataStallSuspected(DETECTION_METHOD_TCP_METRICS, TIMESTAMP, extras); } + @Test + public void testOnDataStallSuspected_UnknownDetectionMethod() throws Exception { + verifyOnDataStallSuspected( + UNKNOWN_DETECTION_METHOD, + FILTERED_UNKNOWN_DETECTION_METHOD, + TIMESTAMP, + PersistableBundle.EMPTY); + } + private void verifyOnDataStallSuspected( int detectionMethod, long timestampMillis, @NonNull PersistableBundle extras) throws Exception { + // Input detection method is expected to match received detection method + verifyOnDataStallSuspected(detectionMethod, detectionMethod, timestampMillis, extras); + } + + private void verifyOnDataStallSuspected( + int inputDetectionMethod, + int expectedDetectionMethod, + long timestampMillis, + @NonNull PersistableBundle extras) + throws Exception { mTestNetwork = setUpTestNetwork(); final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback(); @@ -208,11 +229,11 @@ public class ConnectivityDiagnosticsManagerTest { runWithShellPermissionIdentity( () -> mConnectivityManager.simulateDataStall( - detectionMethod, timestampMillis, mTestNetwork, extras), + inputDetectionMethod, timestampMillis, mTestNetwork, extras), android.Manifest.permission.MANAGE_TEST_NETWORKS); cb.expectOnDataStallSuspected( - mTestNetwork, interfaceName, detectionMethod, timestampMillis, extras); + mTestNetwork, interfaceName, expectedDetectionMethod, timestampMillis, extras); cb.assertNoCallback(); } From fec419cdd6e511525e25802395992c39b00f0c1e Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 19 May 2020 21:01:32 -0700 Subject: [PATCH 4/7] Extract IPsec and test network utility methods This patch moves some test setup functions to util classes in preparation for IKEv2 VPN tests which will use those same utilities. Bug: 148582947 Test: atest IpSecManagerTunnelTest; passing Change-Id: I9aeafa45ab515ce72a72c3de6f70fb26e32e7fd4 Merged-In: I9aeafa45ab515ce72a72c3de6f70fb26e32e7fd4 (cherry picked from commit 30432fa7640603c1e746b7d8c83e2e6052d8f967) --- .../net/cts/IpSecManagerTunnelTest.java | 137 ++++++------------ .../net/src/android/net/cts/PacketUtils.java | 14 ++ .../android/net/cts/util/CtsNetUtils.java | 54 +++++++ 3 files changed, 110 insertions(+), 95 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java index 1d83dda33c..ae38faa124 100644 --- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java +++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java @@ -18,20 +18,17 @@ package android.net.cts; import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS; import static android.net.IpSecManager.UdpEncapsulationSocket; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE; import static android.net.cts.PacketUtils.AES_CBC_IV_LEN; import static android.net.cts.PacketUtils.BytePayload; import static android.net.cts.PacketUtils.EspHeader; import static android.net.cts.PacketUtils.IP4_HDRLEN; import static android.net.cts.PacketUtils.IP6_HDRLEN; -import static android.net.cts.PacketUtils.Ip4Header; -import static android.net.cts.PacketUtils.Ip6Header; import static android.net.cts.PacketUtils.IpHeader; import static android.net.cts.PacketUtils.UDP_HDRLEN; import static android.net.cts.PacketUtils.UdpHeader; +import static android.net.cts.PacketUtils.getIpHeader; +import static android.net.cts.util.CtsNetUtils.TestNetworkCallback; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; @@ -40,38 +37,28 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; -import android.app.AppOpsManager; import android.content.Context; -import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.IpSecAlgorithm; import android.net.IpSecManager; import android.net.IpSecTransform; import android.net.LinkAddress; import android.net.Network; -import android.net.NetworkRequest; import android.net.TestNetworkInterface; import android.net.TestNetworkManager; import android.net.cts.PacketUtils.Payload; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; +import android.net.cts.util.CtsNetUtils; import android.os.ParcelFileDescriptor; -import android.os.SystemProperties; import android.platform.test.annotations.AppModeFull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.compatibility.common.util.SystemUtil; - -import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import org.junit.AfterClass; import org.junit.Before; @@ -114,7 +101,7 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { private static TunUtils sTunUtils; private static Context sContext = InstrumentationRegistry.getContext(); - private static IBinder sBinder = new Binder(); + private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext); @BeforeClass public static void setUpBeforeClass() throws Exception { @@ -127,7 +114,7 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and // a standard permission is insufficient. So we shell out the appop, to give us the // right appop permissions. - setAppop(OP_MANAGE_IPSEC_TUNNELS, true); + mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true); TestNetworkInterface testIface = sTNM.createTunInterface( @@ -137,8 +124,9 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { }); sTunFd = testIface.getFileDescriptor(); - sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName()); - sTunNetwork = sTunNetworkCallback.getNetworkBlocking(); + sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName()); + sTunNetworkCallback.waitForAvailable(); + sTunNetwork = sTunNetworkCallback.currentNetwork; sTunUtils = new TunUtils(sTunFd); } @@ -149,7 +137,7 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { super.setUp(); // Set to true before every run; some tests flip this. - setAppop(OP_MANAGE_IPSEC_TUNNELS, true); + mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true); // Clear sTunUtils state sTunUtils.reset(); @@ -157,7 +145,7 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { @AfterClass public static void tearDownAfterClass() throws Exception { - setAppop(OP_MANAGE_IPSEC_TUNNELS, false); + mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false); sCM.unregisterNetworkCallback(sTunNetworkCallback); @@ -169,50 +157,12 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { .dropShellPermissionIdentity(); } - private static boolean hasTunnelsFeature() { - return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS) - || SystemProperties.getInt("ro.product.first_api_level", 0) - >= Build.VERSION_CODES.Q; - } - - private static void setAppop(int appop, boolean allow) { - String opName = AppOpsManager.opToName(appop); - for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) { - String cmd = - String.format( - "appops set %s %s %s", - pkg, // Package name - opName, // Appop - (allow ? "allow" : "deny")); // Action - SystemUtil.runShellCommand(cmd); - } - } - - private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception { - // Build a network request - NetworkRequest nr = - new NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(TRANSPORT_TEST) - .setNetworkSpecifier(ifname) - .build(); - - TestNetworkCallback cb = new TestNetworkCallback(); - sCM.requestNetwork(nr, cb); - - // Setup the test network after network request is filed to prevent Network from being - // reaped due to no requests matching it. - sTNM.setupTestNetwork(ifname, sBinder); - - return cb; - } - @Test public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception { - if (!hasTunnelsFeature()) return; + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); // Ensure we don't have the appop. Permission is not requested in the Manifest - setAppop(OP_MANAGE_IPSEC_TUNNELS, false); + mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false); // Security exceptions are thrown regardless of IPv4/IPv6. Just test one try { @@ -224,10 +174,10 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { @Test public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception { - if (!hasTunnelsFeature()) return; + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); // Ensure we don't have the appop. Permission is not requested in the Manifest - setAppop(OP_MANAGE_IPSEC_TUNNELS, false); + mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false); // Security exceptions are thrown regardless of IPv4/IPv6. Just test one try (IpSecManager.SecurityParameterIndex spi = @@ -253,19 +203,6 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { public abstract int run(Network ipsecNetwork) throws Exception; } - private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback { - private final CompletableFuture futureNetwork = new CompletableFuture<>(); - - @Override - public void onAvailable(Network network) { - futureNetwork.complete(network); - } - - public Network getNetworkBlocking() throws Exception { - return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } - } - private int getPacketSize( int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) { int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN; @@ -499,8 +436,6 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { public void checkTunnelReflected( int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) throws Exception { - if (!hasTunnelsFeature()) return; - InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6; InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6; @@ -580,7 +515,6 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { boolean transportInTunnelMode, IpSecTunnelTestRunnableFactory factory) throws Exception { - if (!hasTunnelsFeature()) return; InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6; InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6; @@ -648,8 +582,9 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) { // Build the test network tunnelIface.addAddress(localInner, innerPrefixLen); - testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName()); - Network testNetwork = testNetworkCb.getNetworkBlocking(); + testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName()); + testNetworkCb.waitForAvailable(); + Network testNetwork = testNetworkCb.currentNetwork; // Check interface was created assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName())); @@ -718,18 +653,6 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { } } - private IpHeader getIpHeader(int protocol, InetAddress src, InetAddress dst, Payload payload) { - if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) { - throw new IllegalArgumentException("Invalid src/dst address combination"); - } - - if (src instanceof Inet6Address) { - return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload); - } else { - return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload); - } - } - private EspHeader buildTransportModeEspPacket( int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception { IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload); @@ -819,134 +742,158 @@ public class IpSecManagerTunnelTest extends IpSecBaseTest { // Transport-in-Tunnel mode tests @Test public void testTransportInTunnelModeV4InV4() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET, false, true); checkTunnelInput(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV4InV4Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET, true, true); checkTunnelInput(AF_INET, AF_INET, true, true); } @Test public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV4InV6() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET6, false, true); checkTunnelInput(AF_INET, AF_INET6, false, true); } @Test public void testTransportInTunnelModeV4InV6Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV6InV4() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET6, AF_INET, false, true); checkTunnelInput(AF_INET6, AF_INET, false, true); } @Test public void testTransportInTunnelModeV6InV4Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET6, AF_INET, true, true); checkTunnelInput(AF_INET6, AF_INET, true, true); } @Test public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } @Test public void testTransportInTunnelModeV6InV6() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET6, false, true); checkTunnelInput(AF_INET, AF_INET6, false, true); } @Test public void testTransportInTunnelModeV6InV6Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, true); } // Tunnel mode tests @Test public void testTunnelV4InV4() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET, false, false); checkTunnelInput(AF_INET, AF_INET, false, false); } @Test public void testTunnelV4InV4Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, false, false); } @Test public void testTunnelV4InV4UdpEncap() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET, true, false); checkTunnelInput(AF_INET, AF_INET, true, false); } @Test public void testTunnelV4InV4UdpEncapReflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET, true, false); } @Test public void testTunnelV4InV6() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET, AF_INET6, false, false); checkTunnelInput(AF_INET, AF_INET6, false, false); } @Test public void testTunnelV4InV6Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET, AF_INET6, false, false); } @Test public void testTunnelV6InV4() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET6, AF_INET, false, false); checkTunnelInput(AF_INET6, AF_INET, false, false); } @Test public void testTunnelV6InV4Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET6, AF_INET, false, false); } @Test public void testTunnelV6InV4UdpEncap() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET6, AF_INET, true, false); checkTunnelInput(AF_INET6, AF_INET, true, false); } @Test public void testTunnelV6InV4UdpEncapReflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET6, AF_INET, true, false); } @Test public void testTunnelV6InV6() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelOutput(AF_INET6, AF_INET6, false, false); checkTunnelInput(AF_INET6, AF_INET6, false, false); } @Test public void testTunnelV6InV6Reflected() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); checkTunnelReflected(AF_INET6, AF_INET6, false, false); } } diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java index 6177827ba6..0aedecb5ad 100644 --- a/tests/cts/net/src/android/net/cts/PacketUtils.java +++ b/tests/cts/net/src/android/net/cts/PacketUtils.java @@ -27,6 +27,7 @@ import java.nio.ShortBuffer; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.Arrays; + import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; @@ -443,6 +444,19 @@ public class PacketUtils { return Arrays.copyOfRange(buffer.array(), 0, buffer.position()); } + public static IpHeader getIpHeader( + int protocol, InetAddress src, InetAddress dst, Payload payload) { + if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) { + throw new IllegalArgumentException("Invalid src/dst address combination"); + } + + if (src instanceof Inet6Address) { + return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload); + } else { + return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload); + } + } + /* * Debug printing */ diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java index f39b184914..32cdf92144 100644 --- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java +++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,11 +28,13 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.annotation.NonNull; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.LinkProperties; @@ -40,7 +43,12 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.net.NetworkRequest; +import android.net.TestNetworkManager; import android.net.wifi.WifiManager; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.os.SystemProperties; import android.provider.Settings; import android.system.Os; import android.system.OsConstants; @@ -73,6 +81,7 @@ public final class CtsNetUtils { public static final String NETWORK_CALLBACK_ACTION = "ConnectivityManagerTest.NetworkCallbackAction"; + private final IBinder mBinder = new Binder(); private final Context mContext; private final ConnectivityManager mCm; private final ContentResolver mCR; @@ -88,6 +97,51 @@ public final class CtsNetUtils { mCR = context.getContentResolver(); } + /** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */ + public boolean hasIpsecTunnelsFeature() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + || SystemProperties.getInt("ro.product.first_api_level", 0) + >= Build.VERSION_CODES.Q; + } + + /** + * Sets the given appop using shell commands + * + *

Expects caller to hold the shell permission identity. + */ + public void setAppopPrivileged(int appop, boolean allow) { + final String opName = AppOpsManager.opToName(appop); + for (final String pkg : new String[] {"com.android.shell", mContext.getPackageName()}) { + final String cmd = + String.format( + "appops set %s %s %s", + pkg, // Package name + opName, // Appop + (allow ? "allow" : "deny")); // Action + SystemUtil.runShellCommand(cmd); + } + } + + /** Sets up a test network using the provided interface name */ + public TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception { + // Build a network request + final NetworkRequest nr = + new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(ifname) + .build(); + + final TestNetworkCallback cb = new TestNetworkCallback(); + mCm.requestNetwork(nr, cb); + + // Setup the test network after network request is filed to prevent Network from being + // reaped due to no requests matching it. + mContext.getSystemService(TestNetworkManager.class).setupTestNetwork(ifname, mBinder); + + return cb; + } + // Toggle WiFi twice, leaving it in the state it started in public void toggleWifi() { if (mWifiManager.isWifiEnabled()) { From bfc3ee8559a109c7f90a03468564dca07c5398f7 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Tue, 21 Apr 2020 17:01:28 -0700 Subject: [PATCH 5/7] Add basic tests for IKEv2/IPsec VPNs This change adds basic tests for all IKEv2/IPsec VPN public APIs. Additional testing for ensuring IKEv2 setup completes will be done in a subsequent CL. Bug: 148582947 Test: Ikev2VpnTest added Change-Id: Ia5d35c32525b32be4a0dc0584630f5bb9e7f1bcb Merged-In: Ia5d35c32525b32be4a0dc0584630f5bb9e7f1bcb (cherry picked from commit 12f571feaea736de875aebef8ad0cc70630ad6ab) --- .../net/src/android/net/cts/Ikev2VpnTest.java | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 tests/cts/net/src/android/net/cts/Ikev2VpnTest.java diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java new file mode 100644 index 0000000000..8c1cbbb6d9 --- /dev/null +++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java @@ -0,0 +1,357 @@ +/* + * 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.cts; + +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import android.Manifest; +import android.annotation.NonNull; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Ikev2VpnProfile; +import android.net.IpSecAlgorithm; +import android.net.ProxyInfo; +import android.net.VpnManager; +import android.net.cts.util.CtsNetUtils; +import android.platform.test.annotations.AppModeFull; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.org.bouncycastle.x509.X509V1CertificateGenerator; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +@RunWith(AndroidJUnit4.class) +@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)") +public class Ikev2VpnTest { + private static final String TAG = Ikev2VpnTest.class.getSimpleName(); + + private static final String TEST_SERVER_ADDR = "2001:db8::1"; + private static final String TEST_IDENTITY = "client.cts.android.com"; + private static final List TEST_ALLOWED_ALGORITHMS = + Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); + + private static final ProxyInfo TEST_PROXY_INFO = + ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234); + private static final int TEST_MTU = 1300; + + private static final byte[] TEST_PSK = "ikev2".getBytes(); + private static final String TEST_USER = "username"; + private static final String TEST_PASSWORD = "pa55w0rd"; + + // Static state to reduce setup/teardown + private static final Context sContext = InstrumentationRegistry.getContext(); + private static final ConnectivityManager sCM = + (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE); + private static final VpnManager sVpnMgr = + (VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE); + private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext); + + private final X509Certificate mServerRootCa; + private final CertificateAndKey mUserCertKey; + + public Ikev2VpnTest() throws Exception { + // Build certificates + mServerRootCa = generateRandomCertAndKeyPair().cert; + mUserCertKey = generateRandomCertAndKeyPair(); + } + + /** + * Sets the given appop using shell commands + * + *

This method must NEVER be called from within a shell permission, as it will attempt to + * acquire, and then drop the shell permission identity. This results in the caller losing the + * shell permission identity due to these calls not being reference counted. + */ + public void setAppop(int appop, boolean allow) { + // Requires shell permission to update appops. + runWithShellPermissionIdentity(() -> { + mCtsNetUtils.setAppopPrivileged(appop, allow); + }, Manifest.permission.MANAGE_TEST_NETWORKS); + } + + private Ikev2VpnProfile buildIkev2VpnProfileCommon( + Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception { + if (isRestrictedToTestNetworks) { + builder.restrictToTestNetworks(); + } + + return builder.setBypassable(true) + .setProxy(TEST_PROXY_INFO) + .setMaxMtu(TEST_MTU) + .setMetered(false) + .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS) + .build(); + } + + private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks) + throws Exception { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY).setAuthPsk(TEST_PSK); + + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + } + + private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks) + throws Exception { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY) + .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa); + + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + } + + private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks) + throws Exception { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY) + .setAuthDigitalSignature( + mUserCertKey.cert, mUserCertKey.key, mServerRootCa); + + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + } + + private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception { + assertEquals(TEST_SERVER_ADDR, profile.getServerAddr()); + assertEquals(TEST_IDENTITY, profile.getUserIdentity()); + assertEquals(TEST_PROXY_INFO, profile.getProxyInfo()); + assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms()); + assertTrue(profile.isBypassable()); + assertFalse(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + assertFalse(profile.isRestrictedToTestNetworks()); + } + + @Test + public void testBuildIkev2VpnProfilePsk() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + + checkBasicIkev2VpnProfile(profile); + assertArrayEquals(TEST_PSK, profile.getPresharedKey()); + + // Verify nothing else is set. + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildIkev2VpnProfileUsernamePassword() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfileUsernamePassword(false /* isRestrictedToTestNetworks */); + + checkBasicIkev2VpnProfile(profile); + assertEquals(TEST_USER, profile.getUsername()); + assertEquals(TEST_PASSWORD, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + // Verify nothing else is set. + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildIkev2VpnProfileDigitalSignature() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfileDigitalSignature(false /* isRestrictedToTestNetworks */); + + checkBasicIkev2VpnProfile(profile); + assertEquals(mUserCertKey.cert, profile.getUserCert()); + assertEquals(mUserCertKey.key, profile.getRsaPrivateKey()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + // Verify nothing else is set. + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getPresharedKey()); + } + + private void verifyProvisionVpnProfile( + boolean hasActivateVpn, boolean hasActivatePlatformVpn, boolean expectIntent) + throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn); + setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + final Intent intent = sVpnMgr.provisionVpnProfile(profile); + assertEquals(expectIntent, intent != null); + } + + @Test + public void testProvisionVpnProfileNoPreviousConsent() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + verifyProvisionVpnProfile(false /* hasActivateVpn */, + false /* hasActivatePlatformVpn */, true /* expectIntent */); + } + + @Test + public void testProvisionVpnProfilePlatformVpnConsented() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + verifyProvisionVpnProfile(false /* hasActivateVpn */, + true /* hasActivatePlatformVpn */, false /* expectIntent */); + } + + @Test + public void testProvisionVpnProfileVpnServiceConsented() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + verifyProvisionVpnProfile(true /* hasActivateVpn */, + false /* hasActivatePlatformVpn */, false /* expectIntent */); + } + + @Test + public void testProvisionVpnProfileAllPreConsented() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + verifyProvisionVpnProfile(true /* hasActivateVpn */, + true /* hasActivatePlatformVpn */, false /* expectIntent */); + } + + @Test + public void testDeleteVpnProfile() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + assertNull(sVpnMgr.provisionVpnProfile(profile)); + + // Verify that deleting the profile works (even without the appop) + setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false); + sVpnMgr.deleteProvisionedVpnProfile(); + + // Test that the profile was deleted - starting it should throw an IAE. + try { + setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); + sVpnMgr.startProvisionedVpnProfile(); + fail("Expected IllegalArgumentException due to missing profile"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testStartVpnProfileNoPreviousConsent() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + setAppop(AppOpsManager.OP_ACTIVATE_VPN, false); + setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false); + + // Make sure the VpnProfile is not provisioned already. + sVpnMgr.stopProvisionedVpnProfile(); + + try { + sVpnMgr.startProvisionedVpnProfile(); + fail("Expected SecurityException for missing consent"); + } catch (SecurityException expected) { + } + } + + @Test + public void testStartStopVpnProfile() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile. + runWithShellPermissionIdentity(() -> { + mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfilePsk(true /* isRestrictedToTestNetworks */); + assertNull(sVpnMgr.provisionVpnProfile(profile)); + + sVpnMgr.startProvisionedVpnProfile(); + // TODO: When IKEv2 setup is injectable, verify network was set up properly. + + sVpnMgr.stopProvisionedVpnProfile(); + // TODO: When IKEv2 setup is injectable, verify network is lost. + }, Manifest.permission.MANAGE_TEST_NETWORKS); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} From a68fbd80f6d0d8e27870b31f8a020f1d0de0fdd3 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Mon, 4 May 2020 16:13:54 -0700 Subject: [PATCH 6/7] Add Ikev2VpnTests including IKE negotiation. This commit expands IKEv2 VPN CTS testing to ensure that given a successful IKEv2 negotiation, the VPN network will be correctly set up. Additionally, it verifies that the stopProvisionedVpnProfile will teardown the VPN network. Bug: 148582947 Test: atest CtsNetTestCases:Ikev2VpnTest Change-Id: Ib6635f0068200ac0172515989fbdee5c3d49e231 Merged-In: Ib6635f0068200ac0172515989fbdee5c3d49e231 (cherry picked from commit 0ef85ff5a391fe81fb7d06959566d869f805f8b5) --- .../net/src/android/net/cts/IkeTunUtils.java | 188 ++++++++++++++++++ .../net/src/android/net/cts/Ikev2VpnTest.java | 147 ++++++++++++-- .../cts/net/src/android/net/cts/TunUtils.java | 97 +++++---- 3 files changed, 366 insertions(+), 66 deletions(-) create mode 100644 tests/cts/net/src/android/net/cts/IkeTunUtils.java diff --git a/tests/cts/net/src/android/net/cts/IkeTunUtils.java b/tests/cts/net/src/android/net/cts/IkeTunUtils.java new file mode 100644 index 0000000000..fc25292b27 --- /dev/null +++ b/tests/cts/net/src/android/net/cts/IkeTunUtils.java @@ -0,0 +1,188 @@ +/* + * 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.cts; + +import static android.net.cts.PacketUtils.BytePayload; +import static android.net.cts.PacketUtils.IP4_HDRLEN; +import static android.net.cts.PacketUtils.IP6_HDRLEN; +import static android.net.cts.PacketUtils.IpHeader; +import static android.net.cts.PacketUtils.UDP_HDRLEN; +import static android.net.cts.PacketUtils.UdpHeader; +import static android.net.cts.PacketUtils.getIpHeader; +import static android.system.OsConstants.IPPROTO_UDP; + +import android.os.ParcelFileDescriptor; + +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Arrays; + +// TODO: Merge this with the version in the IPsec module (IKEv2 library) CTS tests. +/** An extension of the TunUtils class with IKE-specific packet handling. */ +public class IkeTunUtils extends TunUtils { + private static final int PORT_LEN = 2; + + private static final byte[] NON_ESP_MARKER = new byte[] {0, 0, 0, 0}; + + private static final int IKE_HEADER_LEN = 28; + private static final int IKE_SPI_LEN = 8; + private static final int IKE_IS_RESP_BYTE_OFFSET = 19; + private static final int IKE_MSG_ID_OFFSET = 20; + private static final int IKE_MSG_ID_LEN = 4; + + public IkeTunUtils(ParcelFileDescriptor tunFd) { + super(tunFd); + } + + /** + * Await an expected IKE request and inject an IKE response. + * + * @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER. + */ + public byte[] awaitReqAndInjectResp(long expectedInitIkeSpi, int expectedMsgId, + boolean encapExpected, byte[] respIkePkt) throws Exception { + final byte[] request = awaitIkePacket(expectedInitIkeSpi, expectedMsgId, encapExpected); + + // Build response header by flipping address and port + final InetAddress srcAddr = getDstAddress(request); + final InetAddress dstAddr = getSrcAddress(request); + final int srcPort = getDstPort(request); + final int dstPort = getSrcPort(request); + + final byte[] response = + buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, encapExpected, respIkePkt); + injectPacket(response); + return request; + } + + private byte[] awaitIkePacket(long expectedInitIkeSpi, int expectedMsgId, boolean expectEncap) + throws Exception { + return super.awaitPacket(pkt -> isIke(pkt, expectedInitIkeSpi, expectedMsgId, expectEncap)); + } + + private static boolean isIke( + byte[] pkt, long expectedInitIkeSpi, int expectedMsgId, boolean encapExpected) { + final int ipProtocolOffset; + final int ikeOffset; + + if (isIpv6(pkt)) { + ipProtocolOffset = IP6_PROTO_OFFSET; + ikeOffset = IP6_HDRLEN + UDP_HDRLEN; + } else { + if (encapExpected && !hasNonEspMarkerv4(pkt)) { + return false; + } + + // Use default IPv4 header length (assuming no options) + final int encapMarkerLen = encapExpected ? NON_ESP_MARKER.length : 0; + ipProtocolOffset = IP4_PROTO_OFFSET; + ikeOffset = IP4_HDRLEN + UDP_HDRLEN + encapMarkerLen; + } + + return pkt[ipProtocolOffset] == IPPROTO_UDP + && areSpiAndMsgIdEqual(pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId); + } + + /** Checks if the provided IPv4 packet has a UDP-encapsulation NON-ESP marker */ + private static boolean hasNonEspMarkerv4(byte[] ipv4Pkt) { + final int nonEspMarkerOffset = IP4_HDRLEN + UDP_HDRLEN; + if (ipv4Pkt.length < nonEspMarkerOffset + NON_ESP_MARKER.length) { + return false; + } + + final byte[] nonEspMarker = Arrays.copyOfRange( + ipv4Pkt, nonEspMarkerOffset, nonEspMarkerOffset + NON_ESP_MARKER.length); + return Arrays.equals(NON_ESP_MARKER, nonEspMarker); + } + + private static boolean areSpiAndMsgIdEqual( + byte[] pkt, int ikeOffset, long expectedIkeInitSpi, int expectedMsgId) { + if (pkt.length <= ikeOffset + IKE_HEADER_LEN) { + return false; + } + + final ByteBuffer buffer = ByteBuffer.wrap(pkt); + final long spi = buffer.getLong(ikeOffset); + final int msgId = buffer.getInt(ikeOffset + IKE_MSG_ID_OFFSET); + + return expectedIkeInitSpi == spi && expectedMsgId == msgId; + } + + private static InetAddress getSrcAddress(byte[] pkt) throws Exception { + return getAddress(pkt, true); + } + + private static InetAddress getDstAddress(byte[] pkt) throws Exception { + return getAddress(pkt, false); + } + + private static InetAddress getAddress(byte[] pkt, boolean getSrcAddr) throws Exception { + final int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN; + final int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET; + final int ipOffset = getSrcAddr ? srcIpOffset : srcIpOffset + ipLen; + + if (pkt.length < ipOffset + ipLen) { + // Should be impossible; getAddress() is only called with a full IKE request including + // the IP and UDP headers. + throw new IllegalArgumentException("Packet was too short to contain IP address"); + } + + return InetAddress.getByAddress(Arrays.copyOfRange(pkt, ipOffset, ipOffset + ipLen)); + } + + private static int getSrcPort(byte[] pkt) throws Exception { + return getPort(pkt, true); + } + + private static int getDstPort(byte[] pkt) throws Exception { + return getPort(pkt, false); + } + + private static int getPort(byte[] pkt, boolean getSrcPort) { + final int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN; + final int portOffset = getSrcPort ? srcPortOffset : srcPortOffset + PORT_LEN; + + if (pkt.length < portOffset + PORT_LEN) { + // Should be impossible; getPort() is only called with a full IKE request including the + // IP and UDP headers. + throw new IllegalArgumentException("Packet was too short to contain port"); + } + + final ByteBuffer buffer = ByteBuffer.wrap(pkt); + return Short.toUnsignedInt(buffer.getShort(portOffset)); + } + + private static byte[] buildIkePacket( + InetAddress srcAddr, + InetAddress dstAddr, + int srcPort, + int dstPort, + boolean useEncap, + byte[] payload) + throws Exception { + // Append non-ESP marker if encap is enabled + if (useEncap) { + final ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER.length + payload.length); + buffer.put(NON_ESP_MARKER); + buffer.put(payload); + payload = buffer.array(); + } + + final UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(payload)); + final IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt); + return ipPkt.getPacketBytes(); + } +} diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java index 8c1cbbb6d9..ebce5135cf 100644 --- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java +++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java @@ -16,11 +16,15 @@ package android.net.cts; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.cts.util.CtsNetUtils.TestNetworkCallback; + import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,7 +38,12 @@ import android.content.Intent; import android.net.ConnectivityManager; import android.net.Ikev2VpnProfile; import android.net.IpSecAlgorithm; +import android.net.LinkAddress; +import android.net.Network; +import android.net.NetworkRequest; import android.net.ProxyInfo; +import android.net.TestNetworkInterface; +import android.net.TestNetworkManager; import android.net.VpnManager; import android.net.cts.util.CtsNetUtils; import android.platform.test.annotations.AppModeFull; @@ -42,12 +51,14 @@ import android.platform.test.annotations.AppModeFull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.HexDump; import com.android.org.bouncycastle.x509.X509V1CertificateGenerator; import org.junit.Test; import org.junit.runner.RunWith; import java.math.BigInteger; +import java.net.InetAddress; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; @@ -64,6 +75,45 @@ import javax.security.auth.x500.X500Principal; public class Ikev2VpnTest { private static final String TAG = Ikev2VpnTest.class.getSimpleName(); + // Test vectors for IKE negotiation in test mode. + private static final String SUCCESSFUL_IKE_INIT_RESP = + "46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000" + + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800" + + "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7" + + "a97fa93555f4be9218c14e7f286bb28c6b4fb13825a420f2ffc165854f200bab37d69c8963d4" + + "0acb831d983163aa50622fd35c182efe882cf54d6106222abcfaa597255d302f1b95ab71c142" + + "c279ea5839a180070bff73f9d03fab815f0d5ee2adec7e409d1e35979f8bd92ffd8aab13d1a0" + + "0657d816643ae767e9ae84d2ccfa2bcce1a50572be8d3748ae4863c41ae90da16271e014270f" + + "77edd5cd2e3299f3ab27d7203f93d770bacf816041cdcecd0f9af249033979da4369cb242dd9" + + "6d172e60513ff3db02de63e50eb7d7f596ada55d7946cad0af0669d1f3e2804846ab3f2a930d" + + "df56f7f025f25c25ada694e6231abbb87ee8cfd072c8481dc0b0f6b083fdc3bd89b080e49feb" + + "0288eef6fdf8a26ee2fc564a11e7385215cf2deaf2a9965638fc279c908ccdf04094988d91a2" + + "464b4a8c0326533aff5119ed79ecbd9d99a218b44f506a5eb09351e67da86698b4c58718db25" + + "d55f426fb4c76471b27a41fbce00777bc233c7f6e842e39146f466826de94f564cad8b92bfbe" + + "87c99c4c7973ec5f1eea8795e7da82819753aa7c4fcfdab77066c56b939330c4b0d354c23f83" + + "ea82fa7a64c4b108f1188379ea0eb4918ee009d804100e6bf118771b9058d42141c847d5ec37" + + "6e5ec591c71fc9dac01063c2bd31f9c783b28bf1182900002430f3d5de3449462b31dd28bc27" + + "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186" + + "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529" + + "0000080000402e290000100000402f00020003000400050000000800004014"; + private static final String SUCCESSFUL_IKE_AUTH_RESP = + "46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e" + + "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4" + + "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592" + + "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069" + + "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1" + + "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281"; + private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16); + + private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1"); + private static final InetAddress LOCAL_OUTER_6 = + InetAddress.parseNumericAddress("2001:db8:1::1"); + + private static final int IP4_PREFIX_LEN = 32; + private static final int IP6_PREFIX_LEN = 128; + + // TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet). + private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2"; private static final String TEST_SERVER_ADDR = "2001:db8::1"; private static final String TEST_IDENTITY = "client.cts.android.com"; private static final List TEST_ALLOWED_ALGORITHMS = @@ -73,7 +123,7 @@ public class Ikev2VpnTest { ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234); private static final int TEST_MTU = 1300; - private static final byte[] TEST_PSK = "ikev2".getBytes(); + private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes(); private static final String TEST_USER = "username"; private static final String TEST_PASSWORD = "pa55w0rd"; @@ -115,17 +165,22 @@ public class Ikev2VpnTest { } return builder.setBypassable(true) + .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS) .setProxy(TEST_PROXY_INFO) .setMaxMtu(TEST_MTU) .setMetered(false) - .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS) .build(); } private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks) throws Exception { + return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR, isRestrictedToTestNetworks); + } + + private Ikev2VpnProfile buildIkev2VpnProfilePsk( + String remote, boolean isRestrictedToTestNetworks) throws Exception { final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY).setAuthPsk(TEST_PSK); + new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK); return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); } @@ -300,24 +355,84 @@ public class Ikev2VpnTest { } } + private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils) throws Exception { + // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile. + mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); + + final Ikev2VpnProfile profile = + buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V4, true /* isRestrictedToTestNetworks */); + assertNull(sVpnMgr.provisionVpnProfile(profile)); + + sVpnMgr.startProvisionedVpnProfile(); + + // Inject IKE negotiation + int expectedMsgId = 0; + tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */, + HexDump.hexStringToByteArray(SUCCESSFUL_IKE_INIT_RESP)); + tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, true /* isEncap */, + HexDump.hexStringToByteArray(SUCCESSFUL_IKE_AUTH_RESP)); + + // Verify the VPN network came up + final NetworkRequest nr = new NetworkRequest.Builder() + .clearCapabilities().addTransportType(TRANSPORT_VPN).build(); + + final TestNetworkCallback cb = new TestNetworkCallback(); + sCM.requestNetwork(nr, cb); + cb.waitForAvailable(); + final Network vpnNetwork = cb.currentNetwork; + assertNotNull(vpnNetwork); + + sVpnMgr.stopProvisionedVpnProfile(); + cb.waitForLost(); + assertEquals(vpnNetwork, cb.lastLostNetwork); + } + + private void doTestStartStopVpnProfile() throws Exception { + // Non-final; these variables ensure we clean up properly after our test if we have + // allocated test network resources + final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class); + TestNetworkInterface testIface = null; + TestNetworkCallback tunNetworkCallback = null; + + try { + // Build underlying test network + testIface = tnm.createTunInterface( + new LinkAddress[] { + new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN), + new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)}); + + // Hold on to this callback to ensure network does not get reaped. + tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName()); + final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor()); + + checkStartStopVpnProfileBuildsNetworks(tunUtils); + } finally { + // Make sure to stop the VPN profile. This is safe to call multiple times. + sVpnMgr.stopProvisionedVpnProfile(); + + if (testIface != null) { + testIface.getFileDescriptor().close(); + } + + if (tunNetworkCallback != null) { + sCM.unregisterNetworkCallback(tunNetworkCallback); + } + + final Network testNetwork = tunNetworkCallback.currentNetwork; + if (testNetwork != null) { + tnm.teardownTestNetwork(testNetwork); + } + } + } + @Test public void testStartStopVpnProfile() throws Exception { assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); - // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile. + // Requires shell permission to update appops. runWithShellPermissionIdentity(() -> { - mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); - - final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(true /* isRestrictedToTestNetworks */); - assertNull(sVpnMgr.provisionVpnProfile(profile)); - - sVpnMgr.startProvisionedVpnProfile(); - // TODO: When IKEv2 setup is injectable, verify network was set up properly. - - sVpnMgr.stopProvisionedVpnProfile(); - // TODO: When IKEv2 setup is injectable, verify network is lost. - }, Manifest.permission.MANAGE_TEST_NETWORKS); + doTestStartStopVpnProfile(); + }); } private static class CertificateAndKey { diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java index a0307137a5..adaba9d398 100644 --- a/tests/cts/net/src/android/net/cts/TunUtils.java +++ b/tests/cts/net/src/android/net/cts/TunUtils.java @@ -21,8 +21,8 @@ import static android.net.cts.PacketUtils.IP6_HDRLEN; import static android.net.cts.PacketUtils.IPPROTO_ESP; import static android.net.cts.PacketUtils.UDP_HDRLEN; import static android.system.OsConstants.IPPROTO_UDP; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import android.os.ParcelFileDescriptor; @@ -39,19 +39,18 @@ import java.util.function.Predicate; public class TunUtils { private static final String TAG = TunUtils.class.getSimpleName(); + protected static final int IP4_ADDR_OFFSET = 12; + protected static final int IP4_ADDR_LEN = 4; + protected static final int IP6_ADDR_OFFSET = 8; + protected static final int IP6_ADDR_LEN = 16; + protected static final int IP4_PROTO_OFFSET = 9; + protected static final int IP6_PROTO_OFFSET = 6; + private static final int DATA_BUFFER_LEN = 4096; - private static final int TIMEOUT = 100; + private static final int TIMEOUT = 1000; - private static final int IP4_PROTO_OFFSET = 9; - private static final int IP6_PROTO_OFFSET = 6; - - private static final int IP4_ADDR_OFFSET = 12; - private static final int IP4_ADDR_LEN = 4; - private static final int IP6_ADDR_OFFSET = 8; - private static final int IP6_ADDR_LEN = 16; - - private final ParcelFileDescriptor mTunFd; private final List mPackets = new ArrayList<>(); + private final ParcelFileDescriptor mTunFd; private final Thread mReaderThread; public TunUtils(ParcelFileDescriptor tunFd) { @@ -112,46 +111,15 @@ public class TunUtils { return null; } - /** - * Checks if the specified bytes were ever sent in plaintext. - * - *

Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like - * - * @param plaintext the plaintext bytes to check for - * @param startIndex the index in the list to check for - */ - public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) { - Predicate verifier = - (pkt) -> { - return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) - != -1; - }; - return getFirstMatchingPacket(verifier, startIndex) != null; - } - - public byte[] getEspPacket(int spi, boolean encap, int startIndex) { - return getFirstMatchingPacket( - (pkt) -> { - return isEsp(pkt, spi, encap); - }, - startIndex); - } - - public byte[] awaitEspPacketNoPlaintext( - int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception { + protected byte[] awaitPacket(Predicate verifier) throws Exception { long endTime = System.currentTimeMillis() + TIMEOUT; int startIndex = 0; synchronized (mPackets) { while (System.currentTimeMillis() < endTime) { - byte[] espPkt = getEspPacket(spi, useEncap, startIndex); - if (espPkt != null) { - // Validate packet size - assertEquals(expectedPacketSize, espPkt.length); - - // Always check plaintext from start - assertFalse(hasPlaintextPacket(plaintext, 0)); - return espPkt; // We've found the packet we're looking for. + final byte[] pkt = getFirstMatchingPacket(verifier, startIndex); + if (pkt != null) { + return pkt; // We've found the packet we're looking for. } startIndex = mPackets.size(); @@ -162,10 +130,21 @@ public class TunUtils { mPackets.wait(waitTimeout); } } - - fail("No such ESP packet found with SPI " + spi); } - return null; + + fail("No packet found matching verifier"); + throw new IllegalStateException("Impossible condition; should have thrown in fail()"); + } + + public byte[] awaitEspPacketNoPlaintext( + int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception { + final byte[] espPkt = awaitPacket( + (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext)); + + // Validate packet size + assertEquals(expectedPacketSize, espPkt.length); + + return espPkt; // We've found the packet we're looking for. } private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) { @@ -176,6 +155,24 @@ public class TunUtils { && pkt[espOffset + 3] == (byte) (spi & 0xff); } + /** + * Variant of isEsp that also fails the test if the provided plaintext is found + * + * @param pkt the packet bytes to verify + * @param spi the expected SPI to look for + * @param encap whether encap was enabled, and the packet has a UDP header + * @param plaintext the plaintext packet before outbound encryption, which MUST not appear in + * the provided packet. + */ + private static boolean isEspFailIfSpecifiedPlaintextFound( + byte[] pkt, int spi, boolean encap, byte[] plaintext) { + if (Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) != -1) { + fail("Banned plaintext packet found"); + } + + return isEsp(pkt, spi, encap); + } + private static boolean isEsp(byte[] pkt, int spi, boolean encap) { if (isIpv6(pkt)) { // IPv6 UDP encap not supported by kernels; assume non-encap. @@ -191,7 +188,7 @@ public class TunUtils { } } - private static boolean isIpv6(byte[] pkt) { + public static boolean isIpv6(byte[] pkt) { // First nibble shows IP version. 0x60 for IPv6 return (pkt[0] & (byte) 0xF0) == (byte) 0x60; } From 1f0bdd40bd360bcb3af85cef6dcffd77a4ffe331 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 20 May 2020 01:13:18 -0700 Subject: [PATCH 7/7] Add IPv6 testing for IKEv2 VPN tests This change adds tests for IPv6 IKEv2 VPN profiles. Bug: 148582947 Test: IPv6 tests passing Change-Id: Ic0f71df739bd9162653b5f2878e7ddc446ddde0e Merged-In: Ic0f71df739bd9162653b5f2878e7ddc446ddde0e (cherry picked from commit e19a04da9d2ff0993af8884888bb4a327c546098) --- .../net/src/android/net/cts/Ikev2VpnTest.java | 81 ++++++++++++++----- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java index ebce5135cf..5cc0cb4128 100644 --- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java +++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java @@ -76,7 +76,7 @@ public class Ikev2VpnTest { private static final String TAG = Ikev2VpnTest.class.getSimpleName(); // Test vectors for IKE negotiation in test mode. - private static final String SUCCESSFUL_IKE_INIT_RESP = + private static final String SUCCESSFUL_IKE_INIT_RESP_V4 = "46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000" + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800" + "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7" @@ -96,25 +96,53 @@ public class Ikev2VpnTest { + "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186" + "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529" + "0000080000402e290000100000402f00020003000400050000000800004014"; - private static final String SUCCESSFUL_IKE_AUTH_RESP = + private static final String SUCCESSFUL_IKE_INIT_RESP_V6 = + "46b8eca1e0d72a1800d9ea1babce26bf2120222000000000000002d0220000300000002c01010004030000" + + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800" + + "100000ea0e6dd9ca5930a9a45c323a41f64bfd8cdef7730f5fbff37d7c377da427f489a42aa8" + + "c89233380e6e925990d49de35c2cdcf63a61302c731a4b3569df1ee1bf2457e55a6751838ede" + + "abb75cc63ba5c9e4355e8e784f383a5efe8a44727dc14aeaf8dacc2620fb1c8875416dc07739" + + "7fe4decc1bd514a9c7d270cf21fd734c63a25c34b30b68686e54e8a198f37f27cb491fe27235" + + "fab5476b036d875ccab9a68d65fbf3006197f9bebbf94de0d3802b4fafe1d48d931ce3a1a346" + + "2d65bd639e9bd7fa46299650a9dbaf9b324e40b466942d91a59f41ef8042f8474c4850ed0f63" + + "e9238949d41cd8bbaea9aefdb65443a6405792839563aa5dc5c36b5ce8326ccf8a94d9622b85" + + "038d390d5fc0299e14e1f022966d4ac66515f6108ca04faec44821fe5bbf2ed4f84ff5671219" + + "608cb4c36b44a31ba010c9088f8d5ff943bb9ff857f74be1755f57a5783874adc57f42bb174e" + + "4ad3215de628707014dbcb1707bd214658118fdd7a42b3e1638b991ce5b812a667f1145be811" + + "685e3cd3baf9b18d062657b64c206a4d19a531c252a6a51a04aeaf42c618620cdbab65baca23" + + "82c57ed888422aeaacf7f1bc3fe2247ff7e7eaca218b74d7b31d02f2b0afa123f802529e7e6c" + + "3259d418290740ddbf55686e26998d7edcbbf895664972fed666f2f20af40503aa2af436ec6d" + + "4ec981ab19b9088755d94ae7a7c2066ea331d4e56e290000243fefe5555fce552d57a84e682c" + + "d4a6dfb3f2f94a94464d5bec3d88b88e9559642900001c00004004eb4afff764e7b79bca78b1" + + "3a89100d36d678ae982900001c00004005d177216a3c26f782076e12570d40bfaaa148822929" + + "0000080000402e290000100000402f00020003000400050000000800004014"; + private static final String SUCCESSFUL_IKE_AUTH_RESP_V4 = "46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e" + "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4" + "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592" + "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069" + "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1" + "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281"; + private static final String SUCCESSFUL_IKE_AUTH_RESP_V6 = + "46b8eca1e0d72a1800d9ea1babce26bf2e20232000000001000000f0240000d4aaf6eaa6c06b50447e6f54" + + "827fd8a9d9d6ac8015c1ebb3e8cb03fc6e54b49a107441f50004027cc5021600828026367f03" + + "bc425821cd7772ee98637361300c9b76056e874fea2bd4a17212370b291894264d8c023a01d1" + + "c3b691fd4b7c0b534e8c95af4c4638e2d125cb21c6267e2507cd745d72e8da109c47b9259c6c" + + "57a26f6bc5b337b9b9496d54bdde0333d7a32e6e1335c9ee730c3ecd607a8689aa7b0577b74f" + + "3bf437696a9fd5fc0aee3ed346cd9e15d1dda293df89eb388a8719388a60ca7625754de12cdb" + + "efe4c886c5c401"; private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16); private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1"); private static final InetAddress LOCAL_OUTER_6 = - InetAddress.parseNumericAddress("2001:db8:1::1"); + InetAddress.parseNumericAddress("2001:db8::1"); private static final int IP4_PREFIX_LEN = 32; private static final int IP6_PREFIX_LEN = 128; // TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet). private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2"; - private static final String TEST_SERVER_ADDR = "2001:db8::1"; + private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2"; private static final String TEST_IDENTITY = "client.cts.android.com"; private static final List TEST_ALLOWED_ALGORITHMS = Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); @@ -174,7 +202,7 @@ public class Ikev2VpnTest { private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks) throws Exception { - return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR, isRestrictedToTestNetworks); + return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks); } private Ikev2VpnProfile buildIkev2VpnProfilePsk( @@ -188,7 +216,7 @@ public class Ikev2VpnTest { private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks) throws Exception { final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY) + new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY) .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa); return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); @@ -197,7 +225,7 @@ public class Ikev2VpnTest { private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks) throws Exception { final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR, TEST_IDENTITY) + new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY) .setAuthDigitalSignature( mUserCertKey.cert, mUserCertKey.key, mServerRootCa); @@ -205,7 +233,7 @@ public class Ikev2VpnTest { } private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception { - assertEquals(TEST_SERVER_ADDR, profile.getServerAddr()); + assertEquals(TEST_SERVER_ADDR_V6, profile.getServerAddr()); assertEquals(TEST_IDENTITY, profile.getUserIdentity()); assertEquals(TEST_PROXY_INFO, profile.getProxyInfo()); assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms()); @@ -355,12 +383,18 @@ public class Ikev2VpnTest { } } - private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils) throws Exception { + private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6) + throws Exception { + String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4; + String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4; + String authResp = testIpv6 ? SUCCESSFUL_IKE_AUTH_RESP_V6 : SUCCESSFUL_IKE_AUTH_RESP_V4; + boolean hasNat = !testIpv6; + // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile. mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V4, true /* isRestrictedToTestNetworks */); + buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */); assertNull(sVpnMgr.provisionVpnProfile(profile)); sVpnMgr.startProvisionedVpnProfile(); @@ -368,9 +402,9 @@ public class Ikev2VpnTest { // Inject IKE negotiation int expectedMsgId = 0; tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */, - HexDump.hexStringToByteArray(SUCCESSFUL_IKE_INIT_RESP)); - tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, true /* isEncap */, - HexDump.hexStringToByteArray(SUCCESSFUL_IKE_AUTH_RESP)); + HexDump.hexStringToByteArray(initResp)); + tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, hasNat /* isEncap */, + HexDump.hexStringToByteArray(authResp)); // Verify the VPN network came up final NetworkRequest nr = new NetworkRequest.Builder() @@ -387,7 +421,7 @@ public class Ikev2VpnTest { assertEquals(vpnNetwork, cb.lastLostNetwork); } - private void doTestStartStopVpnProfile() throws Exception { + private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception { // Non-final; these variables ensure we clean up properly after our test if we have // allocated test network resources final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class); @@ -402,10 +436,11 @@ public class Ikev2VpnTest { new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)}); // Hold on to this callback to ensure network does not get reaped. - tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName()); + tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork( + testIface.getInterfaceName()); final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor()); - checkStartStopVpnProfileBuildsNetworks(tunUtils); + checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6); } finally { // Make sure to stop the VPN profile. This is safe to call multiple times. sVpnMgr.stopProvisionedVpnProfile(); @@ -426,12 +461,22 @@ public class Ikev2VpnTest { } @Test - public void testStartStopVpnProfile() throws Exception { + public void testStartStopVpnProfileV4() throws Exception { assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); // Requires shell permission to update appops. runWithShellPermissionIdentity(() -> { - doTestStartStopVpnProfile(); + doTestStartStopVpnProfile(false); + }); + } + + @Test + public void testStartStopVpnProfileV6() throws Exception { + assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); + + // Requires shell permission to update appops. + runWithShellPermissionIdentity(() -> { + doTestStartStopVpnProfile(true); }); }