Home | History | Annotate | Download | only in ethernet
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.ethernet;
     18 
     19 import android.content.Context;
     20 import android.net.ConnectivityManager;
     21 import android.net.DhcpResults;
     22 import android.net.EthernetManager;
     23 import android.net.IEthernetServiceListener;
     24 import android.net.InterfaceConfiguration;
     25 import android.net.IpConfiguration;
     26 import android.net.IpConfiguration.IpAssignment;
     27 import android.net.IpConfiguration.ProxySettings;
     28 import android.net.LinkProperties;
     29 import android.net.NetworkAgent;
     30 import android.net.NetworkCapabilities;
     31 import android.net.NetworkFactory;
     32 import android.net.NetworkInfo;
     33 import android.net.NetworkInfo.DetailedState;
     34 import android.net.NetworkUtils;
     35 import android.net.StaticIpConfiguration;
     36 import android.os.Handler;
     37 import android.os.IBinder;
     38 import android.os.INetworkManagementService;
     39 import android.os.Looper;
     40 import android.os.RemoteCallbackList;
     41 import android.os.RemoteException;
     42 import android.os.ServiceManager;
     43 import android.text.TextUtils;
     44 import android.util.Log;
     45 
     46 import com.android.internal.util.IndentingPrintWriter;
     47 import com.android.server.net.BaseNetworkObserver;
     48 
     49 import java.io.FileDescriptor;
     50 import java.io.PrintWriter;
     51 
     52 
     53 /**
     54  * Manages connectivity for an Ethernet interface.
     55  *
     56  * Ethernet Interfaces may be present at boot time or appear after boot (e.g.,
     57  * for Ethernet adapters connected over USB). This class currently supports
     58  * only one interface. When an interface appears on the system (or is present
     59  * at boot time) this class will start tracking it and bring it up, and will
     60  * attempt to connect when requested. Any other interfaces that subsequently
     61  * appear will be ignored until the tracked interface disappears. Only
     62  * interfaces whose names match the <code>config_ethernet_iface_regex</code>
     63  * regular expression are tracked.
     64  *
     65  * This class reports a static network score of 70 when it is tracking an
     66  * interface and that interface's link is up, and a score of 0 otherwise.
     67  *
     68  * @hide
     69  */
     70 class EthernetNetworkFactory {
     71     private static final String NETWORK_TYPE = "Ethernet";
     72     private static final String TAG = "EthernetNetworkFactory";
     73     private static final int NETWORK_SCORE = 70;
     74     private static final boolean DBG = true;
     75 
     76     /** Tracks interface changes. Called from NetworkManagementService. */
     77     private InterfaceObserver mInterfaceObserver;
     78 
     79     /** For static IP configuration */
     80     private EthernetManager mEthernetManager;
     81 
     82     /** To set link state and configure IP addresses. */
     83     private INetworkManagementService mNMService;
     84 
     85     /* To communicate with ConnectivityManager */
     86     private NetworkCapabilities mNetworkCapabilities;
     87     private NetworkAgent mNetworkAgent;
     88     private LocalNetworkFactory mFactory;
     89     private Context mContext;
     90 
     91     /** Product-dependent regular expression of interface names we track. */
     92     private static String mIfaceMatch = "";
     93 
     94     /** To notify Ethernet status. */
     95     private final RemoteCallbackList<IEthernetServiceListener> mListeners;
     96 
     97     /** Data members. All accesses to these must be synchronized(this). */
     98     private static String mIface = "";
     99     private String mHwAddr;
    100     private static boolean mLinkUp;
    101     private NetworkInfo mNetworkInfo;
    102     private LinkProperties mLinkProperties;
    103 
    104     EthernetNetworkFactory(RemoteCallbackList<IEthernetServiceListener> listeners) {
    105         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
    106         mLinkProperties = new LinkProperties();
    107         initNetworkCapabilities();
    108         mListeners = listeners;
    109     }
    110 
    111     private class LocalNetworkFactory extends NetworkFactory {
    112         LocalNetworkFactory(String name, Context context, Looper looper) {
    113             super(looper, context, name, new NetworkCapabilities());
    114         }
    115 
    116         protected void startNetwork() {
    117             onRequestNetwork();
    118         }
    119         protected void stopNetwork() {
    120         }
    121     }
    122 
    123 
    124     /**
    125      * Updates interface state variables.
    126      * Called on link state changes or on startup.
    127      */
    128     private void updateInterfaceState(String iface, boolean up) {
    129         if (!mIface.equals(iface)) {
    130             return;
    131         }
    132         Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
    133 
    134         synchronized(this) {
    135             mLinkUp = up;
    136             mNetworkInfo.setIsAvailable(up);
    137             if (!up) {
    138                 // Tell the agent we're disconnected. It will call disconnect().
    139                 mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
    140             }
    141             updateAgent();
    142             // set our score lower than any network could go
    143             // so we get dropped.  TODO - just unregister the factory
    144             // when link goes down.
    145             mFactory.setScoreFilter(up ? NETWORK_SCORE : -1);
    146         }
    147     }
    148 
    149     private class InterfaceObserver extends BaseNetworkObserver {
    150         @Override
    151         public void interfaceLinkStateChanged(String iface, boolean up) {
    152             updateInterfaceState(iface, up);
    153         }
    154 
    155         @Override
    156         public void interfaceAdded(String iface) {
    157             maybeTrackInterface(iface);
    158         }
    159 
    160         @Override
    161         public void interfaceRemoved(String iface) {
    162             stopTrackingInterface(iface);
    163         }
    164     }
    165 
    166     private void setInterfaceUp(String iface) {
    167         // Bring up the interface so we get link status indications.
    168         try {
    169             mNMService.setInterfaceUp(iface);
    170             String hwAddr = null;
    171             InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
    172 
    173             if (config == null) {
    174                 Log.e(TAG, "Null iterface config for " + iface + ". Bailing out.");
    175                 return;
    176             }
    177 
    178             synchronized (this) {
    179                 if (!isTrackingInterface()) {
    180                     setInterfaceInfoLocked(iface, config.getHardwareAddress());
    181                     mNetworkInfo.setIsAvailable(true);
    182                     mNetworkInfo.setExtraInfo(mHwAddr);
    183                 } else {
    184                     Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
    185                     mNMService.setInterfaceDown(iface);
    186                 }
    187             }
    188         } catch (RemoteException e) {
    189             Log.e(TAG, "Error upping interface " + mIface + ": " + e);
    190         }
    191     }
    192 
    193     private boolean maybeTrackInterface(String iface) {
    194         // If we don't already have an interface, and if this interface matches
    195         // our regex, start tracking it.
    196         if (!iface.matches(mIfaceMatch) || isTrackingInterface())
    197             return false;
    198 
    199         Log.d(TAG, "Started tracking interface " + iface);
    200         setInterfaceUp(iface);
    201         return true;
    202     }
    203 
    204     private void stopTrackingInterface(String iface) {
    205         if (!iface.equals(mIface))
    206             return;
    207 
    208         Log.d(TAG, "Stopped tracking interface " + iface);
    209         // TODO: Unify this codepath with stop().
    210         synchronized (this) {
    211             NetworkUtils.stopDhcp(mIface);
    212             setInterfaceInfoLocked("", null);
    213             mNetworkInfo.setExtraInfo(null);
    214             mLinkUp = false;
    215             mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
    216             updateAgent();
    217             mNetworkAgent = null;
    218             mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
    219             mLinkProperties = new LinkProperties();
    220         }
    221     }
    222 
    223     private boolean setStaticIpAddress(StaticIpConfiguration staticConfig) {
    224         if (staticConfig.ipAddress != null &&
    225                 staticConfig.gateway != null &&
    226                 staticConfig.dnsServers.size() > 0) {
    227             try {
    228                 Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + staticConfig);
    229                 InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
    230                 config.setLinkAddress(staticConfig.ipAddress);
    231                 mNMService.setInterfaceConfig(mIface, config);
    232                 return true;
    233             } catch(RemoteException|IllegalStateException e) {
    234                Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
    235             }
    236         } else {
    237             Log.e(TAG, "Invalid static IP configuration.");
    238         }
    239         return false;
    240     }
    241 
    242     public void updateAgent() {
    243         synchronized (EthernetNetworkFactory.this) {
    244             if (mNetworkAgent == null) return;
    245             if (DBG) {
    246                 Log.i(TAG, "Updating mNetworkAgent with: " +
    247                       mNetworkCapabilities + ", " +
    248                       mNetworkInfo + ", " +
    249                       mLinkProperties);
    250             }
    251             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
    252             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
    253             mNetworkAgent.sendLinkProperties(mLinkProperties);
    254             // never set the network score below 0.
    255             mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
    256         }
    257     }
    258 
    259     /* Called by the NetworkFactory on the handler thread. */
    260     public void onRequestNetwork() {
    261         // TODO: Handle DHCP renew.
    262         Thread dhcpThread = new Thread(new Runnable() {
    263             public void run() {
    264                 if (DBG) Log.i(TAG, "dhcpThread(+" + mIface + "): mNetworkInfo=" + mNetworkInfo);
    265                 LinkProperties linkProperties;
    266 
    267                 IpConfiguration config = mEthernetManager.getConfiguration();
    268 
    269                 if (config.getIpAssignment() == IpAssignment.STATIC) {
    270                     if (!setStaticIpAddress(config.getStaticIpConfiguration())) {
    271                         // We've already logged an error.
    272                         return;
    273                     }
    274                     linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface);
    275                 } else {
    276                     mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
    277 
    278                     DhcpResults dhcpResults = new DhcpResults();
    279                     // TODO: Handle DHCP renewals better.
    280                     // In general runDhcp handles DHCP renewals for us, because
    281                     // the dhcp client stays running, but if the renewal fails,
    282                     // we will lose our IP address and connectivity without
    283                     // noticing.
    284                     if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
    285                         Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
    286                         // set our score lower than any network could go
    287                         // so we get dropped.
    288                         mFactory.setScoreFilter(-1);
    289                         return;
    290                     }
    291                     linkProperties = dhcpResults.toLinkProperties(mIface);
    292                 }
    293                 if (config.getProxySettings() == ProxySettings.STATIC ||
    294                         config.getProxySettings() == ProxySettings.PAC) {
    295                     linkProperties.setHttpProxy(config.getHttpProxy());
    296                 }
    297 
    298                 String tcpBufferSizes = mContext.getResources().getString(
    299                         com.android.internal.R.string.config_ethernet_tcp_buffers);
    300                 if (TextUtils.isEmpty(tcpBufferSizes) == false) {
    301                     linkProperties.setTcpBufferSizes(tcpBufferSizes);
    302                 }
    303 
    304                 synchronized(EthernetNetworkFactory.this) {
    305                     if (mNetworkAgent != null) {
    306                         Log.e(TAG, "Already have a NetworkAgent - aborting new request");
    307                         return;
    308                     }
    309                     mLinkProperties = linkProperties;
    310                     mNetworkInfo.setIsAvailable(true);
    311                     mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
    312 
    313                     // Create our NetworkAgent.
    314                     mNetworkAgent = new NetworkAgent(mFactory.getLooper(), mContext,
    315                             NETWORK_TYPE, mNetworkInfo, mNetworkCapabilities, mLinkProperties,
    316                             NETWORK_SCORE) {
    317                         public void unwanted() {
    318                             synchronized(EthernetNetworkFactory.this) {
    319                                 if (this == mNetworkAgent) {
    320                                     NetworkUtils.stopDhcp(mIface);
    321 
    322                                     mLinkProperties.clear();
    323                                     mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null,
    324                                             mHwAddr);
    325                                     updateAgent();
    326                                     mNetworkAgent = null;
    327                                     try {
    328                                         mNMService.clearInterfaceAddresses(mIface);
    329                                     } catch (Exception e) {
    330                                         Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
    331                                     }
    332                                 } else {
    333                                     Log.d(TAG, "Ignoring unwanted as we have a more modern " +
    334                                             "instance");
    335                                 }
    336                             }
    337                         };
    338                     };
    339                 }
    340             }
    341         });
    342         dhcpThread.start();
    343     }
    344 
    345     /**
    346      * Begin monitoring connectivity
    347      */
    348     public synchronized void start(Context context, Handler target) {
    349         // The services we use.
    350         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
    351         mNMService = INetworkManagementService.Stub.asInterface(b);
    352         mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
    353 
    354         // Interface match regex.
    355         mIfaceMatch = context.getResources().getString(
    356                 com.android.internal.R.string.config_ethernet_iface_regex);
    357 
    358         // Create and register our NetworkFactory.
    359         mFactory = new LocalNetworkFactory(NETWORK_TYPE, context, target.getLooper());
    360         mFactory.setCapabilityFilter(mNetworkCapabilities);
    361         mFactory.setScoreFilter(-1); // this set high when we have an iface
    362         mFactory.register();
    363 
    364         mContext = context;
    365 
    366         // Start tracking interface change events.
    367         mInterfaceObserver = new InterfaceObserver();
    368         try {
    369             mNMService.registerObserver(mInterfaceObserver);
    370         } catch (RemoteException e) {
    371             Log.e(TAG, "Could not register InterfaceObserver " + e);
    372         }
    373 
    374         // If an Ethernet interface is already connected, start tracking that.
    375         // Otherwise, the first Ethernet interface to appear will be tracked.
    376         try {
    377             final String[] ifaces = mNMService.listInterfaces();
    378             for (String iface : ifaces) {
    379                 synchronized(this) {
    380                     if (maybeTrackInterface(iface)) {
    381                         // We have our interface. Track it.
    382                         // Note: if the interface already has link (e.g., if we
    383                         // crashed and got restarted while it was running),
    384                         // we need to fake a link up notification so we start
    385                         // configuring it. Since we're already holding the lock,
    386                         // any real link up/down notification will only arrive
    387                         // after we've done this.
    388                         if (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
    389                             updateInterfaceState(iface, true);
    390                         }
    391                         break;
    392                     }
    393                 }
    394             }
    395         } catch (RemoteException e) {
    396             Log.e(TAG, "Could not get list of interfaces " + e);
    397         }
    398     }
    399 
    400     public synchronized void stop() {
    401         NetworkUtils.stopDhcp(mIface);
    402         // ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
    403         // with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
    404         // that sets the state to IDLE, and ConnectivityService will still think we're connected.
    405         //
    406         // TODO: stop using explicit comparisons to DISCONNECTED / SUSPENDED in ConnectivityService,
    407         // and instead use isConnectedOrConnecting().
    408         mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
    409         mLinkUp = false;
    410         updateAgent();
    411         mLinkProperties = new LinkProperties();
    412         mNetworkAgent = null;
    413         setInterfaceInfoLocked("", null);
    414         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
    415         mFactory.unregister();
    416     }
    417 
    418     private void initNetworkCapabilities() {
    419         mNetworkCapabilities = new NetworkCapabilities();
    420         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
    421         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    422         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
    423         // We have no useful data on bandwidth. Say 100M up and 100M down. :-(
    424         mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
    425         mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000);
    426     }
    427 
    428     public synchronized boolean isTrackingInterface() {
    429         return !TextUtils.isEmpty(mIface);
    430     }
    431 
    432     /**
    433      * Set interface information and notify listeners if availability is changed.
    434      * This should be called with the lock held.
    435      */
    436     private void setInterfaceInfoLocked(String iface, String hwAddr) {
    437         boolean oldAvailable = isTrackingInterface();
    438         mIface = iface;
    439         mHwAddr = hwAddr;
    440         boolean available = isTrackingInterface();
    441 
    442         if (oldAvailable != available) {
    443             int n = mListeners.beginBroadcast();
    444             for (int i = 0; i < n; i++) {
    445                 try {
    446                     mListeners.getBroadcastItem(i).onAvailabilityChanged(available);
    447                 } catch (RemoteException e) {
    448                     // Do nothing here.
    449                 }
    450             }
    451             mListeners.finishBroadcast();
    452         }
    453     }
    454 
    455     synchronized void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
    456         if (isTrackingInterface()) {
    457             pw.println("Tracking interface: " + mIface);
    458             pw.increaseIndent();
    459             pw.println("MAC address: " + mHwAddr);
    460             pw.println("Link state: " + (mLinkUp ? "up" : "down"));
    461             pw.decreaseIndent();
    462         } else {
    463             pw.println("Not tracking any interface");
    464         }
    465 
    466         pw.println();
    467         pw.println("NetworkInfo: " + mNetworkInfo);
    468         pw.println("LinkProperties: " + mLinkProperties);
    469         pw.println("NetworkAgent: " + mNetworkAgent);
    470     }
    471 }
    472