Loading packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +162 −53 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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; } /** Loading Loading @@ -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. */ Loading @@ -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) { Loading @@ -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 Loading @@ -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) { Loading @@ -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++) { Loading packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +8 −0 Original line number Diff line number Diff line Loading @@ -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); } } packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java +234 −18 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +49 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -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()); Loading @@ -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); Loading @@ -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()); Loading @@ -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); Loading Loading
packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +162 −53 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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; } /** Loading Loading @@ -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. */ Loading @@ -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) { Loading @@ -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 Loading @@ -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) { Loading @@ -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++) { Loading
packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading
packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +8 −0 Original line number Diff line number Diff line Loading @@ -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); } }
packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java +234 −18 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +49 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -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()); Loading @@ -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); Loading @@ -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()); Loading @@ -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); Loading