Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 207b8e2b authored by Chalard Jean's avatar Chalard Jean Committed by android-build-merger
Browse files

Merge "Give VPNs the INTERNET capability when they route most of the IP space" am: 20013384

am: 18e51821

Change-Id: I33c6fcc72753e00af601cf915aa78416174a3871
parents 85f47b72 18e51821
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -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<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.
     */
+75 −6
Original line number Diff line number Diff line
@@ -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<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;
    }
}
+51 −1
Original line number Diff line number Diff line
@@ -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<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);
+49 −2
Original line number Diff line number Diff line
@@ -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);
+99 −0
Original line number Diff line number Diff line
@@ -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<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