diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 5961e722b9..64e159b255 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -124,6 +124,7 @@ package android.net { public final class NetworkAgentConfig implements android.os.Parcelable { method @Nullable public String getSubscriberId(); + method public boolean getVpnRequiresValidation(); method public boolean isBypassableVpn(); } @@ -131,6 +132,7 @@ package android.net { method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setExcludeLocalRoutesVpn(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + method @NonNull public android.net.NetworkAgentConfig.Builder setVpnRequiresValidation(boolean); } public final class NetworkCapabilities implements android.os.Parcelable { diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java index 040bf31178..3f5d5e598a 100644 --- a/framework/src/android/net/NetworkAgentConfig.java +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -34,6 +34,8 @@ import java.util.Objects; */ @SystemApi public final class NetworkAgentConfig implements Parcelable { + // TODO : make this object immutable. The fields that should stay mutable should likely + // migrate to NetworkAgentInfo. /** * If the {@link Network} is a VPN, whether apps are allowed to bypass the @@ -246,6 +248,27 @@ public final class NetworkAgentConfig implements Parcelable { return excludeLocalRouteVpn; } + /** + * Whether network validation should be performed for this VPN network. + * {@see #getVpnRequiresValidation} + * @hide + */ + private boolean mVpnRequiresValidation = false; + + /** + * Whether network validation should be performed for this VPN network. + * + * If this network isn't a VPN this should always be {@code false}, and will be ignored + * if set. + * If this network is a VPN, false means this network should always be considered validated; + * true means it follows the same validation semantics as general internet networks. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public boolean getVpnRequiresValidation() { + return mVpnRequiresValidation; + } + /** @hide */ public NetworkAgentConfig() { } @@ -266,6 +289,7 @@ public final class NetworkAgentConfig implements Parcelable { legacySubTypeName = nac.legacySubTypeName; mLegacyExtraInfo = nac.mLegacyExtraInfo; excludeLocalRouteVpn = nac.excludeLocalRouteVpn; + mVpnRequiresValidation = nac.mVpnRequiresValidation; } } @@ -408,6 +432,25 @@ public final class NetworkAgentConfig implements Parcelable { return this; } + /** + * Sets whether network validation should be performed for this VPN network. + * + * Only agents registering a VPN network should use this setter. On other network + * types it will be ignored. + * False means this network should always be considered validated; + * true means it follows the same validation semantics as general internet. + * + * @param vpnRequiresValidation whether this VPN requires validation. + * Default is {@code false}. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + public Builder setVpnRequiresValidation(boolean vpnRequiresValidation) { + mConfig.mVpnRequiresValidation = vpnRequiresValidation; + return this; + } + /** * Sets whether the apps can bypass the VPN connection. * @@ -458,14 +501,16 @@ public final class NetworkAgentConfig implements Parcelable { && Objects.equals(subscriberId, that.subscriberId) && Objects.equals(legacyTypeName, that.legacyTypeName) && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo) - && excludeLocalRouteVpn == that.excludeLocalRouteVpn; + && excludeLocalRouteVpn == that.excludeLocalRouteVpn + && mVpnRequiresValidation == that.mVpnRequiresValidation; } @Override public int hashCode() { return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, - skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn); + skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn, + mVpnRequiresValidation); } @Override @@ -483,6 +528,7 @@ public final class NetworkAgentConfig implements Parcelable { + ", legacyTypeName = '" + legacyTypeName + '\'' + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\'' + ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\'' + + ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\'' + "}"; } @@ -506,6 +552,7 @@ public final class NetworkAgentConfig implements Parcelable { out.writeString(legacySubTypeName); out.writeString(mLegacyExtraInfo); out.writeInt(excludeLocalRouteVpn ? 1 : 0); + out.writeInt(mVpnRequiresValidation ? 1 : 0); } public static final @NonNull Creator CREATOR = @@ -526,6 +573,7 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.legacySubTypeName = in.readString(); networkAgentConfig.mLegacyExtraInfo = in.readString(); networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0; + networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0; return networkAgentConfig; } diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt index b339a271ef..11d3ba02d0 100644 --- a/tests/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt @@ -20,6 +20,7 @@ import android.os.Build import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.modules.utils.build.SdkLevel.isAtLeastS +import com.android.modules.utils.build.SdkLevel.isAtLeastT import com.android.testutils.ConnectivityModuleTest import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo @@ -49,6 +50,10 @@ class NetworkAgentConfigTest { if (isAtLeastS()) { setBypassableVpn(true) } + if (isAtLeastT()) { + setExcludeLocalRoutesVpn(true) + setVpnRequiresValidation(true) + } }.build() assertParcelingIsLossless(config) } @@ -69,6 +74,10 @@ class NetworkAgentConfigTest { setProvisioningNotificationEnabled(false) setBypassableVpn(true) } + if (isAtLeastT()) { + setExcludeLocalRoutesVpn(true) + setVpnRequiresValidation(true) + } }.build() assertTrue(config.isExplicitlySelected()) @@ -77,6 +86,10 @@ class NetworkAgentConfigTest { assertFalse(config.isPartialConnectivityAcceptable()) assertTrue(config.isUnvalidatedConnectivityAcceptable()) assertEquals("TEST_NETWORK", config.getLegacyTypeName()) + if (isAtLeastT()) { + assertTrue(config.getExcludeLocalRouteVpn()) + assertTrue(config.getVpnRequiresValidation()) + } if (isAtLeastS()) { assertEquals(testExtraInfo, config.getLegacyExtraInfo()) assertFalse(config.isNat64DetectionEnabled()) diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java index 960a9f1b10..943a5599cc 100644 --- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java +++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java @@ -50,6 +50,7 @@ public class VpnProfileTest { private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25; + private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26; @Test public void testDefaults() throws Exception { @@ -78,10 +79,13 @@ public class VpnProfileTest { assertEquals(1360, p.maxMtu); assertFalse(p.areAuthParamsInline); assertFalse(p.isRestrictedToTestNetworks); + assertFalse(p.excludeLocalRoutes); + assertFalse(p.requiresInternetValidation); } private VpnProfile getSampleIkev2Profile(String key) { - final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */); + final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */, + false /* excludesLocalRoutes */, true /* requiresPlatformValidation */); p.name = "foo"; p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; @@ -129,8 +133,8 @@ public class VpnProfileTest { @Test public void testParcelUnparcel() { if (isAtLeastT()) { - // excludeLocalRoutes is added in T. - assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 24); + // excludeLocalRoutes, requiresPlatformValidation were added in T. + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 25); } else { assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); } @@ -174,7 +178,8 @@ public class VpnProfileTest { getEncodedDecodedIkev2ProfileMissingValues( ENCODED_INDEX_AUTH_PARAMS_INLINE, ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS, - ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */); + ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE, + ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); } @@ -194,13 +199,25 @@ public class VpnProfileTest { public void testEncodeDecodeMissingExcludeLocalRoutes() { final String tooFewValues = getEncodedDecodedIkev2ProfileMissingValues( - ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */); + ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE, + ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */); - // Verify decoding without isRestrictedToTestNetworks defaults to false + // Verify decoding without excludeLocalRoutes defaults to false final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); assertFalse(decoded.excludeLocalRoutes); } + @Test + public void testEncodeDecodeMissingRequiresValidation() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */); + + // Verify decoding without requiresValidation defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.requiresInternetValidation); + } + @Test public void testEncodeDecodeLoginsNotSaved() { final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);