blob: 9a2fbc4dd93193d44d9a025b92f820b3ceeaff07 [file] [log] [blame]
package com.android.clockwork.bluetooth.proxy;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_SUBTYPE_ID;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_SUBTYPE_NAME;
import static com.android.clockwork.bluetooth.proxy.WearProxyConstants.PROXY_NETWORK_TYPE_NAME;
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.os.Looper;
import android.util.Log;
import com.android.clockwork.common.DebugAssert;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Lists;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* {@link NetworkAgent} that represents bluetooth companion proxy networks.
*
* Interacts with {@link ConnectivityService} to provide proxy network connectivity
* over bluetooth. {@link ProxyNetworkAgent} provides the container and manipulation
* methods for all network agents used for sysproxy.
*/
public class ProxyNetworkAgent {
private static final String TAG = WearProxyConstants.LOG_TAG;
private static final String NETWORK_AGENT_LOGTAG = "CompanionProxyAgent";
private static final String NETWORK_PROVIDER_NAME = "CompanionProxyProvider";
private static final boolean ALWAYS_CREATE_AGENT = true;
private static final boolean MAYBE_RECYCLE_AGENT = false;
@VisibleForTesting
static final InetAddress IPV4_ADDR_ANY = makeInet4Address(
(byte) 0, (byte) 0, (byte) 0, (byte) 0);
@VisibleForTesting
static final InetAddress IPV4_ADDR_LOCAL = makeInet4Address(
(byte) 127, (byte) 0, (byte) 0, (byte) 1);
private final Context mContext;
private final NetworkCapabilities mCapabilities;
private final boolean mIsLocalEdition;
private List<InetAddress> mDnsServers;
@VisibleForTesting
@Nullable NetworkAgent mCurrentNetworkAgent;
private int mCurrentNetworkScore;
private String mCurrentNetworkInterface;
private int mCurrentNetworkMtu;
@VisibleForTesting
final HashMap<NetworkAgent, NetworkInfo> mNetworkAgents
= new HashMap<NetworkAgent, NetworkInfo>();
/**
* Callback executed when Connectivity Service deems a network agent unwanted()
*/
public interface Listener {
public void onNetworkAgentUnwanted(int netId);
}
protected ProxyNetworkAgent(
@NonNull final Context context,
@NonNull final NetworkCapabilities capabilities,
boolean isLocalEdition) {
mContext = context;
mCapabilities = capabilities;
mIsLocalEdition = isLocalEdition;
setDnsServers(Lists.newArrayList());
}
@MainThread
protected void sendCapabilities(final NetworkCapabilities capabilities) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
mCurrentNetworkAgent.sendNetworkCapabilities(capabilities);
} else {
Log.w(TAG, "Send capabilities with no network agent");
}
}
@MainThread
protected void setNetworkScore(final int networkScore) {
DebugAssert.isMainThread();
if (mCurrentNetworkScore != networkScore) {
mCurrentNetworkScore = networkScore;
if (mCurrentNetworkAgent != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Set network score for current network agent:"
+ mCurrentNetworkAgent.getNetwork() + " score:" + mCurrentNetworkScore);
}
mCurrentNetworkAgent.sendNetworkScore(mCurrentNetworkScore);
} else {
Log.d(TAG, "Setting network score with future network agent");
}
}
}
@MainThread
protected int getNetworkScore() {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
return mCurrentNetworkScore;
} else {
return 0;
}
}
protected void setDnsServers(List<InetAddress> dnsServers) {
DebugAssert.isMainThread();
mDnsServers = DnsConfigHelper.computeDnsServerList(
mIsLocalEdition, dnsServers);
sendLinkProperties();
}
private void sendLinkProperties() {
if (mCurrentNetworkAgent != null) {
mCurrentNetworkAgent.sendLinkProperties(buildLinkProperties());
}
}
private LinkProperties buildLinkProperties() {
DebugAssert.isMainThread();
if (mCurrentNetworkInterface == null) {
throw new IllegalStateException();
}
LinkProperties linkProperties = new LinkProperties();
linkProperties.setMtu(mCurrentNetworkMtu);
linkProperties.setInterfaceName(mCurrentNetworkInterface);
linkProperties.setDnsServers(mDnsServers);
addLinkRoute(linkProperties);
return linkProperties;
}
@VisibleForTesting
void addLinkRoute(LinkProperties linkProperties) {
final String interfaceName = linkProperties.getInterfaceName();
IpPrefix destinationPrefix;
InetAddress gatewayAddress;
if ("lo".equals(interfaceName)) {
destinationPrefix = null;
gatewayAddress = IPV4_ADDR_LOCAL;
} else {
destinationPrefix = new IpPrefix(IPV4_ADDR_ANY, 0);
gatewayAddress = null;
}
linkProperties.addRoute(new RouteInfo(
destinationPrefix, gatewayAddress, interfaceName, RouteInfo.RTN_UNICAST));
}
@MainThread
protected void maybeSetUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
@NonNull final String interfaceName,
final int mtu,
@Nullable final Listener listener) {
doSetUpNetworkAgent(reason, companionName, interfaceName, mtu, mCurrentNetworkScore,
listener, ProxyNetworkAgent.MAYBE_RECYCLE_AGENT);
}
@MainThread
protected void setUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
@NonNull final String interfaceName,
final int mtu,
@Nullable final Listener listener) {
doSetUpNetworkAgent(reason, companionName, interfaceName, mtu, mCurrentNetworkScore,
listener, ProxyNetworkAgent.ALWAYS_CREATE_AGENT);
}
/**
* Create or recycle network agent
*
* We want to re-use the existing network agent, if available, as we only want
* a single network agent session active at any given time.
*
* We want to create a new one during initial start up, or if the previous network
* agent becomes disconnected.
*
* The {@link NetworkAgent} constructor connects that objects handler with
* {@link ConnectiviyService} in order to provide proxy network access.
*
* If there are no clients who want this network this agent will be torn down
* and unwanted() will be called. This will in turn propogate a second callback
* to the creater of {@link ProxyNetworkAgent}.
*/
private void doSetUpNetworkAgent(
@Nullable final String reason,
@Nullable final String companionName,
@NonNull final String interfaceName,
final int mtu,
final int networkScore,
@Nullable final Listener listener,
final boolean forceNewAgent) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
if (forceNewAgent) {
Log.w(
TAG,
"Network updated and overwriting current network agent since"
+ " one already existed ... previous agent:"
+ mCurrentNetworkAgent.getNetwork());
} else {
Log.w(TAG, "Network updated and re-using existing network agent:"
+ mCurrentNetworkAgent.getNetwork());
return;
}
}
mCurrentNetworkInterface = interfaceName;
mCurrentNetworkMtu = mtu;
mCurrentNetworkScore = networkScore;
final NetworkInfo networkInfo =
new NetworkInfo(ConnectivityManager.TYPE_PROXY, PROXY_NETWORK_SUBTYPE_ID,
PROXY_NETWORK_TYPE_NAME, PROXY_NETWORK_SUBTYPE_NAME);
networkInfo.setDetailedState(DetailedState.CONNECTING, reason, companionName);
NetworkAgentConfig networkConfig = new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_PROXY)
.setLegacyTypeName(PROXY_NETWORK_TYPE_NAME)
.setLegacySubType(PROXY_NETWORK_SUBTYPE_ID)
.setLegacySubTypeName(PROXY_NETWORK_SUBTYPE_NAME)
.build();
mCurrentNetworkAgent = new NetworkAgent(
mContext,
Looper.getMainLooper(),
NETWORK_AGENT_LOGTAG,
mCapabilities,
buildLinkProperties(),
networkScore,
networkConfig,
new NetworkProvider(mContext, Looper.getMainLooper(), NETWORK_PROVIDER_NAME)) {
@Override
public void onNetworkUnwanted() {
DebugAssert.isMainThread();
if (listener != null) {
Network network = this.getNetwork();
listener.onNetworkAgentUnwanted(network == null ? -1 : network.getNetId());
}
tearDownNetworkAgent(this);
}
};
mCurrentNetworkAgent.register();
mNetworkAgents.put(mCurrentNetworkAgent, networkInfo);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Created network agent:" + mCurrentNetworkAgent.getNetwork());
}
}
@MainThread
private void tearDownNetworkAgent(
@NonNull final NetworkAgent unwantedNetworkAgent) {
DebugAssert.isMainThread();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Proxy agent is not longer needed"
+ "...tearing down network agent:"
+ unwantedNetworkAgent.getNetwork());
}
final NetworkInfo networkInfo = mNetworkAgents.get(unwantedNetworkAgent);
if (networkInfo == null) {
Log.e(TAG, "Unable to find unwanted network agent in map"
+ " network agent:" + unwantedNetworkAgent.getNetwork());
return;
}
mNetworkAgents.remove(unwantedNetworkAgent);
if (unwantedNetworkAgent == mCurrentNetworkAgent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "disconnected unwanted current network agent:"
+ mCurrentNetworkAgent.getNetwork());
}
mCurrentNetworkAgent = null;
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"unwanted network agent already torn down"
+ " unwanted:"
+ unwantedNetworkAgent.getNetwork()
+ " current:"
+ (mCurrentNetworkAgent == null
? "none"
: mCurrentNetworkAgent.getNetwork()));
}
}
}
@MainThread
protected void invalidateCurrentNetworkAgent() {
DebugAssert.isMainThread();
mCurrentNetworkAgent = null;
}
@MainThread
protected void setConnected(@Nullable final String reason,
@Nullable final String companionName) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent == null) {
Log.w(TAG, "Send network info with no network agent reason:"
+ reason);
}
setCurrentNetworkInfo(DetailedState.CONNECTED, reason, companionName);
}
@MainThread
protected void setDisconnected(@Nullable final String reason,
@Nullable final String companionName) {
DebugAssert.isMainThread();
setCurrentNetworkInfo(DetailedState.DISCONNECTED, reason, companionName);
}
@MainThread
private void setCurrentNetworkInfo(DetailedState detailedState, String reason,
String extraInfo) {
DebugAssert.isMainThread();
if (mCurrentNetworkAgent != null) {
final NetworkInfo networkInfo = mNetworkAgents.get(mCurrentNetworkAgent);
networkInfo.setDetailedState(detailedState, reason, extraInfo);
mNetworkAgents.put(mCurrentNetworkAgent, networkInfo);
if (detailedState == DetailedState.CONNECTED) {
mCurrentNetworkAgent.markConnected();
} else if (detailedState == DetailedState.DISCONNECTED) {
mCurrentNetworkAgent.unregister();
}
}
}
@AnyThread
public void dump(@NonNull final IndentingPrintWriter ipw) {
ipw.printPair("Network agent id", (mCurrentNetworkAgent == null)
? "none" : mCurrentNetworkAgent.getNetwork());
ipw.printPair("score", (mCurrentNetworkAgent == null) ? 0 : mCurrentNetworkScore);
ipw.println();
ipw.increaseIndent();
for (Map.Entry<NetworkAgent, NetworkInfo> entry : mNetworkAgents.entrySet()) {
ipw.printPair(entry.getKey().toString(), entry.getValue());
}
ipw.decreaseIndent();
ipw.println();
}
/** Make an Inet4Address from 4 bytes in network byte order. */
private static InetAddress makeInet4Address(byte b1, byte b2, byte b3, byte b4) {
try {
return InetAddress.getByAddress(new byte[] { b1, b2, b3, b4 });
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e.toString());
}
}
}