/* * 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 static android.net.EthernetManager.ETHERNET_STATE_DISABLED; import static android.net.EthernetManager.ETHERNET_STATE_ENABLED; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.TestNetworkManager.TEST_TAP_PREFIX; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.net.module.util.netlink.NetlinkConstants.IFF_UP; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.EthernetManager; import android.net.IEthernetServiceListener; import android.net.INetd; import android.net.ITetheredInterfaceCallback; import android.net.InterfaceConfigurationParcel; 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.ConditionVariable; import android.os.Handler; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.system.OsConstants; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.HandlerUtils; import com.android.net.module.util.NetdUtils; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.NetlinkMonitor; import com.android.net.module.util.netlink.NetlinkConstants; import com.android.net.module.util.netlink.NetlinkMessage; import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.netlink.RtNetlinkLinkMessage; import com.android.net.module.util.netlink.StructIfinfoMsg; import com.android.server.connectivity.ConnectivityResources; import java.io.FileDescriptor; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Objects; 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. */ @VisibleForTesting(visibility = PACKAGE) public class EthernetTracker { private static final int INTERFACE_MODE_CLIENT = 1; private static final int INTERFACE_MODE_SERVER = 2; private static final String TAG = EthernetTracker.class.getSimpleName(); private static final boolean DBG = EthernetNetworkFactory.DBG; private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+"; // TODO: consider using SharedLog consistently across ethernet service. private static final SharedLog sLog = new SharedLog(TAG); @VisibleForTesting public static final NetworkCapabilities DEFAULT_CAPABILITIES = new NetworkCapabilities.Builder() .addTransportType(TRANSPORT_ETHERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) // TODO: do not hardcode link bandwidth. .setLinkUpstreamBandwidthKbps(100 * 1000 /* 100 Mbps */) .setLinkDownstreamBandwidthKbps(100 * 1000 /* 100 Mbps */) .build(); /** * Interface names we track. This is a product-dependent regular expression. * Use isValidEthernetInterface to check if a interface name is a valid ethernet interface (this * includes test interfaces if setIncludeTestInterfaces is set to true). */ private final String mIfaceMatch; /** * Track test interfaces if true, don't track otherwise. * Volatile is needed as getEthernetInterfaceList() does not run on the handler thread. */ private volatile boolean mIncludeTestInterfaces = false; /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */ private final ConcurrentHashMap mNetworkCapabilities = new ConcurrentHashMap<>(); private final ConcurrentHashMap mIpConfigurations = new ConcurrentHashMap<>(); private final Context mContext; private final INetd mNetd; private final Handler mHandler; private final EthernetNetworkFactory mFactory; private final EthernetConfigStore mConfigStore; private final NetlinkMonitor mNetlinkMonitor; private final Dependencies mDeps; private final RemoteCallbackList mListeners = new RemoteCallbackList<>(); private final TetheredInterfaceRequestList mTetheredInterfaceRequests = new TetheredInterfaceRequestList(); // The first interface discovered is set as the mTetheringInterface. It is the interface that is // returned when a tethered interface is requested; until then, it remains in client mode. Its // current mode is reflected in mTetheringInterfaceMode. private String mTetheringInterface; // If the tethering interface is in server mode, it is not tracked by factory. The HW address // must be maintained by the EthernetTracker. Its current mode is reflected in // mTetheringInterfaceMode. private String mTetheringInterfaceHwAddr; private int mTetheringInterfaceMode = INTERFACE_MODE_CLIENT; // Tracks whether clients were notified that the tethered interface is available private boolean mTetheredInterfaceWasAvailable = false; // Tracks the current state of ethernet as configured by EthernetManager#setEthernetEnabled. private boolean mIsEthernetEnabled = true; private class TetheredInterfaceRequestList extends RemoteCallbackList { @Override public void onCallbackDied(ITetheredInterfaceCallback cb, Object cookie) { mHandler.post(EthernetTracker.this::maybeUntetherInterface); } } public static class Dependencies { public String getInterfaceRegexFromResource(Context context) { final ConnectivityResources resources = new ConnectivityResources(context); return resources.get().getString( com.android.connectivity.resources.R.string.config_ethernet_iface_regex); } public String[] getInterfaceConfigFromResource(Context context) { final ConnectivityResources resources = new ConnectivityResources(context); return resources.get().getStringArray( com.android.connectivity.resources.R.array.config_ethernet_interfaces); } public boolean isAtLeastB() { return SdkLevel.isAtLeastB(); } } private class EthernetNetlinkMonitor extends NetlinkMonitor { EthernetNetlinkMonitor(Handler handler) { super(handler, sLog, EthernetNetlinkMonitor.class.getSimpleName(), OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_LINK); } private void onNewLink(String ifname, boolean linkUp) { if (!mFactory.hasInterface(ifname) && !ifname.equals(mTetheringInterface)) { Log.i(TAG, "onInterfaceAdded, iface: " + ifname); maybeTrackInterface(ifname); } Log.i(TAG, "interfaceLinkStateChanged, iface: " + ifname + ", up: " + linkUp); updateInterfaceState(ifname, linkUp); } private void onDelLink(String ifname) { Log.i(TAG, "onInterfaceRemoved, iface: " + ifname); stopTrackingInterface(ifname); } private void processRtNetlinkLinkMessage(RtNetlinkLinkMessage msg) { final StructIfinfoMsg ifinfomsg = msg.getIfinfoHeader(); // check if the message is valid if (ifinfomsg.family != OsConstants.AF_UNSPEC) return; // ignore messages for the loopback interface if ((ifinfomsg.flags & OsConstants.IFF_LOOPBACK) != 0) return; // check if the received message applies to an ethernet interface. final String ifname = msg.getInterfaceName(); if (!isValidEthernetInterface(ifname)) return; switch (msg.getHeader().nlmsg_type) { case NetlinkConstants.RTM_NEWLINK: final boolean linkUp = (ifinfomsg.flags & NetlinkConstants.IFF_LOWER_UP) != 0; onNewLink(ifname, linkUp); break; case NetlinkConstants.RTM_DELLINK: onDelLink(ifname); break; default: Log.e(TAG, "Unknown rtnetlink link msg type: " + msg); break; } } // Note: processNetlinkMessage is called on the handler thread. @Override protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { if (nlMsg instanceof RtNetlinkLinkMessage) { processRtNetlinkLinkMessage((RtNetlinkLinkMessage) nlMsg); } else { Log.e(TAG, "Unknown netlink message: " + nlMsg); } } } EthernetTracker(@NonNull final Context context, @NonNull final Handler handler, @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) { this(context, handler, factory, netd, new Dependencies()); } @VisibleForTesting EthernetTracker(@NonNull final Context context, @NonNull final Handler handler, @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd, @NonNull final Dependencies deps) { mContext = context; mHandler = handler; mFactory = factory; mNetd = netd; mDeps = deps; // Interface match regex. String ifaceMatchRegex = mDeps.getInterfaceRegexFromResource(mContext); // "*" is a magic string to indicate "pick the default". if (ifaceMatchRegex.equals("*")) { if (SdkLevel.isAtLeastV()) { // On V+, include both usb%d and eth%d interfaces. ifaceMatchRegex = "(usb|eth)\\d+"; } else { // On T and U, include only eth%d interfaces. ifaceMatchRegex = "eth\\d+"; } } mIfaceMatch = ifaceMatchRegex; // Read default Ethernet interface configuration from resources final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context); for (String strConfig : interfaceConfigs) { parseEthernetConfig(strConfig); } mConfigStore = new EthernetConfigStore(); mNetlinkMonitor = new EthernetNetlinkMonitor(mHandler); } void start() { mFactory.register(); mConfigStore.read(); final ArrayMap configs = mConfigStore.getIpConfigurations(); for (int i = 0; i < configs.size(); i++) { mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i)); } mHandler.post(() -> { mNetlinkMonitor.start(); trackAvailableInterfaces(); }); } void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) { if (DBG) { Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration); } writeIpConfiguration(iface, ipConfiguration); mHandler.post(() -> { mFactory.updateInterface(iface, ipConfiguration, null); broadcastInterfaceStateChange(iface); }); } private void writeIpConfiguration(@NonNull final String iface, @NonNull final IpConfiguration ipConfig) { mConfigStore.write(iface, ipConfig); mIpConfigurations.put(iface, ipConfig); } private IpConfiguration getIpConfigurationForCallback(String iface, int state) { return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface); } private void ensureRunningOnEthernetServiceThread() { HandlerUtils.ensureRunningOnHandlerThread(mHandler); } /** * Broadcast the link state or IpConfiguration change of existing Ethernet interfaces to all * listeners. */ protected void broadcastInterfaceStateChange(@NonNull String iface) { ensureRunningOnEthernetServiceThread(); final int state = getInterfaceState(iface); final int role = getInterfaceRole(iface); final IpConfiguration config = getIpConfigurationForCallback(iface, state); final boolean isRestricted = isRestrictedInterface(iface); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { try { if (isRestricted) { final ListenerInfo info = (ListenerInfo) mListeners.getBroadcastCookie(i); if (!info.canUseRestrictedNetworks) continue; } mListeners.getBroadcastItem(i).onInterfaceStateChanged(iface, state, role, config); } catch (RemoteException e) { // Do nothing here. } } mListeners.finishBroadcast(); } /** * Unicast the interface state or IpConfiguration change of existing Ethernet interfaces to a * specific listener. */ protected void unicastInterfaceStateChange(@NonNull IEthernetServiceListener listener, @NonNull String iface) { ensureRunningOnEthernetServiceThread(); final int state = getInterfaceState(iface); final int role = getInterfaceRole(iface); final IpConfiguration config = getIpConfigurationForCallback(iface, state); try { listener.onInterfaceStateChanged(iface, state, role, config); } catch (RemoteException e) { // Do nothing here. } } @VisibleForTesting(visibility = PACKAGE) protected void updateConfiguration(@NonNull final String iface, @Nullable final IpConfiguration ipConfig, @Nullable final NetworkCapabilities capabilities, @Nullable final EthernetCallback cb) { if (DBG) { Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities + ", ipConfig: " + ipConfig); } // TODO: do the right thing if the interface was in server mode: either fail this operation, // or take the interface out of server mode. final IpConfiguration localIpConfig = ipConfig == null ? null : new IpConfiguration(ipConfig); if (ipConfig != null) { writeIpConfiguration(iface, localIpConfig); } if (null != capabilities) { mNetworkCapabilities.put(iface, capabilities); } mHandler.post(() -> { mFactory.updateInterface(iface, localIpConfig, capabilities); // only broadcast state change when the ip configuration is updated. if (ipConfig != null) { broadcastInterfaceStateChange(iface); } // Always return success. Even if the interface does not currently exist, the // IpConfiguration and NetworkCapabilities were saved and will be applied if an // interface with the given name is ever added. cb.onResult(iface); }); } /** Configure the administrative state of ethernet interface by toggling IFF_UP. */ public void setInterfaceEnabled(String iface, boolean enabled, EthernetCallback cb) { mHandler.post(() -> setInterfaceAdministrativeState(iface, enabled, cb)); } IpConfiguration getIpConfiguration(String iface) { return mIpConfigurations.get(iface); } @VisibleForTesting(visibility = PACKAGE) protected boolean isTrackingInterface(String iface) { return mFactory.hasInterface(iface); } private List getAllInterfaces() { final ArrayList interfaces = new ArrayList<>( List.of(mFactory.getAvailableInterfaces(/* includeRestricted */ true))); if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER && mTetheringInterface != null) { interfaces.add(mTetheringInterface); } return interfaces; } String[] getClientModeInterfaces(boolean includeRestricted) { return mFactory.getAvailableInterfaces(includeRestricted); } List getEthernetInterfaceList() { final List interfaceList = new ArrayList(); final Enumeration ifaces; try { ifaces = NetworkInterface.getNetworkInterfaces(); } catch (SocketException e) { Log.e(TAG, "Failed to get ethernet interfaces: ", e); return interfaceList; } // There is a possible race with setIncludeTestInterfaces() which can affect // isValidEthernetInterface (it returns true for test interfaces if setIncludeTestInterfaces // is set to true). // setIncludeTestInterfaces() is only used in tests, and since getEthernetInterfaceList() // does not run on the handler thread, the behavior around setIncludeTestInterfaces() is // indeterminate either way. This can easily be circumvented by waiting on a callback from // a test interface after calling setIncludeTestInterfaces() before calling this function. // In production code, this has no effect. while (ifaces.hasMoreElements()) { NetworkInterface iface = ifaces.nextElement(); if (isValidEthernetInterface(iface.getName())) interfaceList.add(iface.getName()); } return interfaceList; } /** * 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) { mHandler.post(() -> { if (!mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks))) { // Remote process has already died return; } for (String iface : getClientModeInterfaces(canUseRestrictedNetworks)) { unicastInterfaceStateChange(listener, iface); } if (mTetheringInterface != null && mTetheringInterfaceMode == INTERFACE_MODE_SERVER) { unicastInterfaceStateChange(listener, mTetheringInterface); } unicastEthernetStateChange(listener, mIsEthernetEnabled); }); } void removeListener(IEthernetServiceListener listener) { mHandler.post(() -> mListeners.unregister(listener)); } public void setIncludeTestInterfaces(boolean include) { mHandler.post(() -> { mIncludeTestInterfaces = include; if (include) { trackAvailableInterfaces(); } else { removeTestData(); // remove all test interfaces for (String iface : getAllInterfaces()) { if (isValidEthernetInterface(iface)) continue; stopTrackingInterface(iface); } } }); } private void removeTestData() { removeTestIpData(); removeTestCapabilityData(); } private void removeTestIpData() { final Iterator iterator = mIpConfigurations.keySet().iterator(); while (iterator.hasNext()) { final String iface = iterator.next(); if (iface.matches(TEST_IFACE_REGEXP)) { mConfigStore.write(iface, null); iterator.remove(); } } } private void removeTestCapabilityData() { mNetworkCapabilities.keySet().removeIf(iface -> iface.matches(TEST_IFACE_REGEXP)); } public void requestTetheredInterface(ITetheredInterfaceCallback callback) { mHandler.post(() -> { if (!mTetheredInterfaceRequests.register(callback)) { // Remote process has already died return; } if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) { if (mTetheredInterfaceWasAvailable) { notifyTetheredInterfaceAvailable(callback, mTetheringInterface); } return; } setTetheringInterfaceMode(INTERFACE_MODE_SERVER); }); } public void releaseTetheredInterface(ITetheredInterfaceCallback callback) { mHandler.post(() -> { mTetheredInterfaceRequests.unregister(callback); maybeUntetherInterface(); }); } private void notifyTetheredInterfaceAvailable(ITetheredInterfaceCallback cb, String iface) { try { cb.onAvailable(iface); } catch (RemoteException e) { Log.e(TAG, "Error sending tethered interface available callback", e); } } private void notifyTetheredInterfaceUnavailable(ITetheredInterfaceCallback cb) { try { cb.onUnavailable(); } catch (RemoteException e) { Log.e(TAG, "Error sending tethered interface available callback", e); } } private void maybeUntetherInterface() { if (mTetheredInterfaceRequests.getRegisteredCallbackCount() > 0) return; if (mTetheringInterfaceMode == INTERFACE_MODE_CLIENT) return; setTetheringInterfaceMode(INTERFACE_MODE_CLIENT); } private void setTetheringInterfaceMode(int mode) { Log.d(TAG, "Setting tethering interface mode to " + mode); mTetheringInterfaceMode = mode; if (mTetheringInterface != null) { removeInterface(mTetheringInterface); addInterface(mTetheringInterface); // when this broadcast is sent, any calls to notifyTetheredInterfaceAvailable or // notifyTetheredInterfaceUnavailable have already happened broadcastInterfaceStateChange(mTetheringInterface); } } private int getInterfaceState(final String iface) { if (mFactory.hasInterface(iface)) { return mFactory.getInterfaceState(iface); } if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) { // server mode interfaces are not tracked by the factory. // TODO(b/234743836): interface state for server mode interfaces is not tracked // properly; just return link up. return EthernetManager.STATE_LINK_UP; } return EthernetManager.STATE_ABSENT; } private int getInterfaceRole(final String iface) { if (mFactory.hasInterface(iface)) { // only client mode interfaces are tracked by the factory. return EthernetManager.ROLE_CLIENT; } if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) { return EthernetManager.ROLE_SERVER; } return EthernetManager.ROLE_NONE; } private int getInterfaceMode(final String iface) { if (iface.equals(mTetheringInterface)) { return mTetheringInterfaceMode; } return INTERFACE_MODE_CLIENT; } private void removeInterface(String iface) { mFactory.removeInterface(iface); maybeUpdateServerModeInterfaceState(iface, false); } private void stopTrackingInterface(String iface) { removeInterface(iface); if (iface.equals(mTetheringInterface)) { mTetheringInterface = null; mTetheringInterfaceHwAddr = null; } broadcastInterfaceStateChange(iface); } private void addInterface(String iface) { InterfaceConfigurationParcel config = null; // Bring up the interface so we get link status indications. try { // Read the flags before attempting to bring up the interface. If the interface is // already running an UP event is created after adding the interface. config = NetdUtils.getInterfaceConfigParcel(mNetd, iface); // Only bring the interface up when ethernet is enabled, otherwise set interface down. setInterfaceUpState(iface, mIsEthernetEnabled); } catch (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 parcelable for " + iface + ". Bailing out."); return; } final String hwAddress = config.hwAddr; if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) { maybeUpdateServerModeInterfaceState(iface, true); mTetheringInterfaceHwAddr = hwAddress; return; } NetworkCapabilities nc = mNetworkCapabilities.get(iface); if (nc == null) { // Try to resolve using mac address nc = mNetworkCapabilities.get(hwAddress); if (nc == null) { final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP); nc = createDefaultNetworkCapabilities(isTestIface); } } IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface); Log.d(TAG, "Tracking interface in client mode: " + iface); mFactory.addInterface(iface, hwAddress, ipConfiguration, nc); // 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 (NetdUtils.hasFlag(config, INetd.IF_FLAG_RUNNING)) { // no need to send an interface state change as this is not a true "state change". The // callers (maybeTrackInterface() and setTetheringInterfaceMode()) already broadcast the // state change. mFactory.updateInterfaceLinkState(iface, true); } } private void setInterfaceAdministrativeState(String iface, boolean up, EthernetCallback cb) { if (!mIsEthernetEnabled) { cb.onError("Cannot enable/disable interface when ethernet is disabled"); return; } if (getInterfaceState(iface) == EthernetManager.STATE_ABSENT) { cb.onError("Failed to enable/disable absent interface: " + iface); return; } if (getInterfaceRole(iface) == EthernetManager.ROLE_SERVER) { // TODO: support setEthernetState for server mode interfaces. cb.onError("Failed to enable/disable interface in server mode: " + iface); return; } setInterfaceUpState(iface, up); cb.onResult(iface); } private void updateInterfaceState(String iface, boolean up) { final int mode = getInterfaceMode(iface); if (mode == INTERFACE_MODE_SERVER) { // TODO: support tracking link state for interfaces in server mode. return; } // If updateInterfaceLinkState returns false, the interface is already in the correct state. if (mFactory.updateInterfaceLinkState(iface, up)) { broadcastInterfaceStateChange(iface); } } private void maybeUpdateServerModeInterfaceState(String iface, boolean available) { if (available == mTetheredInterfaceWasAvailable || !iface.equals(mTetheringInterface)) { return; } Log.d(TAG, (available ? "Tracking" : "No longer tracking") + " interface in server mode: " + iface); final int pendingCbs = mTetheredInterfaceRequests.beginBroadcast(); for (int i = 0; i < pendingCbs; i++) { ITetheredInterfaceCallback item = mTetheredInterfaceRequests.getBroadcastItem(i); if (available) { notifyTetheredInterfaceAvailable(item, iface); } else { notifyTetheredInterfaceUnavailable(item); } } mTetheredInterfaceRequests.finishBroadcast(); mTetheredInterfaceWasAvailable = available; } private void maybeTrackInterface(String iface) { // If we don't already track this interface, and if this interface matches // our regex, start tracking it. if (mFactory.hasInterface(iface) || iface.equals(mTetheringInterface)) { if (DBG) Log.w(TAG, "Ignoring already-tracked interface " + iface); return; } if (DBG) Log.i(TAG, "maybeTrackInterface: " + iface); // Do not use an interface for tethering if it has configured NetworkCapabilities. if (mTetheringInterface == null && !mNetworkCapabilities.containsKey(iface)) { mTetheringInterface = iface; } addInterface(iface); broadcastInterfaceStateChange(iface); } private void trackAvailableInterfaces() { final List ifaces = getEthernetInterfaceList(); for (String iface : ifaces) { maybeTrackInterface(iface); } } private static class ListenerInfo { boolean canUseRestrictedNetworks = false; ListenerInfo(boolean canUseRestrictedNetworks) { this.canUseRestrictedNetworks = canUseRestrictedNetworks; } } /** * Parses an Ethernet interface configuration * * @param configString represents an Ethernet configuration in the following format: {@code * ;[Network Capabilities];[IP config];[Override Transport]} */ private void parseEthernetConfig(String configString) { final EthernetConfigParser config = new EthernetConfigParser(configString, mDeps.isAtLeastB()); mNetworkCapabilities.put(config.mIface, config.mCaps); if (null != config.mIpConfig) { IpConfiguration ipConfig = parseStaticIpConfiguration(config.mIpConfig); mIpConfigurations.put(config.mIface, ipConfig); } } private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(DEFAULT_CAPABILITIES); if (isTestIface) { builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST); // TODO: do not remove INTERNET capability for test networks. builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); } return builder.build(); } /** * Parses static IP configuration. * * @param staticIpConfig represents static IP configuration in the following format: {@code * ip= gateway= dns= * domains=} */ @VisibleForTesting static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) { final StaticIpConfiguration.Builder staticIpConfigBuilder = new StaticIpConfiguration.Builder(); for (String keyValueAsString : staticIpConfig.trim().split(" ")) { if (TextUtils.isEmpty(keyValueAsString)) continue; String[] pair = keyValueAsString.split("="); if (pair.length != 2) { throw new IllegalArgumentException("Unexpected token: " + keyValueAsString + " in " + staticIpConfig); } String key = pair[0]; String value = pair[1]; switch (key) { case "ip": staticIpConfigBuilder.setIpAddress(new LinkAddress(value)); break; case "domains": staticIpConfigBuilder.setDomains(value); break; case "gateway": staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value)); break; case "dns": { ArrayList dnsAddresses = new ArrayList<>(); for (String address: value.split(",")) { dnsAddresses.add(InetAddress.parseNumericAddress(address)); } staticIpConfigBuilder.setDnsServers(dnsAddresses); break; } default : { throw new IllegalArgumentException("Unexpected key: " + key + " in " + staticIpConfig); } } } return createIpConfiguration(staticIpConfigBuilder.build()); } private static IpConfiguration createIpConfiguration( @NonNull final StaticIpConfiguration staticIpConfig) { return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build(); } private IpConfiguration getOrCreateIpConfiguration(String iface) { IpConfiguration ret = mIpConfigurations.get(iface); if (ret != null) return ret; ret = new IpConfiguration(); ret.setIpAssignment(IpAssignment.DHCP); ret.setProxySettings(ProxySettings.NONE); return ret; } private boolean isValidEthernetInterface(String iface) { return iface.matches(mIfaceMatch) || isValidTestInterface(iface); } /** * Validate if a given interface is valid for testing. * * @param iface the name of the interface to validate. * @return {@code true} if test interfaces are enabled and the given {@code iface} has a test * interface prefix, {@code false} otherwise. */ public boolean isValidTestInterface(@NonNull final String iface) { return mIncludeTestInterfaces && iface.matches(TEST_IFACE_REGEXP); } private void postAndWaitForRunnable(Runnable r) { final ConditionVariable cv = new ConditionVariable(); if (mHandler.post(() -> { r.run(); cv.open(); })) { cv.block(2000L); } } @VisibleForTesting(visibility = PACKAGE) protected void setEthernetEnabled(boolean enabled) { mHandler.post(() -> { if (mIsEthernetEnabled == enabled) return; mIsEthernetEnabled = enabled; for (String iface : getAllInterfaces()) { setInterfaceUpState(iface, enabled); } broadcastEthernetStateChange(mIsEthernetEnabled); }); } private int isEthernetEnabledAsInt(boolean state) { return state ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED; } private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener, boolean enabled) { ensureRunningOnEthernetServiceThread(); try { listener.onEthernetStateChanged(isEthernetEnabledAsInt(enabled)); } catch (RemoteException e) { // Do nothing here. } } private void broadcastEthernetStateChange(boolean enabled) { ensureRunningOnEthernetServiceThread(); final int n = mListeners.beginBroadcast(); for (int i = 0; i < n; i++) { try { mListeners.getBroadcastItem(i) .onEthernetStateChanged(isEthernetEnabledAsInt(enabled)); } catch (RemoteException e) { // Do nothing here. } } mListeners.finishBroadcast(); } private void setInterfaceUpState(@NonNull String interfaceName, boolean up) { if (!NetlinkUtils.setInterfaceFlags(interfaceName, up ? IFF_UP : ~IFF_UP)) { Log.e(TAG, "Failed to set interface " + interfaceName + (up ? " up" : " down")); } } void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { postAndWaitForRunnable(() -> { pw.println(getClass().getSimpleName()); pw.println("Ethernet State: " + (mIsEthernetEnabled ? "enabled" : "disabled")); pw.println("Ethernet interface name filter: " + mIfaceMatch); pw.println("Interface used for tethering: " + mTetheringInterface); pw.println("Tethering interface mode: " + mTetheringInterfaceMode); pw.println("Tethered interface requests: " + mTetheredInterfaceRequests.getRegisteredCallbackCount()); 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); }); } @VisibleForTesting static class EthernetConfigParser { final String mIface; final NetworkCapabilities mCaps; final String mIpConfig; private static NetworkCapabilities parseCapabilities(@Nullable String capabilitiesString, boolean isAtLeastB) { final NetworkCapabilities.Builder builder = NetworkCapabilities.Builder.withoutDefaultCapabilities(); builder.setLinkUpstreamBandwidthKbps(100 * 1000 /* 100 Mbps */); builder.setLinkDownstreamBandwidthKbps(100 * 1000 /* 100 Mbps */); // Ethernet networks have no way to update the following capabilities, so they always // have them. builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); if (capabilitiesString == null) { return builder.build(); } if (isAtLeastB && capabilitiesString.equals("*")) { // On Android B+, a "*" string defaults to the same set of default // capabilities assigned to unconfigured interfaces. // Note that the transport type is populated later with the result of // parseTransportType(). return new NetworkCapabilities.Builder(DEFAULT_CAPABILITIES) .removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) .build(); } for (String strNetworkCapability : capabilitiesString.split(",")) { if (TextUtils.isEmpty(strNetworkCapability)) { continue; } final Integer capability; try { builder.addCapability(Integer.valueOf(strNetworkCapability)); } catch (NumberFormatException e) { Log.e(TAG, "Failed to parse capability: " + strNetworkCapability, e); continue; } } return builder.build(); } private static int parseTransportType(@Nullable String transportString) { if (TextUtils.isEmpty(transportString)) { return TRANSPORT_ETHERNET; } final int parsedTransport; try { parsedTransport = Integer.valueOf(transportString); } catch (NumberFormatException e) { Log.e(TAG, "Failed to parse transport type", e); return TRANSPORT_ETHERNET; } if (!NetworkCapabilities.isValidTransport(parsedTransport)) { return TRANSPORT_ETHERNET; } switch (parsedTransport) { case TRANSPORT_VPN: case TRANSPORT_WIFI_AWARE: case TRANSPORT_LOWPAN: Log.e(TAG, "Unsupported transport type '" + parsedTransport + "'"); return TRANSPORT_ETHERNET; default: return parsedTransport; } } EthernetConfigParser(String configString, boolean isAtLeastB) { Objects.requireNonNull(configString, "EthernetConfigParser requires non-null config"); final String[] tokens = configString.split(";", /* limit of tokens */ 4); mIface = tokens[0]; final NetworkCapabilities nc = parseCapabilities(tokens.length > 1 ? tokens[1] : null, isAtLeastB); final int transportType = parseTransportType(tokens.length > 3 ? tokens[3] : null); nc.addTransportType(transportType); mCaps = nc; mIpConfig = tokens.length > 2 && !TextUtils.isEmpty(tokens[2]) ? tokens[2] : null; } } }