diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java index 6e9f0cd250..9fa146ffca 100644 --- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java +++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java @@ -17,15 +17,16 @@ package android.net.cts; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 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 com.android.testutils.TestableNetworkCallbackKt.anyNetwork; 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; @@ -40,7 +41,6 @@ import android.net.ConnectivityManager; import android.net.Ikev2VpnProfile; import android.net.IpSecAlgorithm; import android.net.Network; -import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.ProxyInfo; import android.net.TestNetworkInterface; @@ -53,8 +53,14 @@ import android.platform.test.annotations.AppModeFull; import androidx.test.InstrumentationRegistry; import com.android.internal.util.HexDump; +import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl; +import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl; +import com.android.networkstack.apishim.common.Ikev2VpnProfileShim; +import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; +import com.android.testutils.RecorderCallback.CallbackEntry; +import com.android.testutils.TestableNetworkCallback; import org.bouncycastle.x509.X509V1CertificateGenerator; import org.junit.After; @@ -167,6 +173,7 @@ public class Ikev2VpnTest { private static final VpnManager sVpnMgr = (VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE); private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext); + private static final long TIMEOUT_MS = 15_000; private final X509Certificate mServerRootCa; private final CertificateAndKey mUserCertKey; @@ -197,31 +204,32 @@ public class Ikev2VpnTest { }, Manifest.permission.MANAGE_TEST_NETWORKS); } - private Ikev2VpnProfile buildIkev2VpnProfileCommon( - Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception { + private Ikev2VpnProfile buildIkev2VpnProfileCommon(@NonNull Ikev2VpnProfile.Builder builder, + boolean isRestrictedToTestNetworks, + boolean requiresValidation) throws Exception { if (isRestrictedToTestNetworks) { builder.restrictToTestNetworks(); } - return builder.setBypassable(true) + builder.setBypassable(true) .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS) .setProxy(TEST_PROXY_INFO) .setMaxMtu(TEST_MTU) - .setMetered(false) - .build(); + .setMetered(false); + if (TestUtils.shouldTestTApis()) { + Ikev2VpnProfileBuilderShimImpl.newInstance().setRequiresInternetValidation( + builder, requiresValidation); + } + return builder.build(); } - private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks) - throws Exception { - return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks); - } - - private Ikev2VpnProfile buildIkev2VpnProfilePsk( - String remote, boolean isRestrictedToTestNetworks) throws Exception { + private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote, + boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception { final Ikev2VpnProfile.Builder builder = new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK); - return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, + requiresValidation); } private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks) @@ -230,7 +238,8 @@ public class Ikev2VpnTest { new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY) .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa); - return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, + false /* requiresValidation */); } private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks) @@ -240,7 +249,8 @@ public class Ikev2VpnTest { .setAuthDigitalSignature( mUserCertKey.cert, mUserCertKey.key, mServerRootCa); - return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks); + return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks, + false /* requiresValidation */); } private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception { @@ -254,12 +264,11 @@ public class Ikev2VpnTest { assertFalse(profile.isRestrictedToTestNetworks()); } - @Test - public void testBuildIkev2VpnProfilePsk() throws Exception { + public void doTestBuildIkev2VpnProfilePsk(final boolean requiresValidation) throws Exception { assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature()); - final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, + false /* isRestrictedToTestNetworks */, requiresValidation); checkBasicIkev2VpnProfile(profile); assertArrayEquals(TEST_PSK, profile.getPresharedKey()); @@ -270,6 +279,22 @@ public class Ikev2VpnTest { assertNull(profile.getServerRootCaCert()); assertNull(profile.getRsaPrivateKey()); assertNull(profile.getUserCert()); + final Ikev2VpnProfileShim shim = Ikev2VpnProfileShimImpl.newInstance(); + if (TestUtils.shouldTestTApis()) { + assertEquals(requiresValidation, shim.isInternetValidationRequired(profile)); + } else { + try { + shim.isInternetValidationRequired(profile); + fail("Only supported from API level 33"); + } catch (UnsupportedApiLevelException expected) { + } + } + } + + @Test + public void testBuildIkev2VpnProfilePsk() throws Exception { + doTestBuildIkev2VpnProfilePsk(true /* requiresValidation */); + doTestBuildIkev2VpnProfilePsk(false /* requiresValidation */); } @Test @@ -316,8 +341,8 @@ public class Ikev2VpnTest { setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn); setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn); - final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, + false /* isRestrictedToTestNetworks */, false /* requiresValidation */); final Intent intent = sVpnMgr.provisionVpnProfile(profile); assertEquals(expectIntent, intent != null); } @@ -360,8 +385,8 @@ public class Ikev2VpnTest { setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); - final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */); + final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, + false /* isRestrictedToTestNetworks */, false /* requiresValidation */); assertNull(sVpnMgr.provisionVpnProfile(profile)); // Verify that deleting the profile works (even without the appop) @@ -394,7 +419,8 @@ public class Ikev2VpnTest { } } - private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6) + private void checkStartStopVpnProfileBuildsNetworks(@NonNull IkeTunUtils tunUtils, + boolean testIpv6, boolean requiresValidation) 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; @@ -404,10 +430,15 @@ public class Ikev2VpnTest { // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile. mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true); - final Ikev2VpnProfile profile = - buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */); + final Ikev2VpnProfile profile = buildIkev2VpnProfilePsk(serverAddr, + true /* isRestrictedToTestNetworks */, requiresValidation); assertNull(sVpnMgr.provisionVpnProfile(profile)); + final TestableNetworkCallback cb = new TestableNetworkCallback(TIMEOUT_MS); + final NetworkRequest nr = new NetworkRequest.Builder() + .clearCapabilities().addTransportType(TRANSPORT_VPN).build(); + sCM.registerNetworkCallback(nr, cb); + sVpnMgr.startProvisionedVpnProfile(); // Inject IKE negotiation @@ -418,35 +449,49 @@ public class Ikev2VpnTest { HexDump.hexStringToByteArray(authResp)); // Verify the VPN network came up - final NetworkRequest nr = new NetworkRequest.Builder() - .clearCapabilities().addTransportType(TRANSPORT_VPN).build(); + final Network vpnNetwork = cb.expectCallback(CallbackEntry.AVAILABLE, anyNetwork()) + .getNetwork(); - final TestNetworkCallback cb = new TestNetworkCallback(); - sCM.requestNetwork(nr, cb); - cb.waitForAvailable(); - final Network vpnNetwork = cb.currentNetwork; - assertNotNull(vpnNetwork); + cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS, caps -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasCapability(NET_CAPABILITY_INTERNET) + && !caps.hasCapability(NET_CAPABILITY_VALIDATED) + && Process.myUid() == caps.getOwnerUid()); + cb.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, vpnNetwork); + cb.expectCallback(CallbackEntry.BLOCKED_STATUS, vpnNetwork); - final NetworkCapabilities caps = sCM.getNetworkCapabilities(vpnNetwork); - assertTrue(caps.hasTransport(TRANSPORT_VPN)); - assertTrue(caps.hasCapability(NET_CAPABILITY_INTERNET)); - assertEquals(Process.myUid(), caps.getOwnerUid()); + // A VPN that requires validation is initially not validated, while one that doesn't + // immediately validate automatically. Because this VPN can't actually access Internet, + // the VPN only validates if it doesn't require validation. If the VPN requires validation + // but unexpectedly sends this callback, expecting LOST below will fail because the next + // callback will be the validated capabilities instead. + // In S and below, |requiresValidation| is ignored, so this callback is always sent + // regardless of its value. + if (!requiresValidation || !TestUtils.shouldTestTApis()) { + cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, TIMEOUT_MS, + entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps() + .hasCapability(NET_CAPABILITY_VALIDATED)); + } sVpnMgr.stopProvisionedVpnProfile(); - cb.waitForLost(); - assertEquals(vpnNetwork, cb.lastLostNetwork); + // Using expectCallback may cause the test to be flaky since test may receive other + // callbacks such as linkproperties change. + cb.eventuallyExpect(CallbackEntry.LOST, TIMEOUT_MS, + lost -> vpnNetwork.equals(lost.getNetwork())); } private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test { private final boolean mTestIpv6Only; + private final boolean mRequiresValidation; /** * Constructs the test * * @param testIpv6Only if true, builds a IPv6-only test; otherwise builds a IPv4-only test + * @param requiresValidation whether this VPN should request platform validation */ - VerifyStartStopVpnProfileTest(boolean testIpv6Only) { + VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation) { mTestIpv6Only = testIpv6Only; + mRequiresValidation = requiresValidation; } @Override @@ -454,7 +499,8 @@ public class Ikev2VpnTest { throws Exception { final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor()); - checkStartStopVpnProfileBuildsNetworks(tunUtils, mTestIpv6Only); + checkStartStopVpnProfileBuildsNetworks( + tunUtils, mTestIpv6Only, mRequiresValidation); } @Override @@ -478,7 +524,10 @@ public class Ikev2VpnTest { // Requires shell permission to update appops. runWithShellPermissionIdentity( - new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false))); + new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, false))); + + runWithShellPermissionIdentity( + new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, true))); } @Test @@ -487,7 +536,9 @@ public class Ikev2VpnTest { // Requires shell permission to update appops. runWithShellPermissionIdentity( - new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true))); + new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, false))); + runWithShellPermissionIdentity( + new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, true))); } private static class CertificateAndKey { diff --git a/tests/cts/net/src/android/net/cts/TestUtils.java b/tests/cts/net/src/android/net/cts/TestUtils.java index c1100b111f..001aa01799 100644 --- a/tests/cts/net/src/android/net/cts/TestUtils.java +++ b/tests/cts/net/src/android/net/cts/TestUtils.java @@ -16,6 +16,8 @@ package android.net.cts; +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + import android.os.Build; import com.android.modules.utils.build.SdkLevel; @@ -33,4 +35,13 @@ public class TestUtils { public static boolean shouldTestSApis() { return SdkLevel.isAtLeastS() && ConstantsShim.VERSION > Build.VERSION_CODES.R; } + + /** + * Whether to test T+ 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. + */ + public static boolean shouldTestTApis() { + // TODO: replace SC_V2 with Build.VERSION_CODES.S_V2 when it's available in mainline branch. + return SdkLevel.isAtLeastT() && ConstantsShim.VERSION > SC_V2; + } } diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java index 365c0cfbb8..2763f5a789 100644 --- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java @@ -343,6 +343,10 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { return mNetworkAgent; } + public NetworkAgentConfig getNetworkAgentConfig() { + return mNetworkAgentConfig; + } + public NetworkCapabilities getNetworkCapabilities() { return mNetworkCapabilities; } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 025b28c71d..4c768030f8 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -337,6 +337,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.ArrayTrackRecord; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LocationPermissionChecker; +import com.android.networkstack.apishim.NetworkAgentConfigShimImpl; import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; import com.android.server.ConnectivityService.NetworkRequestInfo; import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces; @@ -1373,6 +1374,10 @@ public class ConnectivityServiceTest { return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork(); } + public NetworkAgentConfig getNetworkAgentConfig() { + return null == mMockNetworkAgent ? null : mMockNetworkAgent.getNetworkAgentConfig(); + } + @Override public int getActiveVpnType() { return mVpnType; @@ -2936,6 +2941,7 @@ public class ConnectivityServiceTest { @Test public void testRequiresValidation() { assertTrue(NetworkMonitorUtils.isValidationRequired( + NetworkAgentConfigShimImpl.newInstance(null), mCm.getDefaultRequest().networkCapabilities)); } @@ -7933,6 +7939,7 @@ public class ConnectivityServiceTest { // VPN networks do not satisfy the default request and are automatically validated // by NetworkMonitor assertFalse(NetworkMonitorUtils.isValidationRequired( + NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()), mMockVpn.getAgent().getNetworkCapabilities())); mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); @@ -8083,6 +8090,7 @@ public class ConnectivityServiceTest { assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET)); assertFalse(NetworkMonitorUtils.isValidationRequired( + NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()), mMockVpn.getAgent().getNetworkCapabilities())); assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired( mMockVpn.getAgent().getNetworkCapabilities()));