Merge "Add tests for VPN validation in NetworkMonitor"
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 6e9f0cd..9fa146f 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.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 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 @@
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 @@
}, 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 @@
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 @@
.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 @@
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 @@
assertNull(profile.getServerRootCaCert());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
+ final Ikev2VpnProfileShim<Ikev2VpnProfile> 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 @@
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 @@
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 @@
}
}
- 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 @@
// 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 @@
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 @@
throws Exception {
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
- checkStartStopVpnProfileBuildsNetworks(tunUtils, mTestIpv6Only);
+ checkStartStopVpnProfileBuildsNetworks(
+ tunUtils, mTestIpv6Only, mRequiresValidation);
}
@Override
@@ -478,7 +524,10 @@
// 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 @@
// 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 c1100b1..001aa01 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 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 365c0cf..2763f5a 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -343,6 +343,10 @@
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 025b28c..4c76803 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.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 @@
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 @@
@Test
public void testRequiresValidation() {
assertTrue(NetworkMonitorUtils.isValidationRequired(
+ NetworkAgentConfigShimImpl.newInstance(null),
mCm.getDefaultRequest().networkCapabilities));
}
@@ -7933,6 +7939,7 @@
// 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 @@
assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
assertFalse(NetworkMonitorUtils.isValidationRequired(
+ NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()),
mMockVpn.getAgent().getNetworkCapabilities()));
assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
mMockVpn.getAgent().getNetworkCapabilities()));