From adbf1d029b753fabc2a7a5ad3b22d3d416cecdd9 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Mon, 26 Feb 2018 11:52:46 +0900 Subject: [PATCH] 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 | 81 ++++++++++++++++-- .../java/com/android/server/connectivity/Vpn.java | 52 +++++++++++- tests/net/java/android/net/IpPrefixTest.java | 51 ++++++++++- tests/net/java/android/net/NetworkUtilsTest.java | 99 ++++++++++++++++++++++ .../com/android/server/connectivity/VpnTest.java | 78 ++++++++++++++++- 6 files changed, 398 insertions(+), 10 deletions(-) diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 6e2654e3ce7c..4631c565962f 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 @@ -187,6 +188,20 @@ public final class IpPrefix implements Parcelable { } /** + * 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 */ public boolean isIPv6() { @@ -230,6 +245,38 @@ public final class IpPrefix implements Parcelable { } /** + * 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. */ public static final Creator CREATOR = diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index fe9563d6d325..9a5d502673f3 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -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. @@ -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/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index bb46d5e23696..c9bdcf1a73b5 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -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; @@ -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; @@ -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; @@ -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 @@ -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 prefixLengthComparator = IpPrefix.lengthComparator(); + TreeSet ipv4Prefixes = new TreeSet<>(prefixLengthComparator); + TreeSet 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); diff --git a/tests/net/java/android/net/IpPrefixTest.java b/tests/net/java/android/net/IpPrefixTest.java index b5b2c07cbc4e..1f1ba2e6a603 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"); @@ -251,6 +251,53 @@ public class IpPrefixTest { } @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); Random random = new Random(); diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 8d51c3b01258..a5ee8e37553d 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 1dbf9b2dfced..f59850d45ae3 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)); + } } -- 2.11.0