Home | History | Annotate | Download | only in ethernet
      1 /*
      2  * Copyright (C) 2018 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.annotation.Nullable;
     20 import android.content.Context;
     21 import android.net.IEthernetServiceListener;
     22 import android.net.InterfaceConfiguration;
     23 import android.net.IpConfiguration;
     24 import android.net.IpConfiguration.IpAssignment;
     25 import android.net.IpConfiguration.ProxySettings;
     26 import android.net.LinkAddress;
     27 import android.net.NetworkCapabilities;
     28 import android.net.StaticIpConfiguration;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.INetworkManagementService;
     32 import android.os.RemoteCallbackList;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.text.TextUtils;
     36 import android.util.ArrayMap;
     37 import android.util.Log;
     38 
     39 import com.android.internal.annotations.VisibleForTesting;
     40 import com.android.internal.util.IndentingPrintWriter;
     41 import com.android.server.net.BaseNetworkObserver;
     42 
     43 import java.io.FileDescriptor;
     44 import java.net.InetAddress;
     45 import java.util.ArrayList;
     46 import java.util.concurrent.ConcurrentHashMap;
     47 
     48 /**
     49  * Tracks Ethernet interfaces and manages interface configurations.
     50  *
     51  * <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
     52  * in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
     53  * not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
     54  * Interfaces could have associated {@link android.net.IpConfiguration}.
     55  * Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
     56  * connected over USB). This class supports multiple interfaces. When an interface appears on the
     57  * system (or is present at boot time) this class will start tracking it and bring it up. Only
     58  * interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
     59  * tracked.
     60  *
     61  * <p>All public or package private methods must be thread-safe unless stated otherwise.
     62  */
     63 final class EthernetTracker {
     64     private final static String TAG = EthernetTracker.class.getSimpleName();
     65     private final static boolean DBG = EthernetNetworkFactory.DBG;
     66 
     67     /** Product-dependent regular expression of interface names we track. */
     68     private final String mIfaceMatch;
     69 
     70     /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
     71     private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
     72             new ConcurrentHashMap<>();
     73     private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
     74             new ConcurrentHashMap<>();
     75 
     76     private final INetworkManagementService mNMService;
     77     private final Handler mHandler;
     78     private final EthernetNetworkFactory mFactory;
     79     private final EthernetConfigStore mConfigStore;
     80 
     81     private final RemoteCallbackList<IEthernetServiceListener> mListeners =
     82             new RemoteCallbackList<>();
     83 
     84     private volatile IpConfiguration mIpConfigForDefaultInterface;
     85 
     86     EthernetTracker(Context context, Handler handler) {
     87         mHandler = handler;
     88 
     89         // The services we use.
     90         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
     91         mNMService = INetworkManagementService.Stub.asInterface(b);
     92 
     93         // Interface match regex.
     94         mIfaceMatch = context.getResources().getString(
     95                 com.android.internal.R.string.config_ethernet_iface_regex);
     96 
     97         // Read default Ethernet interface configuration from resources
     98         final String[] interfaceConfigs = context.getResources().getStringArray(
     99                 com.android.internal.R.array.config_ethernet_interfaces);
    100         for (String strConfig : interfaceConfigs) {
    101             parseEthernetConfig(strConfig);
    102         }
    103 
    104         mConfigStore = new EthernetConfigStore();
    105 
    106         NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
    107         mFactory = new EthernetNetworkFactory(handler, context, nc);
    108         mFactory.register();
    109     }
    110 
    111     void start() {
    112         mConfigStore.read();
    113 
    114         // Default interface is just the first one we want to track.
    115         mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
    116         final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
    117         for (int i = 0; i < configs.size(); i++) {
    118             mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
    119         }
    120 
    121         try {
    122             mNMService.registerObserver(new InterfaceObserver());
    123         } catch (RemoteException e) {
    124             Log.e(TAG, "Could not register InterfaceObserver " + e);
    125         }
    126 
    127         mHandler.post(this::trackAvailableInterfaces);
    128     }
    129 
    130     void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
    131         if (DBG) {
    132             Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
    133         }
    134 
    135         mConfigStore.write(iface, ipConfiguration);
    136         mIpConfigurations.put(iface, ipConfiguration);
    137 
    138         mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
    139     }
    140 
    141     IpConfiguration getIpConfiguration(String iface) {
    142         return mIpConfigurations.get(iface);
    143     }
    144 
    145     boolean isTrackingInterface(String iface) {
    146         return mFactory.hasInterface(iface);
    147     }
    148 
    149     String[] getInterfaces(boolean includeRestricted) {
    150         return mFactory.getAvailableInterfaces(includeRestricted);
    151     }
    152 
    153     /**
    154      * Returns true if given interface was configured as restricted (doesn't have
    155      * NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
    156      */
    157     boolean isRestrictedInterface(String iface) {
    158         final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
    159         return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
    160     }
    161 
    162     void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
    163         mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
    164     }
    165 
    166     void removeListener(IEthernetServiceListener listener) {
    167         mListeners.unregister(listener);
    168     }
    169 
    170     private void removeInterface(String iface) {
    171         mFactory.removeInterface(iface);
    172     }
    173 
    174     private void addInterface(String iface) {
    175         InterfaceConfiguration config = null;
    176         // Bring up the interface so we get link status indications.
    177         try {
    178             mNMService.setInterfaceUp(iface);
    179             config = mNMService.getInterfaceConfig(iface);
    180         } catch (RemoteException | IllegalStateException e) {
    181             // Either the system is crashing or the interface has disappeared. Just ignore the
    182             // error; we haven't modified any state because we only do that if our calls succeed.
    183             Log.e(TAG, "Error upping interface " + iface, e);
    184         }
    185 
    186         if (config == null) {
    187             Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
    188             return;
    189         }
    190 
    191         final String hwAddress = config.getHardwareAddress();
    192 
    193         NetworkCapabilities nc = mNetworkCapabilities.get(iface);
    194         if (nc == null) {
    195             // Try to resolve using mac address
    196             nc = mNetworkCapabilities.get(hwAddress);
    197             if (nc == null) {
    198                 nc = createDefaultNetworkCapabilities();
    199             }
    200         }
    201         IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
    202         if (ipConfiguration == null) {
    203             ipConfiguration = createDefaultIpConfiguration();
    204         }
    205 
    206         Log.d(TAG, "Started tracking interface " + iface);
    207         mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
    208 
    209         // Note: if the interface already has link (e.g., if we crashed and got
    210         // restarted while it was running), we need to fake a link up notification so we
    211         // start configuring it.
    212         if (config.hasFlag("running")) {
    213             updateInterfaceState(iface, true);
    214         }
    215     }
    216 
    217     private void updateInterfaceState(String iface, boolean up) {
    218         boolean modified = mFactory.updateInterfaceLinkState(iface, up);
    219         if (modified) {
    220             boolean restricted = isRestrictedInterface(iface);
    221             int n = mListeners.beginBroadcast();
    222             for (int i = 0; i < n; i++) {
    223                 try {
    224                     if (restricted) {
    225                         ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i);
    226                         if (!listenerInfo.canUseRestrictedNetworks) {
    227                             continue;
    228                         }
    229                     }
    230                     mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up);
    231                 } catch (RemoteException e) {
    232                     // Do nothing here.
    233                 }
    234             }
    235             mListeners.finishBroadcast();
    236         }
    237     }
    238 
    239     private void maybeTrackInterface(String iface) {
    240         if (DBG) Log.i(TAG, "maybeTrackInterface " + iface);
    241         // If we don't already track this interface, and if this interface matches
    242         // our regex, start tracking it.
    243         if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) {
    244             return;
    245         }
    246 
    247         if (mIpConfigForDefaultInterface != null) {
    248             updateIpConfiguration(iface, mIpConfigForDefaultInterface);
    249             mIpConfigForDefaultInterface = null;
    250         }
    251 
    252         addInterface(iface);
    253     }
    254 
    255     private void trackAvailableInterfaces() {
    256         try {
    257             final String[] ifaces = mNMService.listInterfaces();
    258             for (String iface : ifaces) {
    259                 maybeTrackInterface(iface);
    260             }
    261         } catch (RemoteException | IllegalStateException e) {
    262             Log.e(TAG, "Could not get list of interfaces " + e);
    263         }
    264     }
    265 
    266 
    267     private class InterfaceObserver extends BaseNetworkObserver {
    268 
    269         @Override
    270         public void interfaceLinkStateChanged(String iface, boolean up) {
    271             if (DBG) {
    272                 Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
    273             }
    274             mHandler.post(() -> updateInterfaceState(iface, up));
    275         }
    276 
    277         @Override
    278         public void interfaceAdded(String iface) {
    279             mHandler.post(() -> maybeTrackInterface(iface));
    280         }
    281 
    282         @Override
    283         public void interfaceRemoved(String iface) {
    284             mHandler.post(() -> removeInterface(iface));
    285         }
    286     }
    287 
    288     private static class ListenerInfo {
    289 
    290         boolean canUseRestrictedNetworks = false;
    291 
    292         ListenerInfo(boolean canUseRestrictedNetworks) {
    293             this.canUseRestrictedNetworks = canUseRestrictedNetworks;
    294         }
    295     }
    296 
    297     private void parseEthernetConfig(String configString) {
    298         String[] tokens = configString.split(";");
    299         String name = tokens[0];
    300         String capabilities = tokens.length > 1 ? tokens[1] : null;
    301         NetworkCapabilities nc = createNetworkCapabilities(
    302                 !TextUtils.isEmpty(capabilities)  /* clear default capabilities */, capabilities);
    303         mNetworkCapabilities.put(name, nc);
    304 
    305         if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
    306             IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]);
    307             mIpConfigurations.put(name, ipConfig);
    308         }
    309     }
    310 
    311     private static NetworkCapabilities createDefaultNetworkCapabilities() {
    312         NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
    313         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    314         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
    315         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
    316         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
    317         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
    318 
    319         return nc;
    320     }
    321 
    322     private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
    323         return createNetworkCapabilities(clearDefaultCapabilities, null);
    324     }
    325 
    326     private static NetworkCapabilities createNetworkCapabilities(
    327             boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) {
    328 
    329         NetworkCapabilities nc = new NetworkCapabilities();
    330         if (clearDefaultCapabilities) {
    331             nc.clearAll();  // Remove default capabilities.
    332         }
    333         nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
    334         nc.setLinkUpstreamBandwidthKbps(100 * 1000);
    335         nc.setLinkDownstreamBandwidthKbps(100 * 1000);
    336 
    337         if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
    338             for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
    339                 if (!TextUtils.isEmpty(strNetworkCapability)) {
    340                     nc.addCapability(Integer.valueOf(strNetworkCapability));
    341                 }
    342             }
    343         }
    344 
    345         return nc;
    346     }
    347 
    348     /**
    349      * Parses static IP configuration.
    350      *
    351      * @param staticIpConfig represents static IP configuration in the following format: {@code
    352      * ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
    353      *     domains=<comma-sep-domains>}
    354      */
    355     @VisibleForTesting
    356     static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
    357         StaticIpConfiguration ipConfig = new StaticIpConfiguration();
    358 
    359         for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
    360             if (TextUtils.isEmpty(keyValueAsString)) continue;
    361 
    362             String[] pair = keyValueAsString.split("=");
    363             if (pair.length != 2) {
    364                 throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
    365                         + " in " + staticIpConfig);
    366             }
    367 
    368             String key = pair[0];
    369             String value = pair[1];
    370 
    371             switch (key) {
    372                 case "ip":
    373                     ipConfig.ipAddress = new LinkAddress(value);
    374                     break;
    375                 case "domains":
    376                     ipConfig.domains = value;
    377                     break;
    378                 case "gateway":
    379                     ipConfig.gateway = InetAddress.parseNumericAddress(value);
    380                     break;
    381                 case "dns": {
    382                     ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
    383                     for (String address: value.split(",")) {
    384                         dnsAddresses.add(InetAddress.parseNumericAddress(address));
    385                     }
    386                     ipConfig.dnsServers.addAll(dnsAddresses);
    387                     break;
    388                 }
    389                 default : {
    390                     throw new IllegalArgumentException("Unexpected key: " + key
    391                             + " in " + staticIpConfig);
    392                 }
    393             }
    394         }
    395         return new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, ipConfig, null);
    396     }
    397 
    398     private static IpConfiguration createDefaultIpConfiguration() {
    399         return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
    400     }
    401 
    402     private void postAndWaitForRunnable(Runnable r) {
    403         mHandler.runWithScissors(r, 2000L /* timeout */);
    404     }
    405 
    406     void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
    407         postAndWaitForRunnable(() -> {
    408             pw.println(getClass().getSimpleName());
    409             pw.println("Ethernet interface name filter: " + mIfaceMatch);
    410             pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
    411             pw.println("IP Configurations:");
    412             pw.increaseIndent();
    413             for (String iface : mIpConfigurations.keySet()) {
    414                 pw.println(iface + ": " + mIpConfigurations.get(iface));
    415             }
    416             pw.decreaseIndent();
    417             pw.println();
    418 
    419             pw.println("Network Capabilities:");
    420             pw.increaseIndent();
    421             for (String iface : mNetworkCapabilities.keySet()) {
    422                 pw.println(iface + ": " + mNetworkCapabilities.get(iface));
    423             }
    424             pw.decreaseIndent();
    425             pw.println();
    426 
    427             mFactory.dump(fd, pw, args);
    428         });
    429     }
    430 }
    431