/* * Copyright (C) 2014 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.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; import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.StaticIpConfiguration; 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; /** * Manages connectivity for an Ethernet interface. * * 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 */ class EthernetNetworkFactory { 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; /** For static IP configuration */ private EthernetManager mEthernetManager; /** To set link state and configure IP addresses. */ private INetworkManagementService mNMService; /* 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 synchronized(this). */ private static String mIface = ""; private String mHwAddr; private static boolean mLinkUp; private NetworkInfo mNetworkInfo; private LinkProperties mLinkProperties; EthernetNetworkFactory(RemoteCallbackList<IEthernetServiceListener> listeners) { mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, ""); mLinkProperties = new LinkProperties(); initNetworkCapabilities(); mListeners = listeners; } private class LocalNetworkFactory extends NetworkFactory { LocalNetworkFactory(String name, Context context, Looper looper) { super(looper, context, name, new NetworkCapabilities()); } protected void startNetwork() { onRequestNetwork(); } protected void stopNetwork() { } } /** * Updates interface state variables. * Called on link state changes or on startup. */ private void updateInterfaceState(String iface, boolean up) { if (!mIface.equals(iface)) { return; } Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down")); synchronized(this) { mLinkUp = up; mNetworkInfo.setIsAvailable(up); if (!up) { // Tell the agent we're disconnected. It will call disconnect(). mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr); } updateAgent(); // set our score lower than any network could go // so we get dropped. TODO - just unregister the factory // when link goes down. mFactory.setScoreFilter(up ? NETWORK_SCORE : -1); } } private class InterfaceObserver extends BaseNetworkObserver { @Override public void interfaceLinkStateChanged(String iface, boolean up) { updateInterfaceState(iface, up); } @Override public void interfaceAdded(String iface) { maybeTrackInterface(iface); } @Override public void interfaceRemoved(String iface) { stopTrackingInterface(iface); } } private void setInterfaceUp(String iface) { // Bring up the interface so we get link status indications. try { NetworkUtils.stopDhcp(iface); mNMService.setInterfaceUp(iface); String hwAddr = null; InterfaceConfiguration config = mNMService.getInterfaceConfig(iface); if (config == null) { Log.e(TAG, "Null iterface config for " + iface + ". Bailing out."); return; } synchronized (this) { if (!isTrackingInterface()) { setInterfaceInfoLocked(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 e) { 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 void stopTrackingInterface(String iface) { if (!iface.equals(mIface)) return; Log.d(TAG, "Stopped tracking interface " + iface); // TODO: Unify this codepath with stop(). synchronized (this) { NetworkUtils.stopDhcp(mIface); setInterfaceInfoLocked("", null); mNetworkInfo.setExtraInfo(null); mLinkUp = false; mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr); updateAgent(); mNetworkAgent = null; mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, ""); mLinkProperties = new LinkProperties(); } } private boolean setStaticIpAddress(StaticIpConfiguration staticConfig) { if (staticConfig.ipAddress != null && staticConfig.gateway != null && staticConfig.dnsServers.size() > 0) { try { Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + staticConfig); InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface); config.setLinkAddress(staticConfig.ipAddress); mNMService.setInterfaceConfig(mIface, config); return true; } catch(RemoteException|IllegalStateException e) { Log.e(TAG, "Setting static IP address failed: " + e.getMessage()); } } else { Log.e(TAG, "Invalid static IP configuration."); } return false; } public void updateAgent() { synchronized (EthernetNetworkFactory.this) { 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); } } /* Called by the NetworkFactory on the handler thread. */ public void onRequestNetwork() { // TODO: Handle DHCP renew. Thread dhcpThread = new Thread(new Runnable() { public void run() { if (DBG) Log.i(TAG, "dhcpThread(" + mIface + "): mNetworkInfo=" + mNetworkInfo); LinkProperties linkProperties; IpConfiguration config = mEthernetManager.getConfiguration(); if (config.getIpAssignment() == IpAssignment.STATIC) { if (!setStaticIpAddress(config.getStaticIpConfiguration())) { // We've already logged an error. return; } linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface); } else { mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr); DhcpResults dhcpResults = new DhcpResults(); // TODO: Handle DHCP renewals better. // In general runDhcp handles DHCP renewals for us, because // the dhcp client stays running, but if the renewal fails, // we will lose our IP address and connectivity without // noticing. if (!NetworkUtils.runDhcp(mIface, dhcpResults)) { Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError()); // set our score lower than any network could go // so we get dropped. mFactory.setScoreFilter(-1); // If DHCP timed out (as opposed to failing), the DHCP client will still be // running, because in M we changed its timeout to infinite. Stop it now. NetworkUtils.stopDhcp(mIface); return; } linkProperties = dhcpResults.toLinkProperties(mIface); } if (config.getProxySettings() == ProxySettings.STATIC || config.getProxySettings() == ProxySettings.PAC) { linkProperties.setHttpProxy(config.getHttpProxy()); } String tcpBufferSizes = mContext.getResources().getString( com.android.internal.R.string.config_ethernet_tcp_buffers); if (TextUtils.isEmpty(tcpBufferSizes) == false) { linkProperties.setTcpBufferSizes(tcpBufferSizes); } synchronized(EthernetNetworkFactory.this) { if (mNetworkAgent != null) { Log.e(TAG, "Already have a NetworkAgent - aborting new request"); return; } mLinkProperties = linkProperties; mNetworkInfo.setIsAvailable(true); mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr); // Create our NetworkAgent. mNetworkAgent = new NetworkAgent(mFactory.getLooper(), mContext, NETWORK_TYPE, mNetworkInfo, mNetworkCapabilities, mLinkProperties, NETWORK_SCORE) { public void unwanted() { synchronized(EthernetNetworkFactory.this) { if (this == mNetworkAgent) { NetworkUtils.stopDhcp(mIface); mLinkProperties.clear(); mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr); updateAgent(); mNetworkAgent = null; try { mNMService.clearInterfaceAddresses(mIface); } catch (Exception e) { Log.e(TAG, "Failed to clear addresses or disable ipv6" + e); } } else { Log.d(TAG, "Ignoring unwanted as we have a more modern " + "instance"); } } }; }; } } }); dhcpThread.start(); } /** * Begin monitoring connectivity */ public synchronized void start(Context context, Handler target) { // 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, target.getLooper()); mFactory.setCapabilityFilter(mNetworkCapabilities); mFactory.setScoreFilter(-1); // this set high when we have an iface 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); } // If an Ethernet interface is already connected, start tracking that. // Otherwise, the first Ethernet interface to appear will be tracked. try { final String[] ifaces = mNMService.listInterfaces(); for (String iface : ifaces) { synchronized(this) { 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. Since we're already holding the lock, // any real link up/down notification will only arrive // after we've done this. if (mNMService.getInterfaceConfig(iface).hasFlag("running")) { updateInterfaceState(iface, true); } break; } } } } catch (RemoteException e) { Log.e(TAG, "Could not get list of interfaces " + e); } } public synchronized void stop() { NetworkUtils.stopDhcp(mIface); // 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. // // TODO: stop using explicit comparisons to DISCONNECTED / SUSPENDED in ConnectivityService, // and instead use isConnectedOrConnecting(). mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr); mLinkUp = false; updateAgent(); mLinkProperties = new LinkProperties(); mNetworkAgent = null; setInterfaceInfoLocked("", null); mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, ""); 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_RESTRICTED); // We have no useful data on bandwidth. Say 100M up and 100M down. :-( mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000); mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000); } public synchronized boolean isTrackingInterface() { return !TextUtils.isEmpty(mIface); } /** * Set interface information and notify listeners if availability is changed. * This should be called with the lock held. */ private void setInterfaceInfoLocked(String iface, String hwAddr) { boolean oldAvailable = isTrackingInterface(); mIface = iface; mHwAddr = hwAddr; boolean available = isTrackingInterface(); 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(); } } synchronized void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { 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); } }