diff options
8 files changed, 193 insertions, 19 deletions
diff --git a/core/java/android/net/VpnTransportInfo.java b/core/java/android/net/VpnTransportInfo.java new file mode 100644 index 000000000000..082fa58f8ac2 --- /dev/null +++ b/core/java/android/net/VpnTransportInfo.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import com.android.internal.util.MessageUtils; + +import java.util.Objects; + +/** @hide */ +public final class VpnTransportInfo implements TransportInfo, Parcelable { + private static final SparseArray<String> sTypeToString = + MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); + + /** Type of this VPN. */ + @VpnManager.VpnType public final int type; + + public VpnTransportInfo(@VpnManager.VpnType int type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VpnTransportInfo)) return false; + + VpnTransportInfo that = (VpnTransportInfo) o; + return this.type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); + return String.format("VpnTransportInfo{%s}", typeString); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(type); + } + + public static final @NonNull Creator<VpnTransportInfo> CREATOR = + new Creator<VpnTransportInfo>() { + public VpnTransportInfo createFromParcel(Parcel in) { + return new VpnTransportInfo(in.readInt()); + } + public VpnTransportInfo[] newArray(int size) { + return new VpnTransportInfo[size]; + } + }; +} diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 55b2c3c9e11f..9d67f0b84367 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -762,12 +762,14 @@ public final class NetworkCapabilities implements Parcelable { final int originalSignalStrength = mSignalStrength; final int originalOwnerUid = getOwnerUid(); final int[] originalAdministratorUids = getAdministratorUids(); + final TransportInfo originalTransportInfo = getTransportInfo(); clearAll(); mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) | (1 << TRANSPORT_TEST); mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; mNetworkSpecifier = originalSpecifier; mSignalStrength = originalSignalStrength; + mTransportInfo = originalTransportInfo; // Only retain the owner and administrator UIDs if they match the app registering the remote // caller that registered the network. diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/packages/Connectivity/framework/src/android/net/VpnManager.java index 1812509ba6d2..1e30283a9e6c 100644 --- a/packages/Connectivity/framework/src/android/net/VpnManager.java +++ b/packages/Connectivity/framework/src/android/net/VpnManager.java @@ -55,13 +55,29 @@ import java.security.GeneralSecurityException; public class VpnManager { /** Type representing a lack of VPN @hide */ public static final int TYPE_VPN_NONE = -1; - /** VPN service type code @hide */ + + /** + * A VPN created by an app using the {@link VpnService} API. + * @hide + */ public static final int TYPE_VPN_SERVICE = 1; - /** Platform VPN type code @hide */ + + /** + * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}. + * @hide + */ public static final int TYPE_VPN_PLATFORM = 2; + /** + * An IPsec VPN created by the built-in LegacyVpnRunner. + * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead. + * @hide + */ + @Deprecated + public static final int TYPE_VPN_LEGACY = 3; + /** @hide */ - @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) public @interface VpnType {} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 5164437b6852..f59b9fc4ef46 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -8505,7 +8505,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); // Only VpnService based VPNs should be able to get this information. - if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) { + if (vpn != null && vpn.getActiveVpnType() != VpnManager.TYPE_VPN_SERVICE) { throw new SecurityException( "getConnectionOwnerUid() not allowed for non-VpnService VPNs"); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index fc2c7e01efde..33c19b1ca2b2 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -74,6 +74,7 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.VpnManager; import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.ChildSessionParams; @@ -435,6 +436,7 @@ public class Vpn { mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); loadAlwaysOnPackage(keyStore); } @@ -929,6 +931,7 @@ public class Vpn { jniReset(mInterface); mInterface = null; mNetworkCapabilities.setUids(null); + mNetworkCapabilities.setTransportInfo(null); } // Revoke the connection or stop the VpnRunner. @@ -999,6 +1002,8 @@ public class Vpn { case VpnManager.TYPE_VPN_SERVICE: toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN}; break; + case VpnManager.TYPE_VPN_LEGACY: + return false; default: Log.wtf(TAG, "Unrecognized VPN type while granting authorization"); return false; @@ -1029,6 +1034,8 @@ public class Vpn { return isVpnServicePreConsented(context, packageName); case VpnManager.TYPE_VPN_PLATFORM: return isVpnProfilePreConsented(context, packageName); + case VpnManager.TYPE_VPN_LEGACY: + return VpnConfig.LEGACY_VPN.equals(packageName); default: return false; } @@ -1211,6 +1218,8 @@ public class Vpn { mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId, mConfig.allowedApplications, mConfig.disallowedApplications)); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType())); + // Only apps targeting Q and above can explicitly declare themselves as metered. // These VPNs are assumed metered unless they state otherwise. if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) { @@ -1736,6 +1745,7 @@ public class Vpn { private void cleanupVpnStateLocked() { mStatusIntent = null; mNetworkCapabilities.setUids(null); + mNetworkCapabilities.setTransportInfo(null); mConfig = null; mInterface = null; @@ -1846,22 +1856,18 @@ public class Vpn { } /** - * Gets the currently running App-based VPN type + * Gets the currently running VPN type * - * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an - * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always + * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a + * VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always * Settings-based, the Platform VPNs can be initiated by both apps and Settings. */ - public synchronized int getActiveAppVpnType() { - if (VpnConfig.LEGACY_VPN.equals(mPackage)) { - return VpnManager.TYPE_VPN_NONE; - } - - if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) { - return VpnManager.TYPE_VPN_PLATFORM; - } else { - return VpnManager.TYPE_VPN_SERVICE; - } + public synchronized int getActiveVpnType() { + if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE; + if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE; + return mVpnRunner instanceof IkeV2VpnRunner + ? VpnManager.TYPE_VPN_PLATFORM + : VpnManager.TYPE_VPN_LEGACY; } private void updateAlwaysOnNotification(DetailedState networkState) { diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java new file mode 100644 index 000000000000..2fd5e3861cef --- /dev/null +++ b/tests/net/java/android/net/VpnTransportInfoTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 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; + +import static com.android.testutils.ParcelUtils.assertParcelSane; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VpnTransportInfoTest { + + @Test + public void testParceling() { + VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + assertParcelSane(v, 1 /* fieldCount */); + assertParcelingIsLossless(v); + } + + @Test + public void testEqualsAndHashCode() { + VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE); + VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + assertNotEquals(v1, v2); + assertEquals(v1, v3); + assertEquals(v1.hashCode(), v3.hashCode()); + } +}
\ No newline at end of file diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index ecdc62174ec0..d5d7b475fdb5 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -205,6 +205,7 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; +import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; @@ -1110,7 +1111,7 @@ public class ConnectivityServiceTest { } @Override - public int getActiveAppVpnType() { + public int getActiveVpnType() { return mVpnType; } @@ -1123,10 +1124,12 @@ public class ConnectivityServiceTest { private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp) throws Exception { if (mAgentRegistered) throw new IllegalStateException("already registered"); + updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent"); mConfig = new VpnConfig(); setUids(uids); if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); mInterface = VPN_IFNAME; + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType())); mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, mNetworkCapabilities); mMockNetworkAgent.waitForIdle(TIMEOUT_MS); @@ -7322,6 +7325,7 @@ public class ConnectivityServiceTest { } private void establishLegacyLockdownVpn() throws Exception { + mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); // The legacy lockdown VPN only supports userId 0. final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); mMockVpn.registerAgent(ranges); @@ -7434,6 +7438,9 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); + VpnTransportInfo ti = (VpnTransportInfo) vpnNc.getTransportInfo(); + assertNotNull(ti); + assertEquals(VpnManager.TYPE_VPN_LEGACY, ti.type); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 73cc9f129e79..cffd2d1d428f 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -25,6 +25,7 @@ import static android.net.ConnectivityManager.NetworkCallback; 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.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -74,6 +75,7 @@ import android.net.UidRange; import android.net.UidRangeParcel; import android.net.VpnManager; import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.os.Build.VERSION_CODES; @@ -984,6 +986,13 @@ public class VpnTest { startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve } + private void assertTransportInfoMatches(NetworkCapabilities nc, int type) { + assertNotNull(nc); + VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo(); + assertNotNull(ti); + assertEquals(type, ti.type); + } + public void startRacoon(final String serverAddr, final String expectedAddr) throws Exception { final ConditionVariable legacyRunnerReady = new ConditionVariable(); @@ -1020,8 +1029,10 @@ public class VpnTest { // Now wait for the runner to be ready before testing for the route. ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + ArgumentCaptor<NetworkCapabilities> ncCaptor = + ArgumentCaptor.forClass(NetworkCapabilities.class); verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), - lpCaptor.capture(), any(), anyInt(), any(), anyInt()); + lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt()); // In this test the expected address is always v4 so /32. // Note that the interface needs to be specified because RouteInfo objects stored in @@ -1031,6 +1042,8 @@ public class VpnTest { final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes(); assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes, actualRoutes.contains(expectedRoute)); + + assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY); } finally { // Now interrupt the thread, unblock the runner and clean up. vpn.mVpnRunner.exitVpnRunner(); |