Loading core/java/android/net/Ikev2VpnProfile.java +40 −5 Original line number Original line Diff line number Diff line Loading @@ -101,6 +101,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { private final boolean mIsBypassable; // Defaults in builder private final boolean mIsBypassable; // Defaults in builder private final boolean mIsMetered; // Defaults in builder private final boolean mIsMetered; // Defaults in builder private final int mMaxMtu; // Defaults in builder private final int mMaxMtu; // Defaults in builder private final boolean mIsRestrictedToTestNetworks; private Ikev2VpnProfile( private Ikev2VpnProfile( int type, int type, Loading @@ -116,7 +117,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @NonNull List<String> allowedAlgorithms, @NonNull List<String> allowedAlgorithms, boolean isBypassable, boolean isBypassable, boolean isMetered, boolean isMetered, int maxMtu) { int maxMtu, boolean restrictToTestNetworks) { super(type); super(type); checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); Loading @@ -140,6 +142,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mIsBypassable = isBypassable; mIsBypassable = isBypassable; mIsMetered = isMetered; mIsMetered = isMetered; mMaxMtu = maxMtu; mMaxMtu = maxMtu; mIsRestrictedToTestNetworks = restrictToTestNetworks; validate(); validate(); } } Loading Loading @@ -329,6 +332,15 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return mMaxMtu; return mMaxMtu; } } /** * Returns whether or not this VPN profile is restricted to test networks. * * @hide */ public boolean isRestrictedToTestNetworks() { return mIsRestrictedToTestNetworks; } @Override @Override public int hashCode() { public int hashCode() { return Objects.hash( return Objects.hash( Loading @@ -345,7 +357,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mAllowedAlgorithms, mIsBypassable, mIsBypassable, mIsMetered, mIsMetered, mMaxMtu); mMaxMtu, mIsRestrictedToTestNetworks); } } @Override @Override Loading @@ -368,7 +381,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) && mIsBypassable == other.mIsBypassable && mIsBypassable == other.mIsBypassable && mIsMetered == other.mIsMetered && mIsMetered == other.mIsMetered && mMaxMtu == other.mMaxMtu; && mMaxMtu == other.mMaxMtu && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks; } } /** /** Loading @@ -381,7 +395,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { */ */ @NonNull @NonNull public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */); final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */, mIsRestrictedToTestNetworks); profile.type = mType; profile.type = mType; profile.server = mServerAddr; profile.server = mServerAddr; profile.ipsecIdentifier = mUserIdentity; profile.ipsecIdentifier = mUserIdentity; Loading Loading @@ -449,6 +464,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { builder.setBypassable(profile.isBypassable); builder.setBypassable(profile.isBypassable); builder.setMetered(profile.isMetered); builder.setMetered(profile.isMetered); builder.setMaxMtu(profile.maxMtu); builder.setMaxMtu(profile.maxMtu); if (profile.isRestrictedToTestNetworks) { builder.restrictToTestNetworks(); } switch (profile.type) { switch (profile.type) { case TYPE_IKEV2_IPSEC_USER_PASS: case TYPE_IKEV2_IPSEC_USER_PASS: Loading Loading @@ -621,6 +639,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { private boolean mIsBypassable = false; private boolean mIsBypassable = false; private boolean mIsMetered = true; private boolean mIsMetered = true; private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; private boolean mIsRestrictedToTestNetworks = false; /** /** * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. Loading Loading @@ -841,6 +860,21 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return this; return this; } } /** * Restricts this profile to use test networks (only). * * <p>This method is for testing only, and must not be used by apps. Calling * provisionVpnProfile() with a profile where test-network usage is enabled will require the * MANAGE_TEST_NETWORKS permission. * * @hide */ @NonNull public Builder restrictToTestNetworks() { mIsRestrictedToTestNetworks = true; return this; } /** /** * Validates, builds and provisions the VpnProfile. * Validates, builds and provisions the VpnProfile. * * Loading @@ -862,7 +896,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mAllowedAlgorithms, mIsBypassable, mIsBypassable, mIsMetered, mIsMetered, mMaxMtu); mMaxMtu, mIsRestrictedToTestNetworks); } } } } } } core/java/com/android/internal/net/VpnProfile.java +27 −5 Original line number Original line Diff line number Diff line Loading @@ -136,13 +136,19 @@ public final class VpnProfile implements Cloneable, Parcelable { public boolean isMetered = false; // 21 public boolean isMetered = false; // 21 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 public boolean areAuthParamsInline = false; // 23 public boolean areAuthParamsInline = false; // 23 public final boolean isRestrictedToTestNetworks; // 24 // Helper fields. // Helper fields. @UnsupportedAppUsage @UnsupportedAppUsage public transient boolean saveLogin = false; public transient boolean saveLogin = false; public VpnProfile(String key) { public VpnProfile(String key) { this(key, false); } public VpnProfile(String key, boolean isRestrictedToTestNetworks) { this.key = key; this.key = key; this.isRestrictedToTestNetworks = isRestrictedToTestNetworks; } } @UnsupportedAppUsage @UnsupportedAppUsage Loading Loading @@ -171,6 +177,7 @@ public final class VpnProfile implements Cloneable, Parcelable { isMetered = in.readBoolean(); isMetered = in.readBoolean(); maxMtu = in.readInt(); maxMtu = in.readInt(); areAuthParamsInline = in.readBoolean(); areAuthParamsInline = in.readBoolean(); isRestrictedToTestNetworks = in.readBoolean(); } } /** /** Loading Loading @@ -220,6 +227,7 @@ public final class VpnProfile implements Cloneable, Parcelable { out.writeBoolean(isMetered); out.writeBoolean(isMetered); out.writeInt(maxMtu); out.writeInt(maxMtu); out.writeBoolean(areAuthParamsInline); out.writeBoolean(areAuthParamsInline); out.writeBoolean(isRestrictedToTestNetworks); } } /** /** Loading @@ -237,12 +245,21 @@ public final class VpnProfile implements Cloneable, Parcelable { String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); // Acceptable numbers of values are: // Acceptable numbers of values are: // 14-19: Standard profile, with option for serverCert, proxy // 14-19: Standard profile, with option for serverCert, proxy // 24: Standard profile with serverCert, proxy and platform-VPN parameters. // 24: Standard profile with serverCert, proxy and platform-VPN parameters if ((values.length < 14 || values.length > 19) && values.length != 24) { // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks if ((values.length < 14 || values.length > 19) && values.length != 24 && values.length != 25) { return null; return null; } } VpnProfile profile = new VpnProfile(key); final boolean isRestrictedToTestNetworks; if (values.length >= 25) { isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]); } else { isRestrictedToTestNetworks = false; } VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks); profile.name = values[0]; profile.name = values[0]; profile.type = Integer.parseInt(values[1]); profile.type = Integer.parseInt(values[1]); if (profile.type < 0 || profile.type > TYPE_MAX) { if (profile.type < 0 || profile.type > TYPE_MAX) { Loading Loading @@ -283,6 +300,8 @@ public final class VpnProfile implements Cloneable, Parcelable { profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); } } // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); return profile; return profile; } catch (Exception e) { } catch (Exception e) { Loading Loading @@ -330,6 +349,7 @@ public final class VpnProfile implements Cloneable, Parcelable { builder.append(VALUE_DELIMITER).append(isMetered); builder.append(VALUE_DELIMITER).append(isMetered); builder.append(VALUE_DELIMITER).append(maxMtu); builder.append(VALUE_DELIMITER).append(maxMtu); builder.append(VALUE_DELIMITER).append(areAuthParamsInline); builder.append(VALUE_DELIMITER).append(areAuthParamsInline); builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks); return builder.toString().getBytes(StandardCharsets.UTF_8); return builder.toString().getBytes(StandardCharsets.UTF_8); } } Loading Loading @@ -421,7 +441,8 @@ public final class VpnProfile implements Cloneable, Parcelable { return Objects.hash( return Objects.hash( key, type, server, username, password, dnsServers, searchDomains, routes, mppe, key, type, server, username, password, dnsServers, searchDomains, routes, mppe, l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline); proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline, isRestrictedToTestNetworks); } } /** Checks VPN profiles for interior equality. */ /** Checks VPN profiles for interior equality. */ Loading Loading @@ -453,7 +474,8 @@ public final class VpnProfile implements Cloneable, Parcelable { && isBypassable == other.isBypassable && isBypassable == other.isBypassable && isMetered == other.isMetered && isMetered == other.isMetered && maxMtu == other.maxMtu && maxMtu == other.maxMtu && areAuthParamsInline == other.areAuthParamsInline; && areAuthParamsInline == other.areAuthParamsInline && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks; } } @NonNull @NonNull Loading services/core/java/com/android/server/connectivity/Vpn.java +26 −5 Original line number Original line Diff line number Diff line Loading @@ -65,6 +65,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.DetailedState; import android.net.NetworkProvider; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.RouteInfo; import android.net.RouteInfo; import android.net.UidRange; import android.net.UidRange; import android.net.VpnManager; import android.net.VpnManager; Loading Loading @@ -2225,12 +2226,27 @@ public class Vpn { @Override @Override public void run() { public void run() { // Explicitly use only the network that ConnectivityService thinks is the "best." In // Unless the profile is restricted to test networks, explicitly use only the network // other words, only ever use the currently selected default network. This does mean // that ConnectivityService thinks is the "best." In other words, only ever use the // that in both onLost() and onConnected(), any old sessions MUST be torn down. This // currently selected default network. This does mean that in both onLost() and // does NOT include VPNs. // onConnected(), any old sessions MUST be torn down. This does NOT include VPNs. // // When restricted to test networks, select any network with TRANSPORT_TEST. Since the // creator of the profile and the test network creator both have MANAGE_TEST_NETWORKS, // this is considered safe. final ConnectivityManager cm = ConnectivityManager.from(mContext); final ConnectivityManager cm = ConnectivityManager.from(mContext); cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback); final NetworkRequest req; if (mProfile.isRestrictedToTestNetworks()) { req = new NetworkRequest.Builder() .clearCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_TEST) .build(); } else { req = cm.getDefaultRequest(); } cm.requestNetwork(req, mNetworkCallback); } } private boolean isActiveNetwork(@Nullable Network network) { private boolean isActiveNetwork(@Nullable Network network) { Loading Loading @@ -2868,6 +2884,11 @@ public class Vpn { verifyCallingUidAndPackage(packageName); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); enforceNotRestrictedUser(); if (profile.isRestrictedToTestNetworks) { mContext.enforceCallingPermission(Manifest.permission.MANAGE_TEST_NETWORKS, "Test-mode profiles require the MANAGE_TEST_NETWORKS permission"); } final byte[] encodedProfile = profile.encode(); final byte[] encodedProfile = profile.encode(); if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { throw new IllegalArgumentException("Profile too big"); throw new IllegalArgumentException("Profile too big"); Loading tests/net/java/com/android/internal/net/VpnProfileTest.java +40 −7 Original line number Original line Diff line number Diff line Loading @@ -33,7 +33,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; /** Unit tests for {@link VpnProfile}. */ /** Unit tests for {@link VpnProfile}. */ @SmallTest @SmallTest Loading @@ -41,6 +43,9 @@ import java.util.Arrays; public class VpnProfileTest { public class VpnProfileTest { private static final String DUMMY_PROFILE_KEY = "Test"; private static final String DUMMY_PROFILE_KEY = "Test"; private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; @Test @Test public void testDefaults() throws Exception { public void testDefaults() throws Exception { final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); Loading @@ -67,10 +72,11 @@ public class VpnProfileTest { assertFalse(p.isMetered); assertFalse(p.isMetered); assertEquals(1360, p.maxMtu); assertEquals(1360, p.maxMtu); assertFalse(p.areAuthParamsInline); assertFalse(p.areAuthParamsInline); assertFalse(p.isRestrictedToTestNetworks); } } private VpnProfile getSampleIkev2Profile(String key) { private VpnProfile getSampleIkev2Profile(String key) { final VpnProfile p = new VpnProfile(key); final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */); p.name = "foo"; p.name = "foo"; p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; Loading Loading @@ -116,7 +122,7 @@ public class VpnProfileTest { @Test @Test public void testParcelUnparcel() { public void testParcelUnparcel() { assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22); assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); } } @Test @Test Loading Loading @@ -159,14 +165,41 @@ public class VpnProfileTest { assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); } } private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) { // Sort to ensure when we remove, we can do it from greatest first. Arrays.sort(missingIndices); final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode()); final List<String> parts = new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER))); // Remove from back first to ensure indexing is consistent. for (int i = missingIndices.length - 1; i >= 0; i--) { parts.remove(missingIndices[i]); } return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0])); } @Test @Test public void testEncodeDecodeInvalidNumberOfValues() { public void testEncodeDecodeInvalidNumberOfValues() { final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); final String tooFewValues = final String encoded = new String(profile.encode()); getEncodedDecodedIkev2ProfileMissingValues( final byte[] tooFewValues = ENCODED_INDEX_AUTH_PARAMS_INLINE, encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes(); ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues)); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); } @Test public void testEncodeDecodeMissingIsRestrictedToTestNetworks() { final String tooFewValues = getEncodedDecodedIkev2ProfileMissingValues( ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); // Verify decoding without isRestrictedToTestNetworks defaults to false final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); assertFalse(decoded.isRestrictedToTestNetworks); } } @Test @Test Loading Loading
core/java/android/net/Ikev2VpnProfile.java +40 −5 Original line number Original line Diff line number Diff line Loading @@ -101,6 +101,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { private final boolean mIsBypassable; // Defaults in builder private final boolean mIsBypassable; // Defaults in builder private final boolean mIsMetered; // Defaults in builder private final boolean mIsMetered; // Defaults in builder private final int mMaxMtu; // Defaults in builder private final int mMaxMtu; // Defaults in builder private final boolean mIsRestrictedToTestNetworks; private Ikev2VpnProfile( private Ikev2VpnProfile( int type, int type, Loading @@ -116,7 +117,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @NonNull List<String> allowedAlgorithms, @NonNull List<String> allowedAlgorithms, boolean isBypassable, boolean isBypassable, boolean isMetered, boolean isMetered, int maxMtu) { int maxMtu, boolean restrictToTestNetworks) { super(type); super(type); checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); Loading @@ -140,6 +142,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mIsBypassable = isBypassable; mIsBypassable = isBypassable; mIsMetered = isMetered; mIsMetered = isMetered; mMaxMtu = maxMtu; mMaxMtu = maxMtu; mIsRestrictedToTestNetworks = restrictToTestNetworks; validate(); validate(); } } Loading Loading @@ -329,6 +332,15 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return mMaxMtu; return mMaxMtu; } } /** * Returns whether or not this VPN profile is restricted to test networks. * * @hide */ public boolean isRestrictedToTestNetworks() { return mIsRestrictedToTestNetworks; } @Override @Override public int hashCode() { public int hashCode() { return Objects.hash( return Objects.hash( Loading @@ -345,7 +357,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mAllowedAlgorithms, mIsBypassable, mIsBypassable, mIsMetered, mIsMetered, mMaxMtu); mMaxMtu, mIsRestrictedToTestNetworks); } } @Override @Override Loading @@ -368,7 +381,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) && mIsBypassable == other.mIsBypassable && mIsBypassable == other.mIsBypassable && mIsMetered == other.mIsMetered && mIsMetered == other.mIsMetered && mMaxMtu == other.mMaxMtu; && mMaxMtu == other.mMaxMtu && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks; } } /** /** Loading @@ -381,7 +395,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { */ */ @NonNull @NonNull public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */); final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */, mIsRestrictedToTestNetworks); profile.type = mType; profile.type = mType; profile.server = mServerAddr; profile.server = mServerAddr; profile.ipsecIdentifier = mUserIdentity; profile.ipsecIdentifier = mUserIdentity; Loading Loading @@ -449,6 +464,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { builder.setBypassable(profile.isBypassable); builder.setBypassable(profile.isBypassable); builder.setMetered(profile.isMetered); builder.setMetered(profile.isMetered); builder.setMaxMtu(profile.maxMtu); builder.setMaxMtu(profile.maxMtu); if (profile.isRestrictedToTestNetworks) { builder.restrictToTestNetworks(); } switch (profile.type) { switch (profile.type) { case TYPE_IKEV2_IPSEC_USER_PASS: case TYPE_IKEV2_IPSEC_USER_PASS: Loading Loading @@ -621,6 +639,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { private boolean mIsBypassable = false; private boolean mIsBypassable = false; private boolean mIsMetered = true; private boolean mIsMetered = true; private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; private boolean mIsRestrictedToTestNetworks = false; /** /** * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. Loading Loading @@ -841,6 +860,21 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return this; return this; } } /** * Restricts this profile to use test networks (only). * * <p>This method is for testing only, and must not be used by apps. Calling * provisionVpnProfile() with a profile where test-network usage is enabled will require the * MANAGE_TEST_NETWORKS permission. * * @hide */ @NonNull public Builder restrictToTestNetworks() { mIsRestrictedToTestNetworks = true; return this; } /** /** * Validates, builds and provisions the VpnProfile. * Validates, builds and provisions the VpnProfile. * * Loading @@ -862,7 +896,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mAllowedAlgorithms, mIsBypassable, mIsBypassable, mIsMetered, mIsMetered, mMaxMtu); mMaxMtu, mIsRestrictedToTestNetworks); } } } } } }
core/java/com/android/internal/net/VpnProfile.java +27 −5 Original line number Original line Diff line number Diff line Loading @@ -136,13 +136,19 @@ public final class VpnProfile implements Cloneable, Parcelable { public boolean isMetered = false; // 21 public boolean isMetered = false; // 21 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 public boolean areAuthParamsInline = false; // 23 public boolean areAuthParamsInline = false; // 23 public final boolean isRestrictedToTestNetworks; // 24 // Helper fields. // Helper fields. @UnsupportedAppUsage @UnsupportedAppUsage public transient boolean saveLogin = false; public transient boolean saveLogin = false; public VpnProfile(String key) { public VpnProfile(String key) { this(key, false); } public VpnProfile(String key, boolean isRestrictedToTestNetworks) { this.key = key; this.key = key; this.isRestrictedToTestNetworks = isRestrictedToTestNetworks; } } @UnsupportedAppUsage @UnsupportedAppUsage Loading Loading @@ -171,6 +177,7 @@ public final class VpnProfile implements Cloneable, Parcelable { isMetered = in.readBoolean(); isMetered = in.readBoolean(); maxMtu = in.readInt(); maxMtu = in.readInt(); areAuthParamsInline = in.readBoolean(); areAuthParamsInline = in.readBoolean(); isRestrictedToTestNetworks = in.readBoolean(); } } /** /** Loading Loading @@ -220,6 +227,7 @@ public final class VpnProfile implements Cloneable, Parcelable { out.writeBoolean(isMetered); out.writeBoolean(isMetered); out.writeInt(maxMtu); out.writeInt(maxMtu); out.writeBoolean(areAuthParamsInline); out.writeBoolean(areAuthParamsInline); out.writeBoolean(isRestrictedToTestNetworks); } } /** /** Loading @@ -237,12 +245,21 @@ public final class VpnProfile implements Cloneable, Parcelable { String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); // Acceptable numbers of values are: // Acceptable numbers of values are: // 14-19: Standard profile, with option for serverCert, proxy // 14-19: Standard profile, with option for serverCert, proxy // 24: Standard profile with serverCert, proxy and platform-VPN parameters. // 24: Standard profile with serverCert, proxy and platform-VPN parameters if ((values.length < 14 || values.length > 19) && values.length != 24) { // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks if ((values.length < 14 || values.length > 19) && values.length != 24 && values.length != 25) { return null; return null; } } VpnProfile profile = new VpnProfile(key); final boolean isRestrictedToTestNetworks; if (values.length >= 25) { isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]); } else { isRestrictedToTestNetworks = false; } VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks); profile.name = values[0]; profile.name = values[0]; profile.type = Integer.parseInt(values[1]); profile.type = Integer.parseInt(values[1]); if (profile.type < 0 || profile.type > TYPE_MAX) { if (profile.type < 0 || profile.type > TYPE_MAX) { Loading Loading @@ -283,6 +300,8 @@ public final class VpnProfile implements Cloneable, Parcelable { profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); } } // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); return profile; return profile; } catch (Exception e) { } catch (Exception e) { Loading Loading @@ -330,6 +349,7 @@ public final class VpnProfile implements Cloneable, Parcelable { builder.append(VALUE_DELIMITER).append(isMetered); builder.append(VALUE_DELIMITER).append(isMetered); builder.append(VALUE_DELIMITER).append(maxMtu); builder.append(VALUE_DELIMITER).append(maxMtu); builder.append(VALUE_DELIMITER).append(areAuthParamsInline); builder.append(VALUE_DELIMITER).append(areAuthParamsInline); builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks); return builder.toString().getBytes(StandardCharsets.UTF_8); return builder.toString().getBytes(StandardCharsets.UTF_8); } } Loading Loading @@ -421,7 +441,8 @@ public final class VpnProfile implements Cloneable, Parcelable { return Objects.hash( return Objects.hash( key, type, server, username, password, dnsServers, searchDomains, routes, mppe, key, type, server, username, password, dnsServers, searchDomains, routes, mppe, l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline); proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline, isRestrictedToTestNetworks); } } /** Checks VPN profiles for interior equality. */ /** Checks VPN profiles for interior equality. */ Loading Loading @@ -453,7 +474,8 @@ public final class VpnProfile implements Cloneable, Parcelable { && isBypassable == other.isBypassable && isBypassable == other.isBypassable && isMetered == other.isMetered && isMetered == other.isMetered && maxMtu == other.maxMtu && maxMtu == other.maxMtu && areAuthParamsInline == other.areAuthParamsInline; && areAuthParamsInline == other.areAuthParamsInline && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks; } } @NonNull @NonNull Loading
services/core/java/com/android/server/connectivity/Vpn.java +26 −5 Original line number Original line Diff line number Diff line Loading @@ -65,6 +65,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.DetailedState; import android.net.NetworkProvider; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.RouteInfo; import android.net.RouteInfo; import android.net.UidRange; import android.net.UidRange; import android.net.VpnManager; import android.net.VpnManager; Loading Loading @@ -2225,12 +2226,27 @@ public class Vpn { @Override @Override public void run() { public void run() { // Explicitly use only the network that ConnectivityService thinks is the "best." In // Unless the profile is restricted to test networks, explicitly use only the network // other words, only ever use the currently selected default network. This does mean // that ConnectivityService thinks is the "best." In other words, only ever use the // that in both onLost() and onConnected(), any old sessions MUST be torn down. This // currently selected default network. This does mean that in both onLost() and // does NOT include VPNs. // onConnected(), any old sessions MUST be torn down. This does NOT include VPNs. // // When restricted to test networks, select any network with TRANSPORT_TEST. Since the // creator of the profile and the test network creator both have MANAGE_TEST_NETWORKS, // this is considered safe. final ConnectivityManager cm = ConnectivityManager.from(mContext); final ConnectivityManager cm = ConnectivityManager.from(mContext); cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback); final NetworkRequest req; if (mProfile.isRestrictedToTestNetworks()) { req = new NetworkRequest.Builder() .clearCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_TEST) .build(); } else { req = cm.getDefaultRequest(); } cm.requestNetwork(req, mNetworkCallback); } } private boolean isActiveNetwork(@Nullable Network network) { private boolean isActiveNetwork(@Nullable Network network) { Loading Loading @@ -2868,6 +2884,11 @@ public class Vpn { verifyCallingUidAndPackage(packageName); verifyCallingUidAndPackage(packageName); enforceNotRestrictedUser(); enforceNotRestrictedUser(); if (profile.isRestrictedToTestNetworks) { mContext.enforceCallingPermission(Manifest.permission.MANAGE_TEST_NETWORKS, "Test-mode profiles require the MANAGE_TEST_NETWORKS permission"); } final byte[] encodedProfile = profile.encode(); final byte[] encodedProfile = profile.encode(); if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { throw new IllegalArgumentException("Profile too big"); throw new IllegalArgumentException("Profile too big"); Loading
tests/net/java/com/android/internal/net/VpnProfileTest.java +40 −7 Original line number Original line Diff line number Diff line Loading @@ -33,7 +33,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; /** Unit tests for {@link VpnProfile}. */ /** Unit tests for {@link VpnProfile}. */ @SmallTest @SmallTest Loading @@ -41,6 +43,9 @@ import java.util.Arrays; public class VpnProfileTest { public class VpnProfileTest { private static final String DUMMY_PROFILE_KEY = "Test"; private static final String DUMMY_PROFILE_KEY = "Test"; private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; @Test @Test public void testDefaults() throws Exception { public void testDefaults() throws Exception { final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); Loading @@ -67,10 +72,11 @@ public class VpnProfileTest { assertFalse(p.isMetered); assertFalse(p.isMetered); assertEquals(1360, p.maxMtu); assertEquals(1360, p.maxMtu); assertFalse(p.areAuthParamsInline); assertFalse(p.areAuthParamsInline); assertFalse(p.isRestrictedToTestNetworks); } } private VpnProfile getSampleIkev2Profile(String key) { private VpnProfile getSampleIkev2Profile(String key) { final VpnProfile p = new VpnProfile(key); final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */); p.name = "foo"; p.name = "foo"; p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; Loading Loading @@ -116,7 +122,7 @@ public class VpnProfileTest { @Test @Test public void testParcelUnparcel() { public void testParcelUnparcel() { assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22); assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); } } @Test @Test Loading Loading @@ -159,14 +165,41 @@ public class VpnProfileTest { assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); } } private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) { // Sort to ensure when we remove, we can do it from greatest first. Arrays.sort(missingIndices); final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode()); final List<String> parts = new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER))); // Remove from back first to ensure indexing is consistent. for (int i = missingIndices.length - 1; i >= 0; i--) { parts.remove(missingIndices[i]); } return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0])); } @Test @Test public void testEncodeDecodeInvalidNumberOfValues() { public void testEncodeDecodeInvalidNumberOfValues() { final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); final String tooFewValues = final String encoded = new String(profile.encode()); getEncodedDecodedIkev2ProfileMissingValues( final byte[] tooFewValues = ENCODED_INDEX_AUTH_PARAMS_INLINE, encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes(); ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues)); assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); } @Test public void testEncodeDecodeMissingIsRestrictedToTestNetworks() { final String tooFewValues = getEncodedDecodedIkev2ProfileMissingValues( ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); // Verify decoding without isRestrictedToTestNetworks defaults to false final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); assertFalse(decoded.isRestrictedToTestNetworks); } } @Test @Test Loading