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
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 676bc99..6445603 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -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 @@
/**
* 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);
+ }
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 2e5d09e..29464b7 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -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.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 @@
}
};
- 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();
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index 42996d6..d5beec1 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -21,14 +21,10 @@
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 @@
/**
* 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 @@
"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 @@
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 @@
throw new IllegalArgumentException("listener must not be null");
}
enforceAccessPermission();
- mListeners.register(listener);
+ mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
}
/**
@@ -160,7 +163,7 @@
throw new IllegalArgumentException("listener must not be null");
}
enforceAccessPermission();
- mListeners.unregister(listener);
+ mTracker.removeListener(listener);
}
@Override
@@ -179,12 +182,6 @@
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");
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
new file mode 100644
index 0000000..7f893e0
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -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);
+ });
+ }
+}