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

Commit 5b976f35 authored by Mark Chien's avatar Mark Chien Committed by Gerrit Code Review
Browse files

Merge "Allow tethering pick prefix from all of private address range"

parents 9c0d523b 8a8e7e03
Loading
Loading
Loading
Loading
+162 −53
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.util.PrefixUtils.asIpPrefix;

import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;

import static java.util.Arrays.asList;

import android.content.Context;
@@ -37,9 +41,10 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -58,10 +63,6 @@ import java.util.Set;
public class PrivateAddressCoordinator {
    public static final int PREFIX_LENGTH = 24;

    private static final int MAX_UBYTE = 256;
    private static final int BYTE_MASK = 0xff;
    private static final byte DEFAULT_ID = (byte) 42;

    // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
    // address may be requested before coordinator get current upstream notification. To ensure
    // coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
@@ -69,22 +70,22 @@ public class PrivateAddressCoordinator {
    // mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams().
    private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
    private final ArraySet<IpServer> mDownstreams;
    // IANA has reserved the following three blocks of the IP address space for private intranets:
    // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
    // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
    private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
    private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
    private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
    private final IpPrefix mTetheringPrefix;
    private final List<IpPrefix> mTetheringPrefixes;
    private final ConnectivityManager mConnectivityMgr;
    private final TetheringConfiguration mConfig;
    // keyed by downstream type(TetheringManager.TETHERING_*).
    private final SparseArray<LinkAddress> mCachedAddresses;

    public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
        this(context, config, new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"))));
    }

    public PrivateAddressCoordinator(Context context, TetheringConfiguration config,
            List<IpPrefix> prefixPools) {
        mDownstreams = new ArraySet<>();
        mUpstreamPrefixMap = new ArrayMap<>();
        mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
        mConnectivityMgr = (ConnectivityManager) context.getSystemService(
                Context.CONNECTIVITY_SERVICE);
        mConfig = config;
@@ -92,6 +93,8 @@ public class PrivateAddressCoordinator {
        // Reserved static addresses for bluetooth and wifi p2p.
        mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
        mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));

        mTetheringPrefixes = prefixPools;
    }

    /**
@@ -179,52 +182,148 @@ public class PrivateAddressCoordinator {
            return cachedAddress;
        }

        // Address would be 192.168.[subAddress]/24.
        final byte[] bytes = mTetheringPrefix.getRawAddress();
        final int subAddress = getRandomSubAddr();
        final int subNet = (subAddress >> 8) & BYTE_MASK;
        bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
        for (int i = 0; i < MAX_UBYTE; i++) {
            final int newSubNet = (subNet + i) & BYTE_MASK;
            bytes[2] = (byte) newSubNet;

            final InetAddress addr;
            try {
                addr = InetAddress.getByAddress(bytes);
            } catch (UnknownHostException e) {
                throw new IllegalStateException("Invalid address, shouldn't happen.", e);
            }

            if (isConflict(new IpPrefix(addr, PREFIX_LENGTH))) continue;

        for (IpPrefix prefixRange : mTetheringPrefixes) {
            final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
            if (newAddress != null) {
                mDownstreams.add(ipServer);
            final LinkAddress newAddress = new LinkAddress(addr, PREFIX_LENGTH);
                mCachedAddresses.put(ipServer.interfaceType(), newAddress);
                return newAddress;
            }
        }

        // No available address.
        return null;
    }

    private boolean isConflict(final IpPrefix prefix) {
        // Check whether this prefix is in use or conflict with any current upstream network.
        return isDownstreamPrefixInUse(prefix) || isConflictWithUpstream(prefix);
    private int getPrefixBaseAddress(final IpPrefix prefix) {
        return inet4AddressToIntHTH((Inet4Address) prefix.getAddress());
    }

    /**
     * Check whether input prefix conflict with upstream prefixes or in-use downstream prefixes.
     * If yes, return one of them.
     */
    private IpPrefix getConflictPrefix(final IpPrefix prefix) {
        final IpPrefix upstream = getConflictWithUpstream(prefix);
        if (upstream != null) return upstream;

        return getInUseDownstreamPrefix(prefix);
    }

