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