From 52d046eb8a3d29931f233f992df6df48f709c7dc Mon Sep 17 00:00:00 2001 From: Pavel Maltsev Date: Tue, 31 Oct 2017 15:42:16 -0700 Subject: [PATCH] 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 --- .../server/ethernet/EthernetConfigStore.java | 69 +- .../ethernet/EthernetNetworkFactory.java | 741 ++++++++---------- .../server/ethernet/EthernetServiceImpl.java | 89 +-- .../server/ethernet/EthernetTracker.java | 383 +++++++++ 4 files changed, 809 insertions(+), 473 deletions(-) create mode 100644 service-t/src/com/android/server/ethernet/EthernetTracker.java diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java index 676bc99d8a..6445603b96 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 @@ 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 mIpConfigurations; + private IpConfiguration mIpConfigurationForDefaultInterface; + private final Object mSync = new Object(); + public EthernetConfigStore() { + mIpConfigurations = new ArrayMap<>(0); } - public IpConfiguration readIpAndProxyConfigurations() { - SparseArray networks = readIpAndProxyConfigurations(ipConfigFile); + public void read() { + synchronized (mSync) { + ArrayMap 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 networks = new SparseArray(); - 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 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 2e5d09e2ca..29464b78ce 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.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 config_ethernet_iface_regex - * 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 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 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 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(); } } diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java index 42996d6826..d5beec1cb4 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.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 mListeners = - new RemoteCallbackList(); + 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"); 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 0000000000..7f893e0339 --- /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. + * + *

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. + * + *

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 mNetworkCapabilities = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap mIpConfigurations = + new ConcurrentHashMap<>(); + + private final INetworkManagementService mNMService; + private final Handler mHandler; + private final EthernetNetworkFactory mFactory; + private final EthernetConfigStore mConfigStore; + + private final RemoteCallbackList 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 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); + }); + } +}