    // Get the next non-conflict sub prefix. E.g: To get next sub prefix from 10.0.0.0/8, if the
    // previously selected prefix is 10.20.42.0/24(subPrefix: 0.20.42.0) and the conflicting prefix
    // is 10.16.0.0/20 (10.16.0.0 ~ 10.16.15.255), then the max address under subPrefix is
    // 0.16.15.255 and the next subPrefix is 0.16.16.255/24 (0.16.15.255 + 0.0.1.0).
    // Note: the sub address 0.0.0.255 here is fine to be any value that it will be replaced as
    // selected random sub address later.
    private int getNextSubPrefix(final IpPrefix conflictPrefix, final int prefixRangeMask) {
        final int suffixMask = ~prefixLengthToV4NetmaskIntHTH(conflictPrefix.getPrefixLength());
        // The largest offset within the prefix assignment block that still conflicts with
        // conflictPrefix.
        final int maxConflict =
                (getPrefixBaseAddress(conflictPrefix) | suffixMask) & ~prefixRangeMask;

        final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
        // Pick a sub prefix a full prefix (1 << (32 - PREFIX_LENGTH) addresses) greater than
        // maxConflict. This ensures that the selected prefix never overlaps with conflictPrefix.
        // There is no need to mask the result with PREFIX_LENGTH bits because this is done by
        // findAvailablePrefixFromRange when it constructs the prefix.
        return maxConflict + (1 << (32 - PREFIX_LENGTH));
    }

