diff options
author | Mark Chien <markchien@google.com> | 2020-10-13 13:12:58 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-10-13 13:12:58 +0000 |
commit | 5b976f351666a944c0b51eebcd03954087ef9fbd (patch) | |
tree | ad68df8d63b53c4dd313001b756c070eaf93e3b9 | |
parent | 9c0d523bc0b1ac3ebba92acb7e5d9675aff08aef (diff) | |
parent | 8a8e7e0350d901a18093280dfad6a516a7f26256 (diff) |
Merge "Allow tethering pick prefix from all of private address range"
5 files changed, 454 insertions, 97 deletions
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java index 6276c4e2aa7d..0cf14e3f868c 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java @@ -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); + for (IpPrefix prefixRange : mTetheringPrefixes) { + final LinkAddress newAddress = chooseDownstreamAddress(prefixRange); + if (newAddress != null) { + mDownstreams.add(ipServer); + mCachedAddresses.put(ipServer.interfaceType(), newAddress); + return newAddress; } - - if (isConflict(new IpPrefix(addr, PREFIX_LENGTH))) continue; - - 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)); } - /** Get random sub address value. Return value is in 0 ~ 0xffff. */ + 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 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++) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 474f4e8b603a..5a0c5b0cff5f 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -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. diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 131a5fbf2abe..45b914178e97 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -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); + } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java index 191eb6e71149..86e6f11659c6 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java @@ -51,6 +51,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest public final class PrivateAddressCoordinatorTest { @@ -70,7 +73,17 @@ public final class PrivateAddressCoordinatorTest { private final Network mWifiNetwork = new Network(1); private final Network mMobileNetwork = new Network(2); private final Network mVpnNetwork = new Network(3); - private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork, mVpnNetwork}; + private final Network mMobileNetwork2 = new Network(4); + private final Network mMobileNetwork3 = new Network(5); + private final Network mMobileNetwork4 = new Network(6); + private final Network mMobileNetwork5 = new Network(7); + private final Network mMobileNetwork6 = new Network(8); + private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork, mVpnNetwork, + mMobileNetwork2, mMobileNetwork3, mMobileNetwork4, mMobileNetwork5, mMobileNetwork6}; + private final ArrayList<IpPrefix> mTetheringPrefixes = 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"))); private void setUpIpServers() throws Exception { when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB); @@ -87,7 +100,8 @@ public final class PrivateAddressCoordinatorTest { when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks); when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false); setUpIpServers(); - mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig)); + mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig, + mTetheringPrefixes)); } @Test @@ -117,28 +131,28 @@ public final class PrivateAddressCoordinatorTest { @Test public void testSanitizedAddress() throws Exception { int fakeSubAddr = 0x2b00; // 43.0. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr); LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, false /* useLastAddress */); - assertEquals(new LinkAddress("192.168.43.42/24"), actualAddress); + assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress); mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); fakeSubAddr = 0x2d01; // 45.1. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr); actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, false /* useLastAddress */); - assertEquals(new LinkAddress("192.168.45.42/24"), actualAddress); + assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress); mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); fakeSubAddr = 0x2eff; // 46.255. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr); actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, false /* useLastAddress */); - assertEquals(new LinkAddress("192.168.46.42/24"), actualAddress); + assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress); mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); fakeSubAddr = 0x2f05; // 47.5. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr); actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, false /* useLastAddress */); assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress); @@ -148,7 +162,7 @@ public final class PrivateAddressCoordinatorTest { @Test public void testReservedPrefix() throws Exception { // - Test bluetooth prefix is reserved. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn( + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn( getSubAddress(mBluetoothAddress.getAddress().getAddress())); final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, false /* useLastAddress */); @@ -157,7 +171,7 @@ public final class PrivateAddressCoordinatorTest { mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); // - Test previous enabled hotspot prefix(cached prefix) is reserved. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn( + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn( getSubAddress(hotspotAddress.getAddress().getAddress())); final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mUsbIpServer, false /* useLastAddress */); @@ -167,7 +181,7 @@ public final class PrivateAddressCoordinatorTest { mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer); // - Test wifi p2p prefix is reserved. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn( + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn( getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress())); final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mEthernetIpServer, false /* useLastAddress */); @@ -182,7 +196,7 @@ public final class PrivateAddressCoordinatorTest { public void testRequestLastDownstreamAddress() throws Exception { final int fakeHotspotSubAddr = 0x2b05; final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24"); - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr); final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, true /* useLastAddress */); assertEquals("Wrong wifi prefix: ", predefinedPrefix, asIpPrefix(hotspotAddress)); @@ -196,7 +210,7 @@ public final class PrivateAddressCoordinatorTest { mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer); final int newFakeSubAddr = 0x3c05; - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr); final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, true /* useLastAddress */); @@ -229,11 +243,10 @@ public final class PrivateAddressCoordinatorTest { @Test public void testNoConflictUpstreamPrefix() throws Exception { - final int fakeHotspotSubId = 43; - final int fakeHotspotSubAddr = 0x2b05; + final int fakeHotspotSubAddr = 0x2b05; // 43.5 final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24"); // Force always get subAddress "43.5" for conflict testing. - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr); // - Enable hotspot with prefix 192.168.43.0/24 final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress( mHotspotIpServer, true /* useLastAddress */); @@ -312,6 +325,209 @@ public final class PrivateAddressCoordinatorTest { assertEquals(predefinedPrefix, ethPrefix); } + @Test + public void testChooseAvailablePrefix() throws Exception { + final int randomAddress = 0x8605; // 134.5 + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress); + final LinkAddress addr0 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5. + assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0); + when(mHotspotIpServer.getAddress()).thenReturn(addr0); + final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork, + new LinkAddress("192.168.134.13/26"), null, + makeNetworkCapabilities(TRANSPORT_WIFI)); + mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream); + + // Check whether return address is next prefix of 192.168.134.0/24. + final LinkAddress addr1 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1); + when(mHotspotIpServer.getAddress()).thenReturn(addr1); + final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork, + new LinkAddress("192.168.149.16/19"), null, + makeNetworkCapabilities(TRANSPORT_WIFI)); + mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream2); + + + // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24. + final LinkAddress addr2 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2); + when(mHotspotIpServer.getAddress()).thenReturn(addr2); + final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork, + new LinkAddress("192.168.129.53/18"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + // Update another conflict upstream which is covered by the previous one (but not the first + // one) and verify whether this would affect the result. + final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2, + new LinkAddress("192.168.170.7/19"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2); + + // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24. + final LinkAddress addr3 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3); + when(mHotspotIpServer.getAddress()).thenReturn(addr3); + final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3, + new LinkAddress("192.168.188.133/17"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3); + + // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because + // 192.168.134/24 ~ 192.168.255.255/24 is not available. + final LinkAddress addr4 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4); + when(mHotspotIpServer.getAddress()).thenReturn(addr4); + final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4, + new LinkAddress("192.168.3.59/21"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4); + + // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24. + final LinkAddress addr5 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5); + when(mHotspotIpServer.getAddress()).thenReturn(addr5); + final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5, + new LinkAddress("192.168.68.43/21"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5); + + // Update an upstream that does *not* conflict, check whether return the same address + // 192.168.5/24. + final LinkAddress addr6 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6); + when(mHotspotIpServer.getAddress()).thenReturn(addr6); + final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6, + new LinkAddress("192.168.10.97/21"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6); + + // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24. + final LinkAddress addr7 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7); + when(mHotspotIpServer.getAddress()).thenReturn(addr7); + final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6, + new LinkAddress("192.168.0.0/17"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7); + + // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16. + final LinkAddress addr8 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8); + when(mHotspotIpServer.getAddress()).thenReturn(addr6); + } + + @Test + public void testChoosePrefixFromDifferentRanges() throws Exception { + final int randomAddress = 0x1f2b2a; // 31.43.42 + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress); + final LinkAddress classC1 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42. + assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1); + when(mHotspotIpServer.getAddress()).thenReturn(classC1); + final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork, + new LinkAddress("192.168.88.23/17"), null, + makeNetworkCapabilities(TRANSPORT_WIFI)); + mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream); + verifyNotifyConflictAndRelease(mHotspotIpServer); + + // Check whether return address is next address of prefix 192.168.128.0/17. + final LinkAddress classC2 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2); + when(mHotspotIpServer.getAddress()).thenReturn(classC2); + final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork, + new LinkAddress("192.1.2.3/8"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream); + verifyNotifyConflictAndRelease(mHotspotIpServer); + + // Check whether return address is under prefix 172.16.0.0/12. + final LinkAddress classB1 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1); + when(mHotspotIpServer.getAddress()).thenReturn(classB1); + final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2, + new LinkAddress("172.28.123.100/14"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2); + verifyNotifyConflictAndRelease(mHotspotIpServer); + + // 172.28.0.0 ~ 172.31.255.255 is not available. + // Check whether return address is next address of prefix 172.16.0.0/14. + final LinkAddress classB2 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2); + when(mHotspotIpServer.getAddress()).thenReturn(classB2); + + // Check whether new downstream is next address of address 172.16.0.42/24. + final LinkAddress classB3 = mPrivateAddressCoordinator.requestDownstreamAddress( + mUsbIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3); + when(mUsbIpServer.getAddress()).thenReturn(classB3); + final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3, + new LinkAddress("172.16.0.1/24"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3); + verifyNotifyConflictAndRelease(mHotspotIpServer); + verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT); + + // Check whether return address is next address of prefix 172.16.1.42/24. + final LinkAddress classB4 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4); + when(mHotspotIpServer.getAddress()).thenReturn(classB4); + final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4, + new LinkAddress("172.16.0.1/13"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4); + verifyNotifyConflictAndRelease(mHotspotIpServer); + verifyNotifyConflictAndRelease(mUsbIpServer); + + // Check whether return address is next address of prefix 172.16.0.1/13. + final LinkAddress classB5 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5); + when(mHotspotIpServer.getAddress()).thenReturn(classB5); + // Check whether return address is next address of prefix 172.24.0.42/24. + final LinkAddress classB6 = mPrivateAddressCoordinator.requestDownstreamAddress( + mUsbIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6); + when(mUsbIpServer.getAddress()).thenReturn(classB6); + final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5, + new LinkAddress("172.24.0.1/12"), null, + makeNetworkCapabilities(TRANSPORT_CELLULAR)); + mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5); + verifyNotifyConflictAndRelease(mHotspotIpServer); + verifyNotifyConflictAndRelease(mUsbIpServer); + + // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42. + final LinkAddress classA1 = mPrivateAddressCoordinator.requestDownstreamAddress( + mHotspotIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1); + when(mHotspotIpServer.getAddress()).thenReturn(classA1); + // Check whether new downstream is next address of address 10.31.43.42/24. + final LinkAddress classA2 = mPrivateAddressCoordinator.requestDownstreamAddress( + mUsbIpServer, true/* useLastAddress */); + assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2); + } + + private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception { + verify(ipServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT); + mPrivateAddressCoordinator.releaseDownstream(ipServer); + reset(ipServer); + setUpIpServers(); + } + private int getSubAddress(final byte... ipv4Address) { assertEquals(4, ipv4Address.length); @@ -330,7 +546,7 @@ public final class PrivateAddressCoordinatorTest { @Test public void testEnableLegacyWifiP2PAddress() throws Exception { - when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn( + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn( getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress())); // No matter #shouldEnableWifiP2pDedicatedIp() is enabled or not, legacy wifi p2p prefix // is resevered. diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index df570206e389..20e94b256ad1 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -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); |