Loading core/java/android/net/IpPrefix.java +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 */ Loading Loading @@ -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<IpPrefix> lengthComparator() { return new Comparator<IpPrefix>() { @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. */ Loading core/java/android/net/NetworkUtils.java +75 −6 Original line number Diff line number Diff line Loading @@ -16,19 +16,20 @@ package android.net; import android.os.Parcel; import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; import java.net.InetAddress; 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 android.os.Parcel; import android.util.Log; import android.util.Pair; import java.util.TreeSet; /** * Native methods for managing network interfaces. Loading Loading @@ -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<IpPrefix> deduplicatePrefixSet(final TreeSet<IpPrefix> src) { final TreeSet<IpPrefix> 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<IpPrefix> 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<IpPrefix> 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; } } services/core/java/com/android/server/connectivity/Vpn.java +51 −1 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; Loading Loading @@ -105,6 +106,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; Loading @@ -113,6 +115,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.SortedSet; Loading @@ -131,6 +134,24 @@ public class Vpn { // the device idle whitelist during service launch and VPN bootstrap. private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000; // Settings for how much of the address space should be routed so that Vpn considers // "most" of the address space is routed. This is used to determine whether this Vpn // should be marked with the INTERNET capability. private static final long MOST_IPV4_ADDRESSES_COUNT; private static final BigInteger MOST_IPV6_ADDRESSES_COUNT; static { // 85% of the address space must be routed for Vpn to consider this VPN to provide // INTERNET access. final int howManyPercentIsMost = 85; final long twoPower32 = 1L << 32; MOST_IPV4_ADDRESSES_COUNT = twoPower32 * howManyPercentIsMost / 100; final BigInteger twoPower128 = BigInteger.ONE.shiftLeft(128); MOST_IPV6_ADDRESSES_COUNT = twoPower128 .multiply(BigInteger.valueOf(howManyPercentIsMost)) .divide(BigInteger.valueOf(100)); } // TODO: create separate trackers for each unique VPN to support // automated reconnection Loading Loading @@ -830,10 +851,39 @@ public class Vpn { return lp; } /** * Analyzes the passed LinkedProperties to figure out whether it routes to most of the IP space. * * This returns true if the passed LinkedProperties contains routes to either most of the IPv4 * space or to most of the IPv6 address space, where "most" is defined by the value of the * MOST_IPV{4,6}_ADDRESSES_COUNT constants : if more than this number of addresses are matched * by any of the routes, then it's decided that most of the space is routed. * @hide */ @VisibleForTesting static boolean providesRoutesToMostDestinations(LinkProperties lp) { final Comparator<IpPrefix> prefixLengthComparator = IpPrefix.lengthComparator(); TreeSet<IpPrefix> ipv4Prefixes = new TreeSet<>(prefixLengthComparator); TreeSet<IpPrefix> ipv6Prefixes = new TreeSet<>(prefixLengthComparator); for (final RouteInfo route : lp.getAllRoutes()) { IpPrefix destination = route.getDestination(); if (destination.isIPv4()) { ipv4Prefixes.add(destination); } else { ipv6Prefixes.add(destination); } } if (NetworkUtils.routedIPv4AddressCount(ipv4Prefixes) > MOST_IPV4_ADDRESSES_COUNT) { return true; } return NetworkUtils.routedIPv6AddressCount(ipv6Prefixes) .compareTo(MOST_IPV6_ADDRESSES_COUNT) >= 0; } private void agentConnect() { LinkProperties lp = makeLinkProperties(); if (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()) { if (providesRoutesToMostDestinations(lp)) { mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); } else { mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); Loading tests/net/java/android/net/IpPrefixTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -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"); Loading @@ -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); Loading tests/net/java/android/net/NetworkUtilsTest.java +99 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<IpPrefix> 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<IpPrefix> 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)); } } Loading
core/java/android/net/IpPrefix.java +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 */ Loading Loading @@ -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<IpPrefix> lengthComparator() { return new Comparator<IpPrefix>() { @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. */ Loading
core/java/android/net/NetworkUtils.java +75 −6 Original line number Diff line number Diff line Loading @@ -16,19 +16,20 @@ package android.net; import android.os.Parcel; import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; import java.net.InetAddress; 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 android.os.Parcel; import android.util.Log; import android.util.Pair; import java.util.TreeSet; /** * Native methods for managing network interfaces. Loading Loading @@ -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<IpPrefix> deduplicatePrefixSet(final TreeSet<IpPrefix> src) { final TreeSet<IpPrefix> 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<IpPrefix> 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<IpPrefix> 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; } }
services/core/java/com/android/server/connectivity/Vpn.java +51 −1 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; Loading Loading @@ -105,6 +106,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; Loading @@ -113,6 +115,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.SortedSet; Loading @@ -131,6 +134,24 @@ public class Vpn { // the device idle whitelist during service launch and VPN bootstrap. private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000; // Settings for how much of the address space should be routed so that Vpn considers // "most" of the address space is routed. This is used to determine whether this Vpn // should be marked with the INTERNET capability. private static final long MOST_IPV4_ADDRESSES_COUNT; private static final BigInteger MOST_IPV6_ADDRESSES_COUNT; static { // 85% of the address space must be routed for Vpn to consider this VPN to provide // INTERNET access. final int howManyPercentIsMost = 85; final long twoPower32 = 1L << 32; MOST_IPV4_ADDRESSES_COUNT = twoPower32 * howManyPercentIsMost / 100; final BigInteger twoPower128 = BigInteger.ONE.shiftLeft(128); MOST_IPV6_ADDRESSES_COUNT = twoPower128 .multiply(BigInteger.valueOf(howManyPercentIsMost)) .divide(BigInteger.valueOf(100)); } // TODO: create separate trackers for each unique VPN to support // automated reconnection Loading Loading @@ -830,10 +851,39 @@ public class Vpn { return lp; } /** * Analyzes the passed LinkedProperties to figure out whether it routes to most of the IP space. * * This returns true if the passed LinkedProperties contains routes to either most of the IPv4 * space or to most of the IPv6 address space, where "most" is defined by the value of the * MOST_IPV{4,6}_ADDRESSES_COUNT constants : if more than this number of addresses are matched * by any of the routes, then it's decided that most of the space is routed. * @hide */ @VisibleForTesting static boolean providesRoutesToMostDestinations(LinkProperties lp) { final Comparator<IpPrefix> prefixLengthComparator = IpPrefix.lengthComparator(); TreeSet<IpPrefix> ipv4Prefixes = new TreeSet<>(prefixLengthComparator); TreeSet<IpPrefix> ipv6Prefixes = new TreeSet<>(prefixLengthComparator); for (final RouteInfo route : lp.getAllRoutes()) { IpPrefix destination = route.getDestination(); if (destination.isIPv4()) { ipv4Prefixes.add(destination); } else { ipv6Prefixes.add(destination); } } if (NetworkUtils.routedIPv4AddressCount(ipv4Prefixes) > MOST_IPV4_ADDRESSES_COUNT) { return true; } return NetworkUtils.routedIPv6AddressCount(ipv6Prefixes) .compareTo(MOST_IPV6_ADDRESSES_COUNT) >= 0; } private void agentConnect() { LinkProperties lp = makeLinkProperties(); if (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()) { if (providesRoutesToMostDestinations(lp)) { mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); } else { mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); Loading
tests/net/java/android/net/IpPrefixTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -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"); Loading @@ -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); Loading
tests/net/java/android/net/NetworkUtilsTest.java +99 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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<IpPrefix> 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<IpPrefix> 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)); } }