    private LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
        // The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12).
        final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength());

        // The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12).
        final int baseAddress = getPrefixBaseAddress(prefixRange);

        // The subnet mask corresponding to PREFIX_LENGTH.
        final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);

        // The offset within prefixRange of a randomly-selected prefix of length PREFIX_LENGTH.
        // This may not be the prefix of the address returned by this method:
        // - If it is already in use, the method will return an address in another prefix.
        // - If all prefixes within prefixRange are in use, the method will return null. For
        // example, for a /24 prefix within 172.26.0.0/12, this will be a multiple of 256 in
        // [0, 1048576). In other words, a random 32-bit number with mask 0x000fff00.
        //
        // prefixRangeMask is required to ensure no wrapping. For example, consider:
        // - prefixRange 127.0.0.0/8
        // - randomPrefixStart 127.255.255.0
        // - A conflicting prefix of 127.255.254.0/23
        // In this case without prefixRangeMask, getNextSubPrefix would return 128.0.0.0, which
        // means the "start < end" check in findAvailablePrefixFromRange would not reject the prefix
        // because Java doesn't have unsigned integers, so 128.0.0.0 = 0x80000000 = -2147483648
        // is less than 127.0.0.0 = 0x7f000000 = 2130706432.
        //
        // Additionally, it makes debug output easier to read by making the numbers smaller.
        final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask;

        // A random offset within the prefix. Used to determine the local address once the prefix
        // is selected. It does not result in an IPv4 address ending in .0, .1, or .255
        // For a PREFIX_LENGTH of 255, this is a number between 2 and 254.
        final int subAddress = getSanitizedSubAddr(~prefixMask);

        // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block,
        // such that the prefix does not conflict with any upstream.
        IpPrefix downstreamPrefix = findAvailablePrefixFromRange(
                 randomPrefixStart, (~prefixRangeMask) + 1, baseAddress, prefixRangeMask);
        if (downstreamPrefix != null) return getLinkAddress(downstreamPrefix, subAddress);

        // If that failed, do the same, but between 0 and randomPrefixStart.
        downstreamPrefix = findAvailablePrefixFromRange(
                0, randomPrefixStart, baseAddress, prefixRangeMask);

        return getLinkAddress(downstreamPrefix, subAddress);
    }

    private LinkAddress getLinkAddress(final IpPrefix prefix, final int subAddress) {
        if (prefix == null) return null;

        final InetAddress address = intToInet4AddressHTH(getPrefixBaseAddress(prefix) | subAddress);
        return new LinkAddress(address, PREFIX_LENGTH);
    }

    private IpPrefix findAvailablePrefixFromRange(final int start, final int end,
            final int baseAddress, final int prefixRangeMask) {
        int newSubPrefix = start;
        while (newSubPrefix < end) {
            final InetAddress address = intToInet4AddressHTH(baseAddress | newSubPrefix);
            final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH);

            final IpPrefix conflictPrefix = getConflictPrefix(prefix);

            if (conflictPrefix == null) return prefix;

            newSubPrefix = getNextSubPrefix(conflictPrefix, prefixRangeMask);
        }

        return null;
    }

    /** Get random sub address value. Return value is in 0 ~ 0xffff. */
    /** Get random int which could be used to generate random address. */
    @VisibleForTesting
    public int getRandomSubAddr() {
        return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
    public int getRandomInt() {
        return (new Random()).nextInt();
    }

    private byte getSanitizedAddressSuffix(final int source, byte... excluded) {
        final byte subId = (byte) (source & BYTE_MASK);
        for (byte value : excluded) {
            if (subId == value) return DEFAULT_ID;
    /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */
    private int getSanitizedSubAddr(final int subAddrMask) {
        final int randomSubAddr = getRandomInt() & subAddrMask;
        // If prefix length > 30, the selecting speace would be less than 4 which may be hard to
        // avoid 3 consecutive address.
        if (PREFIX_LENGTH > 30) return randomSubAddr;

        // TODO: maybe it is not necessary to avoid .0, .1 and .255 address because tethering
        // address would not be conflicted. This code only works because PREFIX_LENGTH is not longer
        // than 24
        final int candidate = randomSubAddr & 0xff;
        if (candidate == 0 || candidate == 1 || candidate == 255) {
            return (randomSubAddr & 0xfffffffc) + 2;
        }

        return subId;
        return randomSubAddr;
    }

    /** Release downstream record for IpServer. */
@@ -237,14 +336,18 @@ public class PrivateAddressCoordinator {
        mUpstreamPrefixMap.clear();
    }

    private boolean isConflictWithUpstream(final IpPrefix source) {
    private IpPrefix getConflictWithUpstream(final IpPrefix prefix) {
        for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
            final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i);
            for (IpPrefix target : list) {
                if (isConflictPrefix(source, target)) return true;
            for (IpPrefix upstream : list) {
                if (isConflictPrefix(prefix, upstream)) return upstream;
            }
        }
        return false;
        return null;
    }

    private boolean isConflictWithUpstream(final IpPrefix prefix) {
        return getConflictWithUpstream(prefix) != null;
    }

    private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
@@ -257,11 +360,10 @@ public class PrivateAddressCoordinator {

    // InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last
    // downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p).
    private boolean isDownstreamPrefixInUse(final IpPrefix prefix) {
        // This class always generates downstream prefixes with the same prefix length, so
        // prefixes cannot be contained in each other. They can only be equal to each other.
    private IpPrefix getInUseDownstreamPrefix(final IpPrefix prefix) {
        for (int i = 0; i < mCachedAddresses.size(); i++) {
            if (prefix.equals(asIpPrefix(mCachedAddresses.valueAt(i)))) return true;
            final IpPrefix downstream = asIpPrefix(mCachedAddresses.valueAt(i));
            if (isConflictPrefix(prefix, downstream)) return downstream;
        }

        // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
@@ -270,10 +372,10 @@ public class PrivateAddressCoordinator {
            final IpPrefix target = getDownstreamPrefix(downstream);
            if (target == null) continue;

            if (isConflictPrefix(prefix, target)) return true;
            if (isConflictPrefix(prefix, target)) return target;
        }

        return false;
        return null;
    }

    private IpPrefix getDownstreamPrefix(final IpServer downstream) {
@@ -284,6 +386,13 @@ public class PrivateAddressCoordinator {
    }

    void dump(final IndentingPrintWriter pw) {
        pw.println("mTetheringPrefixes:");
        pw.increaseIndent();
        for (IpPrefix prefix : mTetheringPrefixes) {
            pw.println(prefix);
        }
        pw.decreaseIndent();

        pw.println("mUpstreamPrefixMap:");
        pw.increaseIndent();
        for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
+1 −1
Original line number Diff line number Diff line
@@ -326,7 +326,7 @@ public class Tethering {
        // It is OK for the configuration to be passed to the PrivateAddressCoordinator at
        // construction time because the only part of the configuration it uses is
        // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
        mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext, mConfig);
        mPrivateAddressCoordinator = mDeps.getPrivateAddressCoordinator(mContext, mConfig);

        // Must be initialized after tethering configuration is loaded because BpfCoordinator
        // constructor needs to use the configuration.
+8 −0
Original line number Diff line number Diff line
@@ -156,4 +156,12 @@ public abstract class TetheringDependencies {
    public boolean isTetheringDenied() {
        return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true");
    }

    /**
     * Get a reference to PrivateAddressCoordinator to be used by Tethering.
     */
    public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
            TetheringConfiguration cfg) {
        return new PrivateAddressCoordinator(ctx, cfg);
    }
}
+234 −18

File changed.

Preview size limit exceeded, changes collapsed.

+49 −25
Original line number Diff line number Diff line
@@ -24,6 +24,9 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -179,6 +182,7 @@ public class TetheringTest {
    private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
    private static final String TEST_NCM_IFNAME = "test_ncm0";
    private static final String TEST_ETH_IFNAME = "test_eth0";
    private static final String TEST_BT_IFNAME = "test_pan0";
    private static final String TETHERING_NAME = "Tethering";
    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
    private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
@@ -230,6 +234,7 @@ public class TetheringTest {
    private TetheringConfiguration mConfig;
    private EntitlementManager mEntitleMgr;
    private OffloadController mOffloadCtrl;
    private PrivateAddressCoordinator mPrivateAddressCoordinator;

    private class TestContext extends BroadcastInterceptingContext {
        TestContext(Context base) {
@@ -446,6 +451,18 @@ public class TetheringTest {
        public boolean isTetheringDenied() {
            return false;
        }


        @Override
        public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
                TetheringConfiguration cfg) {
            final ArrayList<IpPrefix> prefixPool = new ArrayList<>(Arrays.asList(
                    new IpPrefix("192.168.0.0/16"),
                    new IpPrefix("172.16.0.0/12"),
                    new IpPrefix("10.0.0.0/8")));
            mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(ctx, cfg, prefixPool));
            return mPrivateAddressCoordinator;
        }
    }

    private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -1875,27 +1892,36 @@ public class TetheringTest {
        sendConfigurationChanged();
    }

    private static UpstreamNetworkState buildV4WifiUpstreamState(final String ipv4Address,
            final int prefixLength, final Network network) {
    private static UpstreamNetworkState buildV4UpstreamState(final LinkAddress address,
            final Network network, final String iface, final int transportType) {
        final LinkProperties prop = new LinkProperties();
        prop.setInterfaceName(TEST_WIFI_IFNAME);
        prop.setInterfaceName(iface);

        prop.addLinkAddress(
                new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address),
                        prefixLength));
        prop.addLinkAddress(address);

        final NetworkCapabilities capabilities = new NetworkCapabilities()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
                .addTransportType(transportType);
        return new UpstreamNetworkState(prop, capabilities, network);
    }

    private void updateV4Upstream(final LinkAddress ipv4Address, final Network network,
            final String iface, final int transportType) {
        final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface,
                transportType);
        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
                0,
                upstream);
        mLooper.dispatchAll();
    }

    @Test
    public void testHandleIpConflict() throws Exception {
        final Network wifiNetwork = new Network(200);
        final Network[] allNetworks = { wifiNetwork };
        when(mCm.getAllNetworks()).thenReturn(allNetworks);
        UpstreamNetworkState upstreamNetwork = null;
        runUsbTethering(upstreamNetwork);
        runUsbTethering(null);
        final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
                ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
        verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture());
@@ -1903,13 +1929,10 @@ public class TetheringTest {
        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                any(), any());
        reset(mNetd, mUsbManager);
        upstreamNetwork = buildV4WifiUpstreamState(ipv4Address, 30, wifiNetwork);
        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
                0,
                upstreamNetwork);
        mLooper.dispatchAll();

        // Cause a prefix conflict by assigning a /30 out of the downstream's /24 to the upstream.
        updateV4Upstream(new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address), 30),
                wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI);
        // verify turn off usb tethering
        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
        mTethering.interfaceRemoved(TEST_USB_IFNAME);
@@ -1921,9 +1944,10 @@ public class TetheringTest {
    @Test
    public void testNoAddressAvailable() throws Exception {
        final Network wifiNetwork = new Network(200);
        final Network[] allNetworks = { wifiNetwork };
        final Network btNetwork = new Network(201);
        final Network mobileNetwork = new Network(202);
        final Network[] allNetworks = { wifiNetwork, btNetwork, mobileNetwork };
        when(mCm.getAllNetworks()).thenReturn(allNetworks);
        final String upstreamAddress = "192.168.0.100";
        runUsbTethering(null);
        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                any(), any());
@@ -1940,13 +1964,13 @@ public class TetheringTest {
        mLooper.dispatchAll();
        reset(mUsbManager, mEm);

        final UpstreamNetworkState upstreamNetwork = buildV4WifiUpstreamState(
                upstreamAddress, 16, wifiNetwork);
        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
                0,
                upstreamNetwork);
        updateV4Upstream(new LinkAddress("192.168.0.100/16"), wifiNetwork, TEST_WIFI_IFNAME,
                TRANSPORT_WIFI);
        updateV4Upstream(new LinkAddress("172.16.0.0/12"), btNetwork, TEST_BT_IFNAME,
                TRANSPORT_BLUETOOTH);
        updateV4Upstream(new LinkAddress("10.0.0.0/8"), mobileNetwork, TEST_MOBILE_IFNAME,
                TRANSPORT_CELLULAR);

        mLooper.dispatchAll();
        // verify turn off usb tethering
        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);