From 7c600431b52fa0dc2de5e06c189c34dbbe1e86dd Mon Sep 17 00:00:00 2001 From: Pavel Maltsev Date: Thu, 1 Feb 2018 11:16:02 -0800 Subject: [PATCH 1/3] Extend network request to query networks w/o capabilites Add another bit mask into NetworkCapabilities class that represents unwanted capabilities and corresponding methods to add and remove them. Bug: 72828388 Bug: 68762530 Test: runtest -x frameworks/base/tests/net/ Change-Id: Ie291167eb74fdb696c7ee145c8cc46ea8115d6ba --- .../java/android/net/NetworkCapabilities.java | 142 ++++++++++++++---- core/java/android/net/NetworkRequest.java | 24 ++- .../android/net/NetworkCapabilitiesTest.java | 120 +++++++++++++++ 3 files changed, 257 insertions(+), 29 deletions(-) diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index d8e17412ba..07e4ef3035 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.net.ConnectivityManager.NetworkCallback; import android.os.Parcel; import android.os.Parcelable; @@ -68,6 +69,7 @@ public final class NetworkCapabilities implements Parcelable { mSignalStrength = nc.mSignalStrength; mUids = nc.mUids; mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; + mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; } } @@ -77,7 +79,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public void clearAll() { - mNetworkCapabilities = mTransportTypes = 0; + mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; @@ -91,6 +93,11 @@ public final class NetworkCapabilities implements Parcelable { */ private long mNetworkCapabilities; + /** + * If any capabilities specified here they must not exist in the matching Network. + */ + private long mUnwantedNetworkCapabilities; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "NET_CAPABILITY_" }, value = { @@ -338,31 +345,55 @@ public final class NetworkCapabilities implements Parcelable { * Adds the given capability to this {@code NetworkCapability} instance. * Multiple capabilities may be applied sequentially. Note that when searching * for a network to satisfy a request, all capabilities requested must be satisfied. + *

+ * If the given capability was previously added to the list of unwanted capabilities + * then the capability will also be removed from the list of unwanted capabilities. * * @param capability the capability to be added. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ public NetworkCapabilities addCapability(@NetCapability int capability) { - if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { - throw new IllegalArgumentException("NetworkCapability out of range"); - } + checkValidCapability(capability); mNetworkCapabilities |= 1 << capability; + mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list return this; } + /** + * Adds the given capability to the list of unwanted capabilities of this + * {@code NetworkCapability} instance. Multiple unwanted capabilities may be applied + * sequentially. Note that when searching for a network to satisfy a request, the network + * must not contain any capability from unwanted capability list. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * @hide + */ + public void addUnwantedCapability(@NetCapability int capability) { + checkValidCapability(capability); + mUnwantedNetworkCapabilities |= 1 << capability; + mNetworkCapabilities &= ~(1 << capability); // remove from requested capabilities + } + /** * Removes (if found) the given capability from this {@code NetworkCapability} instance. + *

+ * Note that this method removes capabilities that was added via {@link #addCapability(int)}, + * {@link #addUnwantedCapability(int)} or {@link #setCapabilities(int[], int[])} . * * @param capability the capability to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ public NetworkCapabilities removeCapability(@NetCapability int capability) { - if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { - throw new IllegalArgumentException("NetworkCapability out of range"); - } - mNetworkCapabilities &= ~(1 << capability); + checkValidCapability(capability); + final long mask = ~(1 << capability); + mNetworkCapabilities &= mask; + mUnwantedNetworkCapabilities &= mask; return this; } @@ -391,31 +422,58 @@ public final class NetworkCapabilities implements Parcelable { return BitUtils.unpackBits(mNetworkCapabilities); } + /** + * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance. + * + * @return an array of unwanted capability values for this instance. + * @hide + */ + public @NetCapability int[] getUnwantedCapabilities() { + return BitUtils.unpackBits(mUnwantedNetworkCapabilities); + } + + /** * Sets all the capabilities set on this {@code NetworkCapability} instance. * This overwrites any existing capabilities. * * @hide */ - public void setCapabilities(@NetCapability int[] capabilities) { + public void setCapabilities(@NetCapability int[] capabilities, + @NetCapability int[] unwantedCapabilities) { mNetworkCapabilities = BitUtils.packBits(capabilities); + mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities); } /** - * Tests for the presence of a capabilitity on this instance. + * @deprecated use {@link #setCapabilities(int[], int[])} + * @hide + */ + @Deprecated + public void setCapabilities(@NetCapability int[] capabilities) { + setCapabilities(capabilities, new int[] {}); + } + + /** + * Tests for the presence of a capability on this instance. * * @param capability the capabilities to be tested for. * @return {@code true} if set on this instance. */ public boolean hasCapability(@NetCapability int capability) { - if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { - return false; - } - return ((mNetworkCapabilities & (1 << capability)) != 0); + return isValidCapability(capability) + && ((mNetworkCapabilities & (1 << capability)) != 0); + } + + /** @hide */ + public boolean hasUnwantedCapability(@NetCapability int capability) { + return isValidCapability(capability) + && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); } private void combineNetCapabilities(NetworkCapabilities nc) { this.mNetworkCapabilities |= nc.mNetworkCapabilities; + this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities; } /** @@ -426,7 +484,9 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public String describeFirstNonRequestableCapability() { - final long nonRequestable = (mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES); + final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities) + & NON_REQUESTABLE_CAPABILITIES; + if (nonRequestable != 0) { return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]); } @@ -436,21 +496,29 @@ public final class NetworkCapabilities implements Parcelable { } private boolean satisfiedByNetCapabilities(NetworkCapabilities nc, boolean onlyImmutable) { - long networkCapabilities = this.mNetworkCapabilities; + long requestedCapabilities = mNetworkCapabilities; + long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities; + long providedCapabilities = nc.mNetworkCapabilities; + if (onlyImmutable) { - networkCapabilities = networkCapabilities & ~MUTABLE_CAPABILITIES; + requestedCapabilities &= ~MUTABLE_CAPABILITIES; + requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES; } - return ((nc.mNetworkCapabilities & networkCapabilities) == networkCapabilities); + return ((providedCapabilities & requestedCapabilities) == requestedCapabilities) + && ((requestedUnwantedCapabilities & providedCapabilities) == 0); } /** @hide */ public boolean equalsNetCapabilities(NetworkCapabilities nc) { - return (nc.mNetworkCapabilities == this.mNetworkCapabilities); + return (nc.mNetworkCapabilities == this.mNetworkCapabilities) + && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities); } private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) { return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == - (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); + (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)) + && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == + (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); } /** @@ -1178,15 +1246,17 @@ public final class NetworkCapabilities implements Parcelable { @Override public int hashCode() { - return ((int) (mNetworkCapabilities & 0xFFFFFFFF) + return (int) (mNetworkCapabilities & 0xFFFFFFFF) + ((int) (mNetworkCapabilities >> 32) * 3) - + ((int) (mTransportTypes & 0xFFFFFFFF) * 5) - + ((int) (mTransportTypes >> 32) * 7) - + (mLinkUpBandwidthKbps * 11) - + (mLinkDownBandwidthKbps * 13) - + Objects.hashCode(mNetworkSpecifier) * 17 - + (mSignalStrength * 19) - + Objects.hashCode(mUids) * 23); + + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5) + + ((int) (mUnwantedNetworkCapabilities >> 32) * 7) + + ((int) (mTransportTypes & 0xFFFFFFFF) * 11) + + ((int) (mTransportTypes >> 32) * 13) + + (mLinkUpBandwidthKbps * 17) + + (mLinkDownBandwidthKbps * 19) + + Objects.hashCode(mNetworkSpecifier) * 23 + + (mSignalStrength * 29) + + Objects.hashCode(mUids) * 31; } @Override @@ -1196,6 +1266,7 @@ public final class NetworkCapabilities implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); + dest.writeLong(mUnwantedNetworkCapabilities); dest.writeLong(mTransportTypes); dest.writeInt(mLinkUpBandwidthKbps); dest.writeInt(mLinkDownBandwidthKbps); @@ -1211,6 +1282,7 @@ public final class NetworkCapabilities implements Parcelable { NetworkCapabilities netCap = new NetworkCapabilities(); netCap.mNetworkCapabilities = in.readLong(); + netCap.mUnwantedNetworkCapabilities = in.readLong(); netCap.mTransportTypes = in.readLong(); netCap.mLinkUpBandwidthKbps = in.readInt(); netCap.mLinkDownBandwidthKbps = in.readInt(); @@ -1239,6 +1311,11 @@ public final class NetworkCapabilities implements Parcelable { appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); } + if (0 != mNetworkCapabilities) { + sb.append(" Unwanted: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, + NetworkCapabilities::capabilityNameOf, "&"); + } if (mLinkUpBandwidthKbps > 0) { sb.append(" LinkUpBandwidth>=").append(mLinkUpBandwidthKbps).append("Kbps"); } @@ -1364,4 +1441,13 @@ public final class NetworkCapabilities implements Parcelable { Preconditions.checkArgument( isValidTransport(transport), "Invalid TransportType " + transport); } + + private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { + return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY; + } + + private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { + Preconditions.checkArgument(isValidCapability(capability), + "NetworkCapability " + capability + "out of range"); + } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 97ded2d73b..a3bf68244e 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -157,6 +157,9 @@ public class NetworkRequest implements Parcelable { * the requested network's required capabilities. Note that when searching * for a network to satisfy a request, all capabilities requested must be * satisfied. + *

+ * If the given capability was previously added to the list of unwanted capabilities + * then the capability will also be removed from the list of unwanted capabilities. * * @param capability The capability to add. * @return The builder to facilitate chaining @@ -168,7 +171,8 @@ public class NetworkRequest implements Parcelable { } /** - * Removes (if found) the given capability from this builder instance. + * Removes (if found) the given capability from this builder instance from both required + * and unwanted capabilities lists. * * @param capability The capability to remove. * @return The builder to facilitate chaining. @@ -192,6 +196,24 @@ public class NetworkRequest implements Parcelable { return this; } + /** + * Add a capability that must not exist in the requested network. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * + * @param capability The capability to add to unwanted capability list. + * @return The builder to facilitate chaining. + * @hide + */ + public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addUnwantedCapability(capability); + return this; + } + /** * Completely clears all the {@code NetworkCapabilities} from this builder instance, * removing even the capabilities that are set by default when the object is constructed. diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index 2414a8e22e..37b580c104 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -17,19 +17,25 @@ package android.net; import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -275,6 +281,120 @@ public class NetworkCapabilitiesTest { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); } + @Test + public void testUnwantedCapabilities() { + NetworkCapabilities network = new NetworkCapabilities(); + + NetworkCapabilities request = new NetworkCapabilities(); + assertTrue("Request: " + request + ", Network:" + network, + request.satisfiedByNetworkCapabilities(network)); + + // Adding capabilities that doesn't exist in the network anyway + request.addUnwantedCapability(NET_CAPABILITY_WIFI_P2P); + request.addUnwantedCapability(NET_CAPABILITY_NOT_METERED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + assertArrayEquals(new int[] {NET_CAPABILITY_WIFI_P2P, NET_CAPABILITY_NOT_METERED}, + request.getUnwantedCapabilities()); + + // This is a default capability, just want to make sure its there because we use it below. + assertTrue(network.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Verify that adding unwanted capability will effectively remove it from capability list. + request.addUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.hasUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertFalse(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + + // Now this request won't be satisfied because network contains NOT_RESTRICTED. + assertFalse(request.satisfiedByNetworkCapabilities(network)); + network.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + + // Verify that adding capability will effectively remove it from unwanted list + request.addCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertFalse(request.hasUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + assertFalse(request.satisfiedByNetworkCapabilities(network)); + network.addCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + } + + @Test + public void testEqualsNetCapabilities() { + int CAPABILITY = NET_CAPABILITY_MMS; // An arbitrary not mutable capability. + + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + assertTrue(nc1.equalsNetCapabilities(nc2)); + assertEquals(nc1, nc2); + + nc1.addCapability(CAPABILITY); + assertFalse(nc1.equalsNetCapabilities(nc2)); + assertNotEquals(nc1, nc2); + nc2.addCapability(CAPABILITY); + assertTrue(nc1.equalsNetCapabilities(nc2)); + assertEquals(nc1, nc2); + + nc1.addUnwantedCapability(CAPABILITY); + assertFalse(nc1.equalsNetCapabilities(nc2)); + nc2.addUnwantedCapability(CAPABILITY); + assertTrue(nc1.equalsNetCapabilities(nc2)); + + nc1.removeCapability(CAPABILITY); + assertFalse(nc1.equalsNetCapabilities(nc2)); + nc2.removeCapability(CAPABILITY); + assertTrue(nc1.equalsNetCapabilities(nc2)); + } + + @Test + public void testCombineCapabilities() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + + nc1.addUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + nc1.addCapability(NET_CAPABILITY_NOT_ROAMING); + assertNotEquals(nc1, nc2); + nc2.combineCapabilities(nc1); + assertEquals(nc1, nc2); + assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL)); + + // This will effectively move NOT_ROAMING capability from required to unwanted for nc1. + nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING); + + nc2.combineCapabilities(nc1); + // We will get this capability in both requested and unwanted lists thus this request + // will never be satisfied. + assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); + } + + @Test + public void testSetCapabilities() { + final int[] REQUIRED_CAPABILITIES = new int[] { + NET_CAPABILITY_INTERNET, NET_CAPABILITY_NOT_VPN }; + final int[] UNWANTED_CAPABILITIES = new int[] { + NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_METERED + }; + + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + + nc1.setCapabilities(REQUIRED_CAPABILITIES, UNWANTED_CAPABILITIES); + assertArrayEquals(REQUIRED_CAPABILITIES, nc1.getCapabilities()); + + // Verify that setting and adding capabilities leads to the same object state. + nc2.clearAll(); + for (int cap : REQUIRED_CAPABILITIES) { + nc2.addCapability(cap); + } + for (int cap : UNWANTED_CAPABILITIES) { + nc2.addUnwantedCapability(cap); + } + assertEquals(nc1, nc2); + } + private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) { Parcel p = Parcel.obtain(); netCap.writeToParcel(p, /* flags */ 0); From 9cbc882c085085264454ea8a4558d2e4ef8faf73 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Mon, 26 Feb 2018 11:52:46 +0900 Subject: [PATCH 2/3] Give VPNs the INTERNET capability when they route most of the IP space Test: manual, plus wrote some new tests for this Bug: 72765718 Change-Id: I9759da72b752fd8eeb1d0647db9ab341f04c0528 --- core/java/android/net/IpPrefix.java | 47 +++++++++ core/java/android/net/NetworkUtils.java | 87 ++++++++++++++-- tests/net/java/android/net/IpPrefixTest.java | 51 +++++++++- .../java/android/net/NetworkUtilsTest.java | 99 +++++++++++++++++++ .../android/server/connectivity/VpnTest.java | 78 ++++++++++++++- 5 files changed, 350 insertions(+), 12 deletions(-) diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 6e2654e3ce..4631c56596 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -25,6 +25,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Comparator; /** * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a @@ -186,6 +187,20 @@ public final class IpPrefix implements Parcelable { return Arrays.equals(this.address, addrBytes); } + /** + * Returns whether the specified prefix is entirely contained in this prefix. + * + * Note this is mathematical inclusion, so a prefix is always contained within itself. + * @param otherPrefix the prefix to test + * @hide + */ + public boolean containsPrefix(IpPrefix otherPrefix) { + if (otherPrefix.getPrefixLength() < prefixLength) return false; + final byte[] otherAddress = otherPrefix.getRawAddress(); + NetworkUtils.maskRawAddress(otherAddress, prefixLength); + return Arrays.equals(otherAddress, address); + } + /** * @hide */ @@ -229,6 +244,38 @@ public final class IpPrefix implements Parcelable { dest.writeInt(prefixLength); } + /** + * Returns a comparator ordering IpPrefixes by length, shorter to longer. + * Contents of the address will break ties. + * @hide + */ + public static Comparator lengthComparator() { + return new Comparator() { + @Override + public int compare(IpPrefix prefix1, IpPrefix prefix2) { + if (prefix1.isIPv4()) { + if (prefix2.isIPv6()) return -1; + } else { + if (prefix2.isIPv4()) return 1; + } + final int p1len = prefix1.getPrefixLength(); + final int p2len = prefix2.getPrefixLength(); + if (p1len < p2len) return -1; + if (p2len < p1len) return 1; + final byte[] a1 = prefix1.address; + final byte[] a2 = prefix2.address; + final int len = a1.length < a2.length ? a1.length : a2.length; + for (int i = 0; i < len; ++i) { + if (a1[i] < a2[i]) return -1; + if (a1[i] > a2[i]) return 1; + } + if (a2.length < len) return 1; + if (a1.length < len) return -1; + return 0; + } + }; + } + /** * Implement the Parcelable interface. */ diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index fe9563d6d3..9a5d502673 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -16,19 +16,20 @@ package android.net; -import java.io.FileDescriptor; -import java.net.InetAddress; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Collection; -import java.util.Locale; - import android.os.Parcel; import android.util.Log; import android.util.Pair; +import java.io.FileDescriptor; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Locale; +import java.util.TreeSet; /** * Native methods for managing network interfaces. @@ -385,4 +386,72 @@ public class NetworkUtils { result = builder.toString(); return result; } + + /** + * Returns a prefix set without overlaps. + * + * This expects the src set to be sorted from shorter to longer. Results are undefined + * failing this condition. The returned prefix set is sorted in the same order as the + * passed set, with the same comparator. + */ + private static TreeSet deduplicatePrefixSet(final TreeSet src) { + final TreeSet dst = new TreeSet<>(src.comparator()); + // Prefixes match addresses that share their upper part up to their length, therefore + // the only kind of possible overlap in two prefixes is strict inclusion of the longer + // (more restrictive) in the shorter (including equivalence if they have the same + // length). + // Because prefixes in the src set are sorted from shorter to longer, deduplicating + // is done by simply iterating in order, and not adding any longer prefix that is + // already covered by a shorter one. + newPrefixes: + for (IpPrefix newPrefix : src) { + for (IpPrefix existingPrefix : dst) { + if (existingPrefix.containsPrefix(newPrefix)) { + continue newPrefixes; + } + } + dst.add(newPrefix); + } + return dst; + } + + /** + * Returns how many IPv4 addresses match any of the prefixes in the passed ordered set. + * + * Obviously this returns an integral value between 0 and 2**32. + * The behavior is undefined if any of the prefixes is not an IPv4 prefix or if the + * set is not ordered smallest prefix to longer prefix. + * + * @param prefixes the set of prefixes, ordered by length + */ + public static long routedIPv4AddressCount(final TreeSet prefixes) { + long routedIPCount = 0; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv4()) { + Log.wtf(TAG, "Non-IPv4 prefix in routedIPv4AddressCount"); + } + int rank = 32 - prefix.getPrefixLength(); + routedIPCount += 1L << rank; + } + return routedIPCount; + } + + /** + * Returns how many IPv6 addresses match any of the prefixes in the passed ordered set. + * + * This returns a BigInteger between 0 and 2**128. + * The behavior is undefined if any of the prefixes is not an IPv6 prefix or if the + * set is not ordered smallest prefix to longer prefix. + */ + public static BigInteger routedIPv6AddressCount(final TreeSet prefixes) { + BigInteger routedIPCount = BigInteger.ZERO; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv6()) { + Log.wtf(TAG, "Non-IPv6 prefix in routedIPv6AddressCount"); + } + int rank = 128 - prefix.getPrefixLength(); + routedIPCount = routedIPCount.add(BigInteger.ONE.shiftLeft(rank)); + } + return routedIPCount; + } } diff --git a/tests/net/java/android/net/IpPrefixTest.java b/tests/net/java/android/net/IpPrefixTest.java index b5b2c07cbc..1f1ba2e6a6 100644 --- a/tests/net/java/android/net/IpPrefixTest.java +++ b/tests/net/java/android/net/IpPrefixTest.java @@ -223,14 +223,14 @@ public class IpPrefixTest { } @Test - public void testContains() { + public void testContainsInetAddress() { IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127"); assertTrue(p.contains(Address("2001:db8:f00::ace:d00c"))); assertTrue(p.contains(Address("2001:db8:f00::ace:d00d"))); assertFalse(p.contains(Address("2001:db8:f00::ace:d00e"))); assertFalse(p.contains(Address("2001:db8:f00::bad:d00d"))); assertFalse(p.contains(Address("2001:4868:4860::8888"))); - assertFalse(p.contains(null)); + assertFalse(p.contains((InetAddress)null)); assertFalse(p.contains(Address("8.8.8.8"))); p = new IpPrefix("192.0.2.0/23"); @@ -250,6 +250,53 @@ public class IpPrefixTest { assertFalse(ipv4Default.contains(Address("2001:db8::f00"))); } + @Test + public void testContainsIpPrefix() { + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("0.0.0.0/0"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/0"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/8"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/24"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/23"))); + + assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.2.3.4/8"))); + assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.254.12.9/8"))); + assertTrue(new IpPrefix("1.2.3.4/21").containsPrefix(new IpPrefix("1.2.3.4/21"))); + assertTrue(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.4/32"))); + + assertTrue(new IpPrefix("1.2.3.4/20").containsPrefix(new IpPrefix("1.2.3.0/24"))); + + assertFalse(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.5/32"))); + assertFalse(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("2.2.3.4/8"))); + assertFalse(new IpPrefix("0.0.0.0/16").containsPrefix(new IpPrefix("0.0.0.0/15"))); + assertFalse(new IpPrefix("100.0.0.0/8").containsPrefix(new IpPrefix("99.0.0.0/8"))); + + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("::/0"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/1"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("3d8a:661:a0::770/8"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/8"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/64"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/113"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/128"))); + + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/64"))); + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/120"))); + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/32"))); + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2006:db8:f00::ace:d00d/96"))); + + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/128"))); + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/100").containsPrefix( + new IpPrefix("2001:db8:f00::ace:ccaf/110"))); + + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00e/128"))); + assertFalse(new IpPrefix("::/30").containsPrefix(new IpPrefix("::/29"))); + } + @Test public void testHashCode() { IpPrefix p = new IpPrefix(new byte[4], 0); diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 8d51c3b012..a5ee8e3755 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -19,8 +19,10 @@ package android.net; import android.net.NetworkUtils; import android.test.suitebuilder.annotation.SmallTest; +import java.math.BigInteger; import java.net.Inet4Address; import java.net.InetAddress; +import java.util.TreeSet; import junit.framework.TestCase; @@ -67,4 +69,101 @@ public class NetworkUtilsTest extends TestCase { assertInvalidNetworkMask(IPv4Address("255.255.255.253")); assertInvalidNetworkMask(IPv4Address("255.255.0.255")); } + + @SmallTest + public void testRoutedIPv4AddressCount() { + final TreeSet set = new TreeSet<>(IpPrefix.lengthComparator()); + // No routes routes to no addresses. + assertEquals(0, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("0.0.0.0/0")); + assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("20.18.0.0/16")); + set.add(new IpPrefix("20.18.0.0/24")); + set.add(new IpPrefix("20.18.0.0/8")); + // There is a default route, still covers everything + assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("20.18.0.0/24")); + set.add(new IpPrefix("20.18.0.0/8")); + // The 8-length includes the 24-length prefix + assertEquals(1l << 24, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("10.10.10.126/25")); + // The 8-length does not include this 25-length prefix + assertEquals((1l << 24) + (1 << 7), NetworkUtils.routedIPv4AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + assertEquals(1l, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("1.2.3.5/32")); + set.add(new IpPrefix("1.2.3.6/32")); + + set.add(new IpPrefix("1.2.3.7/32")); + set.add(new IpPrefix("1.2.3.8/32")); + set.add(new IpPrefix("1.2.3.9/32")); + set.add(new IpPrefix("1.2.3.0/32")); + assertEquals(7l, NetworkUtils.routedIPv4AddressCount(set)); + + // 1.2.3.4/30 eats 1.2.3.{4-7}/32 + set.add(new IpPrefix("1.2.3.4/30")); + set.add(new IpPrefix("6.2.3.4/28")); + set.add(new IpPrefix("120.2.3.4/16")); + assertEquals(7l - 4 + 4 + 16 + 65536, NetworkUtils.routedIPv4AddressCount(set)); + } + + @SmallTest + public void testRoutedIPv6AddressCount() { + final TreeSet set = new TreeSet<>(IpPrefix.lengthComparator()); + // No routes routes to no addresses. + assertEquals(BigInteger.ZERO, NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("::/0")); + assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("1234:622a::18/64")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8")); + // There is a default route, still covers everything + assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8")); + // The 8-length includes the 96-length prefix + assertEquals(BigInteger.ONE.shiftLeft(120), NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("10::26/64")); + // The 8-length does not include this 64-length prefix + assertEquals(BigInteger.ONE.shiftLeft(120).add(BigInteger.ONE.shiftLeft(64)), + NetworkUtils.routedIPv6AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + assertEquals(BigInteger.ONE, NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad5/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad6/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad7/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad8/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad9/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad0/128")); + assertEquals(BigInteger.valueOf(7), NetworkUtils.routedIPv6AddressCount(set)); + + // add4:f00:80:f7:1111::6ad4/126 eats add4:f00:8[:f7:1111::6ad{4-7}/128 + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/126")); + set.add(new IpPrefix("d00d:f00:80:f7:1111::6ade/124")); + set.add(new IpPrefix("f00b:a33::/112")); + assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), + NetworkUtils.routedIPv6AddressCount(set)); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 1dbf9b2dfc..f59850d45a 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -57,9 +57,13 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.IpPrefix; +import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; +import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnService; import android.os.Build.VERSION_CODES; @@ -90,7 +94,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; - +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Tests for {@link Vpn}. @@ -563,4 +568,75 @@ public class VpnTest { return networks.get(network); }).when(mConnectivityManager).getNetworkCapabilities(any()); } + + // Need multiple copies of this, but Java's Stream objects can't be reused or + // duplicated. + private Stream publicIpV4Routes() { + return Stream.of( + "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", + "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", + "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", + "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", + "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", + "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", + "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"); + } + + private Stream publicIpV6Routes() { + return Stream.of( + "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6", + "fe00::/8", "2605:ef80:e:af1d::/64"); + } + + @Test + public void testProvidesRoutesToMostDestinations() { + final LinkProperties lp = new LinkProperties(); + + // Default route provides routes to all IPv4 destinations. + lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + // Empty LP provides routes to no destination + lp.clear(); + assertFalse(Vpn.providesRoutesToMostDestinations(lp)); + + // All IPv4 routes except for local networks. This is the case most relevant + // to this function. It provides routes to almost the entire space. + // (clone the stream so that we can reuse it later) + publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to + // provide routes to "most" destinations. + lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + // Remove the /2 route, which represent a quarter of the available routing space. + // This LP does not provides routes to "most" destinations any more. + lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); + assertFalse(Vpn.providesRoutesToMostDestinations(lp)); + + lp.clear(); + publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + lp.removeRoute(new RouteInfo(new IpPrefix("::/1"))); + assertFalse(Vpn.providesRoutesToMostDestinations(lp)); + + // V6 does not provide sufficient coverage but v4 does + publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + // V4 still does + lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + + // V4 does not any more + lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); + assertFalse(Vpn.providesRoutesToMostDestinations(lp)); + + // V4 does not, but V6 has sufficient coverage again + lp.addRoute(new RouteInfo(new IpPrefix("::/1"))); + assertTrue(Vpn.providesRoutesToMostDestinations(lp)); + } } From 9a396cc3c6402ddca1387b788c0dbc56981f05cb Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 21 Feb 2018 18:43:54 +0900 Subject: [PATCH 3/3] Give apps with NETWORK_SETTINGS right to see any VPN. ...not only the ones that apply to them. Bug: 73217368 Test: runtest frameworks-net and CTS Change-Id: I436972a3e51e98bdd815771b451bcedadf684763 --- .../java/android/net/NetworkCapabilities.java | 14 ++++-- core/java/android/net/NetworkRequest.java | 11 ++++- .../android/server/ConnectivityService.java | 49 ++++++++++++------- .../android/net/NetworkCapabilitiesTest.java | 6 ++- .../server/ConnectivityServiceTest.java | 15 +++++- 5 files changed, 68 insertions(+), 27 deletions(-) diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index d8e17412ba..b899cbf811 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -890,7 +890,16 @@ public final class NetworkCapabilities implements Parcelable { /** * List of UIDs this network applies to. No restriction if null. *

- * This is typically (and at this time, only) used by VPN. This network is only available to + * For networks, mUids represent the list of network this applies to, and null means this + * network applies to all UIDs. + * For requests, mUids is the list of UIDs this network MUST apply to to match ; ALL UIDs + * must be included in a network so that they match. As an exception to the general rule, + * a null mUids field for requests mean "no requirements" rather than what the general rule + * would suggest ("must apply to all UIDs") : this is because this has shown to be what users + * of this API expect in practice. A network that must match all UIDs can still be + * expressed with a set ranging the entire set of possible UIDs. + *

+ * mUids is typically (and at this time, only) used by VPN. This network is only available to * the UIDs in this list, and it is their default network. Apps in this list that wish to * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this * member is null, then the network is not restricted by app UID. If it's an empty list, then @@ -1012,8 +1021,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public boolean satisfiedByUids(NetworkCapabilities nc) { - if (null == nc.mUids) return true; // The network satisfies everything. - if (null == mUids) return false; // Not everything allowed but requires everything + if (null == nc.mUids || null == mUids) return true; // The network satisfies everything. for (UidRange requiredRange : mUids) { if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true; if (!nc.appliesToUidRange(requiredRange)) { diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 97ded2d73b..f1dfbd1658 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.text.TextUtils; import java.util.Objects; @@ -131,12 +132,18 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { - private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities(); + private final NetworkCapabilities mNetworkCapabilities; /** * Default constructor for Builder. */ - public Builder() {} + public Builder() { + // By default, restrict this request to networks available to this app. + // Apps can rescind this restriction, but ConnectivityService will enforce + // it for apps that do not have the NETWORK_SETTINGS permission. + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.setSingleUid(Process.myUid()); + } /** * Build {@link NetworkRequest} give the current set of capabilities. diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fd2ef18d62..3021e6a539 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -17,6 +17,7 @@ package com.android.server; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.TYPE_ETHERNET; @@ -1329,9 +1330,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { synchronized (nai) { if (nai.networkCapabilities != null) { - // TODO : don't remove the UIDs when communicating with processes - // that have the NETWORK_SETTINGS permission. - return networkCapabilitiesWithoutUids(nai.networkCapabilities); + return networkCapabilitiesWithoutUidsUnlessAllowed(nai.networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); } } } @@ -1344,10 +1344,18 @@ public class ConnectivityService extends IConnectivityManager.Stub return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network)); } - private NetworkCapabilities networkCapabilitiesWithoutUids(NetworkCapabilities nc) { + private NetworkCapabilities networkCapabilitiesWithoutUidsUnlessAllowed( + NetworkCapabilities nc, int callerPid, int callerUid) { + if (checkSettingsPermission(callerPid, callerUid)) return new NetworkCapabilities(nc); return new NetworkCapabilities(nc).setUids(null); } + private void restrictRequestUidsForCaller(NetworkCapabilities nc) { + if (!checkSettingsPermission()) { + nc.setSingleUid(Binder.getCallingUid()); + } + } + @Override public NetworkState[] getAllNetworkState() { // Require internal since we're handing out IMSI details @@ -1546,6 +1554,16 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private boolean checkSettingsPermission() { + return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.NETWORK_SETTINGS); + } + + private boolean checkSettingsPermission(int pid, int uid) { + return PERMISSION_GRANTED == mContext.checkPermission( + android.Manifest.permission.NETWORK_SETTINGS, pid, uid); + } + private void enforceTetherAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, @@ -4213,13 +4231,12 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceMeteredApnPolicy(networkCapabilities); } ensureRequestableCapabilities(networkCapabilities); - // Set the UID range for this request to the single UID of the requester. + // Set the UID range for this request to the single UID of the requester, or to an empty + // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there // are no visible methods to set the UIDs, an app could use reflection to try and get // networks for other apps so it's essential that the UIDs are overwritten. - // TODO : don't forcefully set the UID when communicating with processes - // that have the NETWORK_SETTINGS permission. - networkCapabilities.setSingleUid(Binder.getCallingUid()); + restrictRequestUidsForCaller(networkCapabilities); if (timeoutMs < 0) { throw new IllegalArgumentException("Bad timeout specified"); @@ -4293,9 +4310,7 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceMeteredApnPolicy(networkCapabilities); ensureRequestableCapabilities(networkCapabilities); ensureValidNetworkSpecifier(networkCapabilities); - // TODO : don't forcefully set the UID when communicating with processes - // that have the NETWORK_SETTINGS permission. - networkCapabilities.setSingleUid(Binder.getCallingUid()); + restrictRequestUidsForCaller(networkCapabilities); NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); @@ -4349,9 +4364,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); - // TODO : don't forcefully set the UIDs when communicating with processes - // that have the NETWORK_SETTINGS permission. - nc.setSingleUid(Binder.getCallingUid()); + restrictRequestUidsForCaller(nc); if (!ConnectivityManager.checkChangePermission(mContext)) { // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get @@ -4381,9 +4394,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureValidNetworkSpecifier(networkCapabilities); final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); - // TODO : don't forcefully set the UIDs when communicating with processes - // that have the NETWORK_SETTINGS permission. - nc.setSingleUid(Binder.getCallingUid()); + restrictRequestUidsForCaller(nc); NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); @@ -4947,8 +4958,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } case ConnectivityManager.CALLBACK_CAP_CHANGED: { // networkAgent can't be null as it has been accessed a few lines above. - final NetworkCapabilities nc = - networkCapabilitiesWithoutUids(networkAgent.networkCapabilities); + final NetworkCapabilities nc = networkCapabilitiesWithoutUidsUnlessAllowed( + networkAgent.networkCapabilities, nri.mPid, nri.mUid); putParcelable(bundle, nc); break; } diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index 2414a8e22e..b32f0fd2be 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -223,7 +223,9 @@ public class NetworkCapabilitiesTest { assertFalse(netCap.appliesToUidRange(new UidRange(60, 3400))); NetworkCapabilities netCap2 = new NetworkCapabilities(); - assertFalse(netCap2.satisfiedByUids(netCap)); + // A new netcap object has null UIDs, so anything will satisfy it. + assertTrue(netCap2.satisfiedByUids(netCap)); + // Still not equal though. assertFalse(netCap2.equalsUids(netCap)); netCap2.setUids(uids); assertTrue(netCap2.satisfiedByUids(netCap)); @@ -240,7 +242,7 @@ public class NetworkCapabilitiesTest { assertTrue(netCap.appliesToUid(650)); assertFalse(netCap.appliesToUid(500)); - assertFalse(new NetworkCapabilities().satisfiedByUids(netCap)); + assertTrue(new NetworkCapabilities().satisfiedByUids(netCap)); netCap.combineCapabilities(new NetworkCapabilities()); assertTrue(netCap.appliesToUid(500)); assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000))); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 28f8122002..5ea21ea4f1 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -387,6 +387,7 @@ public class ConnectivityServiceTest { mScore = 20; break; case TRANSPORT_VPN: + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN); mScore = ConnectivityConstants.VPN_DEFAULT_SCORE; break; default: @@ -3744,14 +3745,19 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback genericNotVpnNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); - final NetworkRequest genericRequest = new NetworkRequest.Builder().build(); + final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build(); + final NetworkRequest genericRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN).build(); final NetworkRequest wifiRequest = new NetworkRequest.Builder() .addTransportType(TRANSPORT_WIFI).build(); final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) .addTransportType(TRANSPORT_VPN).build(); mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); + mCm.registerNetworkCallback(genericNotVpnRequest, genericNotVpnNetworkCallback); mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); @@ -3759,6 +3765,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); vpnNetworkCallback.assertNoCallback(); @@ -3773,16 +3780,19 @@ public class ConnectivityServiceTest { vpnNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); + genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent); + genericNotVpnNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent); ranges.clear(); vpnNetworkAgent.setUids(ranges); genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); @@ -3790,18 +3800,21 @@ public class ConnectivityServiceTest { vpnNetworkAgent.setUids(ranges); genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); + genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); mWiFiNetworkAgent.disconnect(); genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); vpnNetworkCallback.assertNoCallback(); vpnNetworkAgent.disconnect(); genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent); + genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);