Enable multiple active Ethernet interfaces
- reworked EthernetNetworkFactory to support multiple active Ethernet interfaces - allow vendors to specify network capabilities + ip config through XML config overlay Test: manual using hikey960 + multiple usb->eth adapters Change-Id: Ie39bcb0d2a3f960f497222159c7bd5797accaa68
This commit is contained in:
@@ -17,11 +17,8 @@
|
||||
package com.android.server.ethernet;
|
||||
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.server.net.IpConfigStore;
|
||||
|
||||
@@ -29,34 +26,60 @@ import com.android.server.net.IpConfigStore;
|
||||
/**
|
||||
* This class provides an API to store and manage Ethernet network configuration.
|
||||
*/
|
||||
public class EthernetConfigStore extends IpConfigStore {
|
||||
private static final String TAG = "EthernetConfigStore";
|
||||
|
||||
public class EthernetConfigStore {
|
||||
private static final String ipConfigFile = Environment.getDataDirectory() +
|
||||
"/misc/ethernet/ipconfig.txt";
|
||||
|
||||
private IpConfigStore mStore = new IpConfigStore();
|
||||
private ArrayMap<String, IpConfiguration> mIpConfigurations;
|
||||
private IpConfiguration mIpConfigurationForDefaultInterface;
|
||||
private final Object mSync = new Object();
|
||||
|
||||
public EthernetConfigStore() {
|
||||
mIpConfigurations = new ArrayMap<>(0);
|
||||
}
|
||||
|
||||
public IpConfiguration readIpAndProxyConfigurations() {
|
||||
SparseArray<IpConfiguration> networks = readIpAndProxyConfigurations(ipConfigFile);
|
||||
public void read() {
|
||||
synchronized (mSync) {
|
||||
ArrayMap<String, IpConfiguration> configs =
|
||||
IpConfigStore.readIpConfigurations(ipConfigFile);
|
||||
|
||||
if (networks.size() == 0) {
|
||||
Log.w(TAG, "No Ethernet configuration found. Using default.");
|
||||
return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
|
||||
// This configuration may exist in old file versions when there was only a single active
|
||||
// Ethernet interface.
|
||||
if (configs.containsKey("0")) {
|
||||
mIpConfigurationForDefaultInterface = configs.remove("0");
|
||||
}
|
||||
|
||||
mIpConfigurations = configs;
|
||||
}
|
||||
|
||||
if (networks.size() > 1) {
|
||||
// Currently we only support a single Ethernet interface.
|
||||
Log.w(TAG, "Multiple Ethernet configurations detected. Only reading first one.");
|
||||
}
|
||||
|
||||
return networks.valueAt(0);
|
||||
}
|
||||
|
||||
public void writeIpAndProxyConfigurations(IpConfiguration config) {
|
||||
SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
|
||||
networks.put(0, config);
|
||||
writeIpAndProxyConfigurations(ipConfigFile, networks);
|
||||
public void write(String iface, IpConfiguration config) {
|
||||
boolean modified;
|
||||
|
||||
synchronized (mSync) {
|
||||
if (config == null) {
|
||||
modified = mIpConfigurations.remove(iface) != null;
|
||||
} else {
|
||||
IpConfiguration oldConfig = mIpConfigurations.put(iface, config);
|
||||
modified = !config.equals(oldConfig);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayMap<String, IpConfiguration> getIpConfigurations() {
|
||||
synchronized (mSync) {
|
||||
return new ArrayMap<>(mIpConfigurations);
|
||||
}
|
||||
}
|
||||
|
||||
public IpConfiguration getIpConfigurationForDefaultInterface() {
|
||||
synchronized (mSync) {
|
||||
return new IpConfiguration(mIpConfigurationForDefaultInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
|
||||
package com.android.server.ethernet;
|
||||
|
||||
import static android.net.ConnectivityManager.TYPE_ETHERNET;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.DhcpResults;
|
||||
import android.net.EthernetManager;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.InterfaceConfiguration;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
@@ -31,292 +28,215 @@ import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkFactory;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkInfo.DetailedState;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.net.ip.IpManager;
|
||||
import android.net.ip.IpManager.ProvisioningConfiguration;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkSpecifier;
|
||||
import android.net.StringNetworkSpecifier;
|
||||
import android.net.ip.IpClient;
|
||||
import android.net.ip.IpClient.ProvisioningConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages connectivity for an Ethernet interface.
|
||||
* {@link NetworkFactory} that represents Ethernet networks.
|
||||
*
|
||||
* Ethernet Interfaces may be present at boot time or appear after boot (e.g.,
|
||||
* for Ethernet adapters connected over USB). This class currently supports
|
||||
* only one interface. When an interface appears on the system (or is present
|
||||
* at boot time) this class will start tracking it and bring it up, and will
|
||||
* attempt to connect when requested. Any other interfaces that subsequently
|
||||
* appear will be ignored until the tracked interface disappears. Only
|
||||
* interfaces whose names match the <code>config_ethernet_iface_regex</code>
|
||||
* regular expression are tracked.
|
||||
*
|
||||
* This class reports a static network score of 70 when it is tracking an
|
||||
* interface and that interface's link is up, and a score of 0 otherwise.
|
||||
*
|
||||
* @hide
|
||||
* This class reports a static network score of 70 when it is tracking an interface and that
|
||||
* interface's link is up, and a score of 0 otherwise.
|
||||
*/
|
||||
class EthernetNetworkFactory {
|
||||
public class EthernetNetworkFactory extends NetworkFactory {
|
||||
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
|
||||
final static boolean DBG = true;
|
||||
|
||||
private final static int NETWORK_SCORE = 70;
|
||||
private static final String NETWORK_TYPE = "Ethernet";
|
||||
private static final String TAG = "EthernetNetworkFactory";
|
||||
private static final int NETWORK_SCORE = 70;
|
||||
private static final boolean DBG = true;
|
||||
|
||||
/** Tracks interface changes. Called from NetworkManagementService. */
|
||||
private InterfaceObserver mInterfaceObserver;
|
||||
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
|
||||
new ConcurrentHashMap<>();
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
|
||||
/** For static IP configuration */
|
||||
private EthernetManager mEthernetManager;
|
||||
public EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter) {
|
||||
super(handler.getLooper(), context, NETWORK_TYPE, filter);
|
||||
|
||||
/** To set link state and configure IP addresses. */
|
||||
private INetworkManagementService mNMService;
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
|
||||
/** All code runs here, including start(). */
|
||||
private Handler mHandler;
|
||||
|
||||
/* To communicate with ConnectivityManager */
|
||||
private NetworkCapabilities mNetworkCapabilities;
|
||||
private NetworkAgent mNetworkAgent;
|
||||
private LocalNetworkFactory mFactory;
|
||||
private Context mContext;
|
||||
|
||||
/** Product-dependent regular expression of interface names we track. */
|
||||
private static String mIfaceMatch = "";
|
||||
|
||||
/** To notify Ethernet status. */
|
||||
private final RemoteCallbackList<IEthernetServiceListener> mListeners;
|
||||
|
||||
/** Data members. All accesses to these must be on the handler thread. */
|
||||
private String mIface = "";
|
||||
private String mHwAddr;
|
||||
private boolean mLinkUp;
|
||||
private NetworkInfo mNetworkInfo;
|
||||
private LinkProperties mLinkProperties;
|
||||
private IpManager mIpManager;
|
||||
private boolean mNetworkRequested = false;
|
||||
|
||||
EthernetNetworkFactory(RemoteCallbackList<IEthernetServiceListener> listeners) {
|
||||
initNetworkCapabilities();
|
||||
clearInfo();
|
||||
mListeners = listeners;
|
||||
setScoreFilter(NETWORK_SCORE);
|
||||
}
|
||||
|
||||
private class LocalNetworkFactory extends NetworkFactory {
|
||||
LocalNetworkFactory(String name, Context context, Looper looper) {
|
||||
super(looper, context, name, new NetworkCapabilities());
|
||||
@Override
|
||||
public boolean acceptRequest(NetworkRequest request, int score) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "acceptRequest, request: " + request + ", score: " + score);
|
||||
}
|
||||
|
||||
protected void startNetwork() {
|
||||
if (!mNetworkRequested) {
|
||||
mNetworkRequested = true;
|
||||
maybeStartIpManager();
|
||||
}
|
||||
return networkForRequest(request) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
|
||||
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||
|
||||
if (network == null) {
|
||||
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
protected void stopNetwork() {
|
||||
mNetworkRequested = false;
|
||||
stopIpManager();
|
||||
if (++network.refCount == 1) {
|
||||
network.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearInfo() {
|
||||
mLinkProperties = new LinkProperties();
|
||||
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
|
||||
mNetworkInfo.setExtraInfo(mHwAddr);
|
||||
mNetworkInfo.setIsAvailable(isTrackingInterface());
|
||||
}
|
||||
@Override
|
||||
protected void releaseNetworkFor(NetworkRequest networkRequest) {
|
||||
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||
if (network == null) {
|
||||
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
private void stopIpManager() {
|
||||
if (mIpManager != null) {
|
||||
mIpManager.shutdown();
|
||||
mIpManager = null;
|
||||
if (--network.refCount == 1) {
|
||||
network.stop();
|
||||
}
|
||||
// ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
|
||||
// with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
|
||||
// that sets the state to IDLE, and ConnectivityService will still think we're connected.
|
||||
//
|
||||
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
|
||||
if (mNetworkAgent != null) {
|
||||
updateAgent();
|
||||
mNetworkAgent = null;
|
||||
}
|
||||
clearInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates interface state variables.
|
||||
* Called on link state changes or on startup.
|
||||
* Returns an array of available interface names. The array is sorted: unrestricted interfaces
|
||||
* goes first, then sorted by name.
|
||||
*/
|
||||
private void updateInterfaceState(String iface, boolean up) {
|
||||
if (!mIface.equals(iface)) {
|
||||
String[] getAvailableInterfaces(boolean includeRestricted) {
|
||||
return mTrackingInterfaces.values()
|
||||
.stream()
|
||||
.filter(iface -> !iface.isRestricted() || includeRestricted)
|
||||
.sorted((iface1, iface2) -> {
|
||||
int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
|
||||
return r == 0 ? iface1.name.compareTo(iface2.name) : r;
|
||||
})
|
||||
.map(iface -> iface.name)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
void addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
|
||||
IpConfiguration ipConfiguration) {
|
||||
if (mTrackingInterfaces.containsKey(ifaceName)) {
|
||||
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
|
||||
|
||||
mLinkUp = up;
|
||||
if (up) {
|
||||
maybeStartIpManager();
|
||||
if (DBG) {
|
||||
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
|
||||
}
|
||||
|
||||
NetworkInterfaceState iface = new NetworkInterfaceState(
|
||||
ifaceName, hwAddress, mHandler, mContext, capabilities);
|
||||
iface.setIpConfig(ipConfiguration);
|
||||
mTrackingInterfaces.put(ifaceName, iface);
|
||||
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
private void updateCapabilityFilter() {
|
||||
NetworkCapabilities capabilitiesFilter = new NetworkCapabilities();
|
||||
capabilitiesFilter.clearAll();
|
||||
|
||||
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
|
||||
capabilitiesFilter.combineCapabilities(iface.mCapabilities);
|
||||
}
|
||||
|
||||
if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
|
||||
setCapabilityFilter(capabilitiesFilter);
|
||||
}
|
||||
|
||||
void removeInterface(String interfaceName) {
|
||||
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
|
||||
if (iface != null) {
|
||||
iface.stop();
|
||||
}
|
||||
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
/** Returns true if state has been modified */
|
||||
boolean updateInterfaceLinkState(String ifaceName, boolean up) {
|
||||
if (!mTrackingInterfaces.containsKey(ifaceName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
|
||||
}
|
||||
|
||||
NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||
return iface.updateLinkState(up);
|
||||
}
|
||||
|
||||
boolean hasInterface(String interfacName) {
|
||||
return mTrackingInterfaces.containsKey(interfacName);
|
||||
}
|
||||
|
||||
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
|
||||
NetworkInterfaceState network = mTrackingInterfaces.get(iface);
|
||||
if (network != null) {
|
||||
network.setIpConfig(ipConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
|
||||
String requestedIface = null;
|
||||
|
||||
NetworkSpecifier specifier = request.networkCapabilities.getNetworkSpecifier();
|
||||
if (specifier instanceof StringNetworkSpecifier) {
|
||||
requestedIface = ((StringNetworkSpecifier) specifier).specifier;
|
||||
}
|
||||
|
||||
NetworkInterfaceState network = null;
|
||||
if (!TextUtils.isEmpty(requestedIface)) {
|
||||
NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
|
||||
if (n != null && n.statisified(request.networkCapabilities)) {
|
||||
network = n;
|
||||
}
|
||||
} else {
|
||||
stopIpManager();
|
||||
}
|
||||
}
|
||||
|
||||
private class InterfaceObserver extends BaseNetworkObserver {
|
||||
@Override
|
||||
public void interfaceLinkStateChanged(String iface, boolean up) {
|
||||
mHandler.post(() -> {
|
||||
updateInterfaceState(iface, up);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interfaceAdded(String iface) {
|
||||
mHandler.post(() -> {
|
||||
maybeTrackInterface(iface);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interfaceRemoved(String iface) {
|
||||
mHandler.post(() -> {
|
||||
if (stopTrackingInterface(iface)) {
|
||||
trackFirstAvailableInterface();
|
||||
for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
|
||||
if (n.statisified(request.networkCapabilities)) {
|
||||
network = n;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setInterfaceUp(String iface) {
|
||||
// Bring up the interface so we get link status indications.
|
||||
try {
|
||||
mNMService.setInterfaceUp(iface);
|
||||
String hwAddr = null;
|
||||
InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
|
||||
|
||||
if (config == null) {
|
||||
Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTrackingInterface()) {
|
||||
setInterfaceInfo(iface, config.getHardwareAddress());
|
||||
mNetworkInfo.setIsAvailable(true);
|
||||
mNetworkInfo.setExtraInfo(mHwAddr);
|
||||
} else {
|
||||
Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
|
||||
mNMService.setInterfaceDown(iface);
|
||||
}
|
||||
} catch (RemoteException | IllegalStateException e) {
|
||||
// Either the system is crashing or the interface has disappeared. Just ignore the
|
||||
// error; we haven't modified any state because we only do that if our calls succeed.
|
||||
Log.e(TAG, "Error upping interface " + mIface + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean maybeTrackInterface(String iface) {
|
||||
// If we don't already have an interface, and if this interface matches
|
||||
// our regex, start tracking it.
|
||||
if (!iface.matches(mIfaceMatch) || isTrackingInterface())
|
||||
return false;
|
||||
|
||||
Log.d(TAG, "Started tracking interface " + iface);
|
||||
setInterfaceUp(iface);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean stopTrackingInterface(String iface) {
|
||||
if (!iface.equals(mIface))
|
||||
return false;
|
||||
|
||||
Log.d(TAG, "Stopped tracking interface " + iface);
|
||||
setInterfaceInfo("", null);
|
||||
stopIpManager();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateAgent() {
|
||||
if (mNetworkAgent == null) return;
|
||||
if (DBG) {
|
||||
Log.i(TAG, "Updating mNetworkAgent with: " +
|
||||
mNetworkCapabilities + ", " +
|
||||
mNetworkInfo + ", " +
|
||||
mLinkProperties);
|
||||
}
|
||||
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
|
||||
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
|
||||
mNetworkAgent.sendLinkProperties(mLinkProperties);
|
||||
// never set the network score below 0.
|
||||
mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
|
||||
}
|
||||
|
||||
void onIpLayerStarted(LinkProperties linkProperties) {
|
||||
if (mNetworkAgent != null) {
|
||||
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
|
||||
stopIpManager();
|
||||
return;
|
||||
}
|
||||
mLinkProperties = linkProperties;
|
||||
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
|
||||
|
||||
// Create our NetworkAgent.
|
||||
mNetworkAgent = new NetworkAgent(mHandler.getLooper(), mContext,
|
||||
NETWORK_TYPE, mNetworkInfo, mNetworkCapabilities, mLinkProperties,
|
||||
NETWORK_SCORE) {
|
||||
public void unwanted() {
|
||||
if (this == mNetworkAgent) {
|
||||
stopIpManager();
|
||||
} else if (mNetworkAgent != null) {
|
||||
Log.d(TAG, "Ignoring unwanted as we have a more modern " +
|
||||
"instance");
|
||||
} // Otherwise, we've already called stopIpManager.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void onIpLayerStopped(LinkProperties linkProperties) {
|
||||
// This cannot happen due to provisioning timeout, because our timeout is 0. It can only
|
||||
// happen if we're provisioned and we lose provisioning.
|
||||
stopIpManager();
|
||||
maybeStartIpManager();
|
||||
}
|
||||
|
||||
void updateLinkProperties(LinkProperties linkProperties) {
|
||||
mLinkProperties = linkProperties;
|
||||
if (mNetworkAgent != null) {
|
||||
mNetworkAgent.sendLinkProperties(linkProperties);
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeStartIpManager() {
|
||||
if (mNetworkRequested && mIpManager == null && isTrackingInterface()) {
|
||||
startIpManager();
|
||||
}
|
||||
}
|
||||
|
||||
public void startIpManager() {
|
||||
if (DBG) {
|
||||
Log.d(TAG, String.format("starting IpManager(%s): mNetworkInfo=%s", mIface,
|
||||
mNetworkInfo));
|
||||
Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
|
||||
}
|
||||
|
||||
IpConfiguration config = mEthernetManager.getConfiguration();
|
||||
return network;
|
||||
}
|
||||
|
||||
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
|
||||
IpManager.Callback ipmCallback = new IpManager.Callback() {
|
||||
private static class NetworkInterfaceState {
|
||||
final String name;
|
||||
|
||||
private final String mHwAddress;
|
||||
private final NetworkCapabilities mCapabilities;
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
private final NetworkInfo mNetworkInfo;
|
||||
|
||||
private static String sTcpBufferSizes = null; // Lazy initialized.
|
||||
|
||||
private boolean mLinkUp;
|
||||
private LinkProperties mLinkProperties = new LinkProperties();
|
||||
|
||||
private IpClient mIpClient;
|
||||
private NetworkAgent mNetworkAgent;
|
||||
private IpConfiguration mIpConfig;
|
||||
|
||||
long refCount = 0;
|
||||
|
||||
private final IpClient.Callback mIpClientCallback = new IpClient.Callback() {
|
||||
@Override
|
||||
public void onProvisioningSuccess(LinkProperties newLp) {
|
||||
mHandler.post(() -> onIpLayerStarted(newLp));
|
||||
@@ -333,178 +253,191 @@ class EthernetNetworkFactory {
|
||||
}
|
||||
};
|
||||
|
||||
stopIpManager();
|
||||
mIpManager = new IpManager(mContext, mIface, ipmCallback);
|
||||
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
|
||||
NetworkCapabilities capabilities) {
|
||||
name = ifaceName;
|
||||
mCapabilities = capabilities;
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
|
||||
if (config.getProxySettings() == ProxySettings.STATIC ||
|
||||
config.getProxySettings() == ProxySettings.PAC) {
|
||||
mIpManager.setHttpProxy(config.getHttpProxy());
|
||||
mHwAddress = hwAddress;
|
||||
mNetworkInfo = new NetworkInfo(TYPE_ETHERNET, 0, NETWORK_TYPE, "");
|
||||
mNetworkInfo.setExtraInfo(mHwAddress);
|
||||
mNetworkInfo.setIsAvailable(true);
|
||||
}
|
||||
|
||||
final String tcpBufferSizes = mContext.getResources().getString(
|
||||
com.android.internal.R.string.config_ethernet_tcp_buffers);
|
||||
if (!TextUtils.isEmpty(tcpBufferSizes)) {
|
||||
mIpManager.setTcpBufferSizes(tcpBufferSizes);
|
||||
void setIpConfig(IpConfiguration ipConfig) {
|
||||
|
||||
this.mIpConfig = ipConfig;
|
||||
}
|
||||
|
||||
final ProvisioningConfiguration provisioningConfiguration;
|
||||
if (config.getIpAssignment() == IpAssignment.STATIC) {
|
||||
provisioningConfiguration = IpManager.buildProvisioningConfiguration()
|
||||
.withStaticConfiguration(config.getStaticIpConfiguration())
|
||||
.build();
|
||||
} else {
|
||||
provisioningConfiguration = mIpManager.buildProvisioningConfiguration()
|
||||
.withProvisioningTimeoutMs(0)
|
||||
.build();
|
||||
boolean statisified(NetworkCapabilities requestedCapabilities) {
|
||||
return requestedCapabilities.satisfiedByNetworkCapabilities(mCapabilities);
|
||||
}
|
||||
|
||||
mIpManager.startProvisioning(provisioningConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin monitoring connectivity
|
||||
*/
|
||||
public void start(Context context, Handler handler) {
|
||||
mHandler = handler;
|
||||
|
||||
// The services we use.
|
||||
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
|
||||
mNMService = INetworkManagementService.Stub.asInterface(b);
|
||||
mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
|
||||
|
||||
// Interface match regex.
|
||||
mIfaceMatch = context.getResources().getString(
|
||||
com.android.internal.R.string.config_ethernet_iface_regex);
|
||||
|
||||
// Create and register our NetworkFactory.
|
||||
mFactory = new LocalNetworkFactory(NETWORK_TYPE, context, mHandler.getLooper());
|
||||
mFactory.setCapabilityFilter(mNetworkCapabilities);
|
||||
mFactory.setScoreFilter(NETWORK_SCORE);
|
||||
mFactory.register();
|
||||
|
||||
mContext = context;
|
||||
|
||||
// Start tracking interface change events.
|
||||
mInterfaceObserver = new InterfaceObserver();
|
||||
try {
|
||||
mNMService.registerObserver(mInterfaceObserver);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not register InterfaceObserver " + e);
|
||||
boolean isRestricted() {
|
||||
return mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
// If an Ethernet interface is already connected, start tracking that.
|
||||
// Otherwise, the first Ethernet interface to appear will be tracked.
|
||||
mHandler.post(() -> trackFirstAvailableInterface());
|
||||
}
|
||||
private void start() {
|
||||
if (DBG) {
|
||||
Log.d(TAG, String.format("starting IpClient(%s): mNetworkInfo=%s", name,
|
||||
mNetworkInfo));
|
||||
}
|
||||
if (mIpClient != null) stop();
|
||||
|
||||
public void trackFirstAvailableInterface() {
|
||||
try {
|
||||
final String[] ifaces = mNMService.listInterfaces();
|
||||
for (String iface : ifaces) {
|
||||
if (maybeTrackInterface(iface)) {
|
||||
// We have our interface. Track it.
|
||||
// Note: if the interface already has link (e.g., if we crashed and got
|
||||
// restarted while it was running), we need to fake a link up notification so we
|
||||
// start configuring it.
|
||||
if (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
|
||||
updateInterfaceState(iface, true);
|
||||
}
|
||||
break;
|
||||
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddress);
|
||||
|
||||
mIpClient = new IpClient(mContext, name, mIpClientCallback);
|
||||
|
||||
if (sTcpBufferSizes == null) {
|
||||
sTcpBufferSizes = mContext.getResources().getString(
|
||||
com.android.internal.R.string.config_ethernet_tcp_buffers);
|
||||
}
|
||||
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
|
||||
}
|
||||
|
||||
void onIpLayerStarted(LinkProperties linkProperties) {
|
||||
if (mNetworkAgent != null) {
|
||||
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
mLinkProperties = linkProperties;
|
||||
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddress);
|
||||
mNetworkInfo.setIsAvailable(true);
|
||||
|
||||
// Create our NetworkAgent.
|
||||
mNetworkAgent = new NetworkAgent(mHandler.getLooper(), mContext,
|
||||
NETWORK_TYPE, mNetworkInfo, mCapabilities, mLinkProperties,
|
||||
NETWORK_SCORE) {
|
||||
public void unwanted() {
|
||||
if (this == mNetworkAgent) {
|
||||
stop();
|
||||
} else if (mNetworkAgent != null) {
|
||||
Log.d(TAG, "Ignoring unwanted as we have a more modern " +
|
||||
"instance");
|
||||
} // Otherwise, we've already called stop.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void onIpLayerStopped(LinkProperties linkProperties) {
|
||||
// This cannot happen due to provisioning timeout, because our timeout is 0. It can only
|
||||
// happen if we're provisioned and we lose provisioning.
|
||||
start();
|
||||
}
|
||||
|
||||
void updateLinkProperties(LinkProperties linkProperties) {
|
||||
mLinkProperties = linkProperties;
|
||||
if (mNetworkAgent != null) {
|
||||
mNetworkAgent.sendLinkProperties(linkProperties);
|
||||
}
|
||||
} catch (RemoteException|IllegalStateException e) {
|
||||
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||
}
|
||||
|
||||
/** Returns true if state has been modified */
|
||||
boolean updateLinkState(boolean up) {
|
||||
if (mLinkUp == up) return false;
|
||||
|
||||
mLinkUp = up;
|
||||
if (up) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (mIpClient != null) {
|
||||
mIpClient.shutdown();
|
||||
mIpClient = null;
|
||||
}
|
||||
// ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
|
||||
// with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
|
||||
// that sets the state to IDLE, and ConnectivityService will still think we're connected.
|
||||
//
|
||||
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddress);
|
||||
if (mNetworkAgent != null) {
|
||||
updateAgent();
|
||||
mNetworkAgent = null;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
private void updateAgent() {
|
||||
if (mNetworkAgent == null) return;
|
||||
if (DBG) {
|
||||
Log.i(TAG, "Updating mNetworkAgent with: " +
|
||||
mCapabilities + ", " +
|
||||
mNetworkInfo + ", " +
|
||||
mLinkProperties);
|
||||
}
|
||||
mNetworkAgent.sendNetworkCapabilities(mCapabilities);
|
||||
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
|
||||
mNetworkAgent.sendLinkProperties(mLinkProperties);
|
||||
// never set the network score below 0.
|
||||
mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
mLinkProperties.clear();
|
||||
mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
|
||||
mNetworkInfo.setIsAvailable(false);
|
||||
}
|
||||
|
||||
private static void provisionIpClient(IpClient ipClient, IpConfiguration config,
|
||||
String tcpBufferSizes) {
|
||||
if (config.getProxySettings() == ProxySettings.STATIC ||
|
||||
config.getProxySettings() == ProxySettings.PAC) {
|
||||
ipClient.setHttpProxy(config.getHttpProxy());
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(tcpBufferSizes)) {
|
||||
ipClient.setTcpBufferSizes(tcpBufferSizes);
|
||||
}
|
||||
|
||||
final ProvisioningConfiguration provisioningConfiguration;
|
||||
if (config.getIpAssignment() == IpAssignment.STATIC) {
|
||||
provisioningConfiguration = IpClient.buildProvisioningConfiguration()
|
||||
.withStaticConfiguration(config.getStaticIpConfiguration())
|
||||
.build();
|
||||
} else {
|
||||
provisioningConfiguration = IpClient.buildProvisioningConfiguration()
|
||||
.withProvisioningTimeoutMs(0)
|
||||
.build();
|
||||
}
|
||||
|
||||
ipClient.startProvisioning(provisioningConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{ "
|
||||
+ "iface: " + name + ", "
|
||||
+ "up: " + mLinkUp + ", "
|
||||
+ "hwAddress: " + mHwAddress + ", "
|
||||
+ "networkInfo: " + mNetworkInfo + ", "
|
||||
+ "networkAgent: " + mNetworkAgent + ", "
|
||||
+ "ipClient: " + mIpClient + ","
|
||||
+ "linkProperties: " + mLinkProperties
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
stopIpManager();
|
||||
setInterfaceInfo("", null);
|
||||
mFactory.unregister();
|
||||
}
|
||||
|
||||
private void initNetworkCapabilities() {
|
||||
mNetworkCapabilities = new NetworkCapabilities();
|
||||
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
// We have no useful data on bandwidth. Say 100M up and 100M down. :-(
|
||||
mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
|
||||
mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000);
|
||||
}
|
||||
|
||||
public boolean isTrackingInterface() {
|
||||
return !TextUtils.isEmpty(mIface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set interface information and notify listeners if availability is changed.
|
||||
*/
|
||||
private void setInterfaceInfo(String iface, String hwAddr) {
|
||||
boolean oldAvailable = isTrackingInterface();
|
||||
mIface = iface;
|
||||
mHwAddr = hwAddr;
|
||||
boolean available = isTrackingInterface();
|
||||
|
||||
mNetworkInfo.setExtraInfo(mHwAddr);
|
||||
mNetworkInfo.setIsAvailable(available);
|
||||
|
||||
if (oldAvailable != available) {
|
||||
int n = mListeners.beginBroadcast();
|
||||
for (int i = 0; i < n; i++) {
|
||||
try {
|
||||
mListeners.getBroadcastItem(i).onAvailabilityChanged(available);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
private void postAndWaitForRunnable(Runnable r) throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
latch.await();
|
||||
}
|
||||
|
||||
|
||||
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||
try {
|
||||
postAndWaitForRunnable(() -> {
|
||||
pw.println("Network Requested: " + mNetworkRequested);
|
||||
if (isTrackingInterface()) {
|
||||
pw.println("Tracking interface: " + mIface);
|
||||
pw.increaseIndent();
|
||||
pw.println("MAC address: " + mHwAddr);
|
||||
pw.println("Link state: " + (mLinkUp ? "up" : "down"));
|
||||
pw.decreaseIndent();
|
||||
} else {
|
||||
pw.println("Not tracking any interface");
|
||||
}
|
||||
|
||||
pw.println();
|
||||
pw.println("NetworkInfo: " + mNetworkInfo);
|
||||
pw.println("LinkProperties: " + mLinkProperties);
|
||||
pw.println("NetworkAgent: " + mNetworkAgent);
|
||||
if (mIpManager != null) {
|
||||
pw.println("IpManager:");
|
||||
pw.increaseIndent();
|
||||
mIpManager.dump(fd, pw, args);
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalStateException("dump() interrupted");
|
||||
super.dump(fd, pw, args);
|
||||
pw.println(getClass().getSimpleName());
|
||||
pw.println("Tracking interfaces:");
|
||||
pw.increaseIndent();
|
||||
for (String iface: mTrackingInterfaces.keySet()) {
|
||||
NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
|
||||
pw.println(iface + ":" + ifaceState);
|
||||
pw.increaseIndent();
|
||||
ifaceState.mIpClient.dump(fd, pw, args);
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,10 @@ import android.content.pm.PackageManager;
|
||||
import android.net.IEthernetManager;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.PrintWriterPrinter;
|
||||
|
||||
@@ -41,31 +37,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
/**
|
||||
* EthernetServiceImpl handles remote Ethernet operation requests by implementing
|
||||
* the IEthernetManager interface.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
private static final String TAG = "EthernetServiceImpl";
|
||||
|
||||
private final Context mContext;
|
||||
private final EthernetConfigStore mEthernetConfigStore;
|
||||
private final AtomicBoolean mStarted = new AtomicBoolean(false);
|
||||
private IpConfiguration mIpConfiguration;
|
||||
|
||||
private Handler mHandler;
|
||||
private final EthernetNetworkFactory mTracker;
|
||||
private final RemoteCallbackList<IEthernetServiceListener> mListeners =
|
||||
new RemoteCallbackList<IEthernetServiceListener>();
|
||||
private EthernetTracker mTracker;
|
||||
|
||||
public EthernetServiceImpl(Context context) {
|
||||
mContext = context;
|
||||
Log.i(TAG, "Creating EthernetConfigStore");
|
||||
mEthernetConfigStore = new EthernetConfigStore();
|
||||
mIpConfiguration = mEthernetConfigStore.readIpAndProxyConfigurations();
|
||||
|
||||
Log.i(TAG, "Read stored IP configuration: " + mIpConfiguration);
|
||||
|
||||
mTracker = new EthernetNetworkFactory(mListeners);
|
||||
}
|
||||
|
||||
private void enforceAccessPermission() {
|
||||
@@ -80,6 +63,18 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
"ConnectivityService");
|
||||
}
|
||||
|
||||
private void enforceUseRestrictedNetworksPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
|
||||
"ConnectivityService");
|
||||
}
|
||||
|
||||
private boolean checkUseRestrictedNetworksPermission() {
|
||||
return mContext.checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Log.i(TAG, "Starting Ethernet service");
|
||||
|
||||
@@ -87,60 +82,68 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
handlerThread.start();
|
||||
mHandler = new Handler(handlerThread.getLooper());
|
||||
|
||||
mTracker.start(mContext, mHandler);
|
||||
mTracker = new EthernetTracker(mContext, mHandler);
|
||||
mTracker.start();
|
||||
|
||||
mStarted.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAvailableInterfaces() throws RemoteException {
|
||||
return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet configuration
|
||||
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
|
||||
*/
|
||||
@Override
|
||||
public IpConfiguration getConfiguration() {
|
||||
public IpConfiguration getConfiguration(String iface) {
|
||||
enforceAccessPermission();
|
||||
|
||||
synchronized (mIpConfiguration) {
|
||||
return new IpConfiguration(mIpConfiguration);
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
enforceUseRestrictedNetworksPermission();
|
||||
}
|
||||
|
||||
return new IpConfiguration(mTracker.getIpConfiguration(iface));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Ethernet configuration
|
||||
*/
|
||||
@Override
|
||||
public void setConfiguration(IpConfiguration config) {
|
||||
public void setConfiguration(String iface, IpConfiguration config) {
|
||||
if (!mStarted.get()) {
|
||||
Log.w(TAG, "System isn't ready enough to change ethernet configuration");
|
||||
}
|
||||
|
||||
enforceConnectivityInternalPermission();
|
||||
|
||||
synchronized (mIpConfiguration) {
|
||||
mEthernetConfigStore.writeIpAndProxyConfigurations(config);
|
||||
|
||||
// TODO: this does not check proxy settings, gateways, etc.
|
||||
// Fix this by making IpConfiguration a complete representation of static configuration.
|
||||
if (!config.equals(mIpConfiguration)) {
|
||||
mIpConfiguration = new IpConfiguration(config);
|
||||
mTracker.stop();
|
||||
mTracker.start(mContext, mHandler);
|
||||
}
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
enforceUseRestrictedNetworksPermission();
|
||||
}
|
||||
|
||||
// TODO: this does not check proxy settings, gateways, etc.
|
||||
// Fix this by making IpConfiguration a complete representation of static configuration.
|
||||
mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the system currently has one or more
|
||||
* Ethernet interfaces.
|
||||
* Indicates whether given interface is available.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
public boolean isAvailable(String iface) {
|
||||
enforceAccessPermission();
|
||||
return mTracker.isTrackingInterface();
|
||||
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
enforceUseRestrictedNetworksPermission();
|
||||
}
|
||||
|
||||
return mTracker.isTrackingInterface(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Addes a listener.
|
||||
* Adds a listener.
|
||||
* @param listener A {@link IEthernetServiceListener} to add.
|
||||
*/
|
||||
public void addListener(IEthernetServiceListener listener) {
|
||||
@@ -148,7 +151,7 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
throw new IllegalArgumentException("listener must not be null");
|
||||
}
|
||||
enforceAccessPermission();
|
||||
mListeners.register(listener);
|
||||
mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +163,7 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
throw new IllegalArgumentException("listener must not be null");
|
||||
}
|
||||
enforceAccessPermission();
|
||||
mListeners.unregister(listener);
|
||||
mTracker.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,12 +182,6 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
mTracker.dump(fd, pw, args);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println();
|
||||
pw.println("Stored Ethernet configuration: ");
|
||||
pw.increaseIndent();
|
||||
pw.println(mIpConfiguration);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println("Handler:");
|
||||
pw.increaseIndent();
|
||||
mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
|
||||
|
||||
383
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
383
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.server.ethernet;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.InterfaceConfiguration;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Tracks Ethernet interfaces and manages interface configurations.
|
||||
*
|
||||
* <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
|
||||
* in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
|
||||
* not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
|
||||
* Interfaces could have associated {@link android.net.IpConfiguration}.
|
||||
* Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
|
||||
* connected over USB). This class supports multiple interfaces. When an interface appears on the
|
||||
* system (or is present at boot time) this class will start tracking it and bring it up. Only
|
||||
* interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
|
||||
* tracked.
|
||||
*
|
||||
* <p>All public or package private methods must be thread-safe unless stated otherwise.
|
||||
*/
|
||||
final class EthernetTracker {
|
||||
private final static String TAG = EthernetTracker.class.getSimpleName();
|
||||
private final static boolean DBG = EthernetNetworkFactory.DBG;
|
||||
|
||||
/** Product-dependent regular expression of interface names we track. */
|
||||
private final String mIfaceMatch;
|
||||
|
||||
/** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
|
||||
private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
|
||||
new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final INetworkManagementService mNMService;
|
||||
private final Handler mHandler;
|
||||
private final EthernetNetworkFactory mFactory;
|
||||
private final EthernetConfigStore mConfigStore;
|
||||
|
||||
private final RemoteCallbackList<IEthernetServiceListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
private volatile IpConfiguration mIpConfigForDefaultInterface;
|
||||
|
||||
EthernetTracker(Context context, Handler handler) {
|
||||
mHandler = handler;
|
||||
|
||||
// The services we use.
|
||||
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
|
||||
mNMService = INetworkManagementService.Stub.asInterface(b);
|
||||
|
||||
// Interface match regex.
|
||||
mIfaceMatch = context.getResources().getString(
|
||||
com.android.internal.R.string.config_ethernet_iface_regex);
|
||||
|
||||
// Read default Ethernet interface configuration from resources
|
||||
final String[] interfaceConfigs = context.getResources().getStringArray(
|
||||
com.android.internal.R.array.config_ethernet_interfaces);
|
||||
for (String strConfig : interfaceConfigs) {
|
||||
parseEthernetConfig(strConfig);
|
||||
}
|
||||
|
||||
mConfigStore = new EthernetConfigStore();
|
||||
|
||||
NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
|
||||
mFactory = new EthernetNetworkFactory(handler, context, nc);
|
||||
mFactory.register();
|
||||
}
|
||||
|
||||
void start() {
|
||||
mConfigStore.read();
|
||||
|
||||
// Default interface is just the first one we want to track.
|
||||
mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
|
||||
final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
|
||||
}
|
||||
|
||||
try {
|
||||
mNMService.registerObserver(new InterfaceObserver());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not register InterfaceObserver " + e);
|
||||
}
|
||||
|
||||
mHandler.post(this::trackAvailableInterfaces);
|
||||
}
|
||||
|
||||
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
|
||||
}
|
||||
|
||||
mConfigStore.write(iface, ipConfiguration);
|
||||
mIpConfigurations.put(iface, ipConfiguration);
|
||||
|
||||
mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
|
||||
}
|
||||
|
||||
IpConfiguration getIpConfiguration(String iface) {
|
||||
return mIpConfigurations.get(iface);
|
||||
}
|
||||
|
||||
boolean isTrackingInterface(String iface) {
|
||||
return mFactory.hasInterface(iface);
|
||||
}
|
||||
|
||||
String[] getInterfaces(boolean includeRestricted) {
|
||||
return mFactory.getAvailableInterfaces(includeRestricted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given interface was configured as restricted (doesn't have
|
||||
* NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
|
||||
*/
|
||||
boolean isRestrictedInterface(String iface) {
|
||||
final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||
return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
|
||||
mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
|
||||
}
|
||||
|
||||
void removeListener(IEthernetServiceListener listener) {
|
||||
mListeners.unregister(listener);
|
||||
}
|
||||
|
||||
private void removeInterface(String iface) {
|
||||
mFactory.removeInterface(iface);
|
||||
}
|
||||
|
||||
private void addInterface(String iface) {
|
||||
InterfaceConfiguration config = null;
|
||||
// Bring up the interface so we get link status indications.
|
||||
try {
|
||||
mNMService.setInterfaceUp(iface);
|
||||
config = mNMService.getInterfaceConfig(iface);
|
||||
} catch (RemoteException | IllegalStateException e) {
|
||||
// Either the system is crashing or the interface has disappeared. Just ignore the
|
||||
// error; we haven't modified any state because we only do that if our calls succeed.
|
||||
Log.e(TAG, "Error upping interface " + iface, e);
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
|
||||
return;
|
||||
}
|
||||
|
||||
final String hwAddress = config.getHardwareAddress();
|
||||
|
||||
NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||
if (nc == null) {
|
||||
// Try to resolve using mac address
|
||||
nc = mNetworkCapabilities.get(hwAddress);
|
||||
if (nc == null) {
|
||||
nc = createDefaultNetworkCapabilities();
|
||||
}
|
||||
}
|
||||
IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
|
||||
if (ipConfiguration == null) {
|
||||
ipConfiguration = createDefaultIpConfiguration();
|
||||
}
|
||||
|
||||
Log.d(TAG, "Started tracking interface " + iface);
|
||||
mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
|
||||
|
||||
// Note: if the interface already has link (e.g., if we crashed and got
|
||||
// restarted while it was running), we need to fake a link up notification so we
|
||||
// start configuring it.
|
||||
if (config.hasFlag("running")) {
|
||||
updateInterfaceState(iface, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInterfaceState(String iface, boolean up) {
|
||||
boolean modified = mFactory.updateInterfaceLinkState(iface, up);
|
||||
if (modified) {
|
||||
boolean restricted = isRestrictedInterface(iface);
|
||||
int n = mListeners.beginBroadcast();
|
||||
for (int i = 0; i < n; i++) {
|
||||
try {
|
||||
if (restricted) {
|
||||
ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i);
|
||||
if (!listenerInfo.canUseRestrictedNetworks) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeTrackInterface(String iface) {
|
||||
if (DBG) Log.i(TAG, "maybeTrackInterface " + iface);
|
||||
// If we don't already track this interface, and if this interface matches
|
||||
// our regex, start tracking it.
|
||||
if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIpConfigForDefaultInterface != null) {
|
||||
updateIpConfiguration(iface, mIpConfigForDefaultInterface);
|
||||
mIpConfigForDefaultInterface = null;
|
||||
}
|
||||
|
||||
addInterface(iface);
|
||||
}
|
||||
|
||||
private void trackAvailableInterfaces() {
|
||||
try {
|
||||
final String[] ifaces = mNMService.listInterfaces();
|
||||
for (String iface : ifaces) {
|
||||
maybeTrackInterface(iface);
|
||||
}
|
||||
} catch (RemoteException | IllegalStateException e) {
|
||||
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class InterfaceObserver extends BaseNetworkObserver {
|
||||
|
||||
@Override
|
||||
public void interfaceLinkStateChanged(String iface, boolean up) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
|
||||
}
|
||||
mHandler.post(() -> updateInterfaceState(iface, up));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interfaceAdded(String iface) {
|
||||
mHandler.post(() -> maybeTrackInterface(iface));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interfaceRemoved(String iface) {
|
||||
mHandler.post(() -> removeInterface(iface));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListenerInfo {
|
||||
|
||||
boolean canUseRestrictedNetworks = false;
|
||||
|
||||
ListenerInfo(boolean canUseRestrictedNetworks) {
|
||||
this.canUseRestrictedNetworks = canUseRestrictedNetworks;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseEthernetConfig(String configString) {
|
||||
String[] tokens = configString.split(";");
|
||||
String name = tokens[0];
|
||||
String capabilities = tokens.length > 1 ? tokens[1] : null;
|
||||
NetworkCapabilities nc = createNetworkCapabilities(
|
||||
!TextUtils.isEmpty(capabilities) /* clear default capabilities */, capabilities);
|
||||
mNetworkCapabilities.put(name, nc);
|
||||
|
||||
if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
|
||||
IpConfiguration ipConfig = createStaticIpConfiguration(tokens[2]);
|
||||
mIpConfigurations.put(name, ipConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createDefaultNetworkCapabilities() {
|
||||
NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
|
||||
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
|
||||
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
|
||||
|
||||
return nc;
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
|
||||
return createNetworkCapabilities(clearDefaultCapabilities, null);
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createNetworkCapabilities(
|
||||
boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) {
|
||||
|
||||
NetworkCapabilities nc = new NetworkCapabilities();
|
||||
if (clearDefaultCapabilities) {
|
||||
nc.clearAll(); // Remove default capabilities.
|
||||
}
|
||||
nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||
nc.setLinkUpstreamBandwidthKbps(100 * 1000);
|
||||
nc.setLinkDownstreamBandwidthKbps(100 * 1000);
|
||||
|
||||
if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
|
||||
for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
|
||||
if (!TextUtils.isEmpty(strNetworkCapability)) {
|
||||
nc.addCapability(Integer.valueOf(strNetworkCapability));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nc;
|
||||
}
|
||||
|
||||
private static IpConfiguration createStaticIpConfiguration(String strIpAddress) {
|
||||
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
|
||||
staticIpConfiguration.ipAddress = new LinkAddress(strIpAddress);
|
||||
return new IpConfiguration(
|
||||
IpAssignment.STATIC, ProxySettings.NONE, staticIpConfiguration, null);
|
||||
}
|
||||
|
||||
private static IpConfiguration createDefaultIpConfiguration() {
|
||||
return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
|
||||
}
|
||||
|
||||
private void postAndWaitForRunnable(Runnable r) {
|
||||
mHandler.runWithScissors(r, 2000L /* timeout */);
|
||||
}
|
||||
|
||||
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||
postAndWaitForRunnable(() -> {
|
||||
pw.println(getClass().getSimpleName());
|
||||
pw.println("Ethernet interface name filter: " + mIfaceMatch);
|
||||
pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
|
||||
pw.println("IP Configurations:");
|
||||
pw.increaseIndent();
|
||||
for (String iface : mIpConfigurations.keySet()) {
|
||||
pw.println(iface + ": " + mIpConfigurations.get(iface));
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
pw.println("Network Capabilities:");
|
||||
pw.increaseIndent();
|
||||
for (String iface : mNetworkCapabilities.keySet()) {
|
||||
pw.println(iface + ": " + mNetworkCapabilities.get(iface));
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
mFactory.dump(fd, pw, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user