blob: 160a166b632bf2c4b1be200e81d2373684343d0d [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.networkstack.tethering;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.ip.IpServer;
import android.net.util.PrefixUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* This class coordinate IP addresses conflict problem.
*
* Tethering downstream IP addresses may conflict with network assigned addresses. This
* coordinator is responsible for recording all of network assigned addresses and dispatched
* free address to downstream interfaces.
*
* This class is not thread-safe and should be accessed on the same tethering internal thread.
* @hide
*/
public class PrivateAddressCoordinator {
public static final int PREFIX_LENGTH = 24;
private static final int MAX_UBYTE = 256;
private static final int BYTE_MASK = 0xff;
// reserved for bluetooth tethering.
private static final int BLUETOOTH_RESERVED = 44;
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
// when tethering is down. Instead coordinator would remove all depcreted upstreams from
// mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprectedUpstreams().
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 final IpPrefix mTetheringPrefix;
private final ConnectivityManager mConnectivityMgr;
public PrivateAddressCoordinator(Context context) {
mDownstreams = new ArraySet<>();
mUpstreamPrefixMap = new ArrayMap<>();
mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
mConnectivityMgr = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
}
/**
* Record a new upstream IpPrefix which may conflict with tethering downstreams.
* The downstreams will be notified if a conflict is found.
*/
public void updateUpstreamPrefix(final Network network, final LinkProperties lp) {
final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
if (ipv4Prefixes.isEmpty()) {
removeUpstreamPrefix(network);
return;
}
mUpstreamPrefixMap.put(network, ipv4Prefixes);
handleMaybePrefixConflict(ipv4Prefixes);
}
private ArrayList<IpPrefix> getIpv4Prefixes(final List<LinkAddress> linkAddresses) {
final ArrayList<IpPrefix> list = new ArrayList<>();
for (LinkAddress address : linkAddresses) {
if (!address.isIpv4()) continue;
list.add(PrefixUtils.asIpPrefix(address));
}
return list;
}
private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
for (IpServer downstream : mDownstreams) {
final IpPrefix target = getDownstreamPrefix(downstream);
if (target == null) continue;
for (IpPrefix source : prefixes) {
if (isConflictPrefix(source, target)) {
downstream.sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
break;
}
}
}
}
/** Remove IpPrefix records corresponding to input network. */
public void removeUpstreamPrefix(final Network network) {
mUpstreamPrefixMap.remove(network);
}
private void maybeRemoveDeprectedUpstreams() {
if (!mDownstreams.isEmpty() || mUpstreamPrefixMap.isEmpty()) return;
final ArrayList<Network> toBeRemoved = new ArrayList<>();
List<Network> allNetworks = Arrays.asList(mConnectivityMgr.getAllNetworks());
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
final Network network = mUpstreamPrefixMap.keyAt(i);
if (!allNetworks.contains(network)) toBeRemoved.add(network);
}
mUpstreamPrefixMap.removeAll(toBeRemoved);
}
/**
* Pick a random available address and mark its prefix as in use for the provided IpServer,
* returns null if there is no available address.
*/
@Nullable
public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
maybeRemoveDeprectedUpstreams();
// 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;
if (newSubNet == BLUETOOTH_RESERVED) continue;
bytes[2] = (byte) newSubNet;
final InetAddress addr;
try {
addr = InetAddress.getByAddress(bytes);
} catch (UnknownHostException e) {
throw new IllegalStateException("Invalid address, shouldn't happen.", e);
}
final IpPrefix prefix = new IpPrefix(addr, PREFIX_LENGTH);
// Check whether this prefix is in use.
if (isDownstreamPrefixInUse(prefix)) continue;
// Check whether this prefix is conflict with any current upstream network.
if (isConflictWithUpstream(prefix)) continue;
mDownstreams.add(ipServer);
return new LinkAddress(addr, PREFIX_LENGTH);
}
// No available address.
return null;
}
/** Get random sub address value. Return value is in 0 ~ 0xffff. */
@VisibleForTesting
public int getRandomSubAddr() {
return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
}
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;
}
return subId;
}
/** Release downstream record for IpServer. */
public void releaseDownstream(final IpServer ipServer) {
mDownstreams.remove(ipServer);
}
/** Clear current upstream prefixes records. */
public void clearUpstreamPrefixes() {
mUpstreamPrefixMap.clear();
}
private boolean isConflictWithUpstream(final IpPrefix source) {
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;
}
}
return false;
}
private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
if (prefix2.getPrefixLength() < prefix1.getPrefixLength()) {
return prefix2.contains(prefix1.getAddress());
}
return prefix1.contains(prefix2.getAddress());
}
private boolean isDownstreamPrefixInUse(final IpPrefix source) {
// 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.
for (IpServer downstream : mDownstreams) {
final IpPrefix prefix = getDownstreamPrefix(downstream);
if (source.equals(prefix)) return true;
}
return false;
}
private IpPrefix getDownstreamPrefix(final IpServer downstream) {
final LinkAddress address = downstream.getAddress();
if (address == null) return null;
return PrefixUtils.asIpPrefix(address);
}
void dump(final IndentingPrintWriter pw) {
pw.decreaseIndent();
pw.println("mUpstreamPrefixMap:");
pw.increaseIndent();
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i));
}
pw.decreaseIndent();
pw.println("mDownstreams:");
pw.increaseIndent();
for (IpServer ipServer : mDownstreams) {
pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress());
}
pw.decreaseIndent();
}
}