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 
EthernetTracker(Context context, Handler handler)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 
start()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 
updateIpConfiguration(String iface, IpConfiguration ipConfiguration)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 
getIpConfiguration(String iface)141     IpConfiguration getIpConfiguration(String iface) {
142         return mIpConfigurations.get(iface);
143     }
144 
isTrackingInterface(String iface)145     boolean isTrackingInterface(String iface) {
146         return mFactory.hasInterface(iface);
147     }
148 
getInterfaces(boolean includeRestricted)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      */
isRestrictedInterface(String iface)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 
addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks)162     void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
163         mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
164     }
165 
removeListener(IEthernetServiceListener listener)166     void removeListener(IEthernetServiceListener listener) {
167         mListeners.unregister(listener);
168     }
169 
removeInterface(String iface)170     private void removeInterface(String iface) {
171         mFactory.removeInterface(iface);
172     }
173 
addInterface(String iface)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 
updateInterfaceState(String iface, boolean up)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 
maybeTrackInterface(String iface)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 
trackAvailableInterfaces()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
interfaceLinkStateChanged(String iface, boolean up)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
interfaceAdded(String iface)278         public void interfaceAdded(String iface) {
279             mHandler.post(() -> maybeTrackInterface(iface));
280         }
281 
282         @Override
interfaceRemoved(String iface)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 
ListenerInfo(boolean canUseRestrictedNetworks)292         ListenerInfo(boolean canUseRestrictedNetworks) {
293             this.canUseRestrictedNetworks = canUseRestrictedNetworks;
294         }
295     }
296 
297     /**
298      * Parses an Ethernet interface configuration
299      *
300      * @param configString represents an Ethernet configuration in the following format: {@code
301      * <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]}
302      */
parseEthernetConfig(String configString)303     private void parseEthernetConfig(String configString) {
304         String[] tokens = configString.split(";", /* limit of tokens */ 4);
305         String name = tokens[0];
306         String capabilities = tokens.length > 1 ? tokens[1] : null;
307         String transport = tokens.length > 3 ? tokens[3] : null;
308         NetworkCapabilities nc = createNetworkCapabilities(
309                 !TextUtils.isEmpty(capabilities)  /* clear default capabilities */, capabilities,
310                 transport);
311         mNetworkCapabilities.put(name, nc);
312 
313         if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
314             IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]);
315             mIpConfigurations.put(name, ipConfig);
316         }
317     }
318 
createDefaultNetworkCapabilities()319     private static NetworkCapabilities createDefaultNetworkCapabilities() {
320         NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
321         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
322         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
323         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
324         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
325         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
326 
327         return nc;
328     }
329 
createNetworkCapabilities(boolean clearDefaultCapabilities)330     private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
331         return createNetworkCapabilities(clearDefaultCapabilities, null, null);
332     }
333 
334     /**
335      * Parses a static list of network capabilities
336      *
337      * @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities
338      * @param commaSeparatedCapabilities A comma separated string list of integer encoded
339      *                                   NetworkCapability.NET_CAPABILITY_* values
340      * @param overrideTransport A string representing a single integer encoded override transport
341      *                          type. Must be one of the NetworkCapability.TRANSPORT_*
342      *                          values. TRANSPORT_VPN is not supported. Errors with input
343      *                          will cause the override to be ignored.
344      */
345     @VisibleForTesting
createNetworkCapabilities( boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities, @Nullable String overrideTransport)346     static NetworkCapabilities createNetworkCapabilities(
347             boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities,
348             @Nullable String overrideTransport) {
349 
350         NetworkCapabilities nc = new NetworkCapabilities();
351         if (clearDefaultCapabilities) {
352             nc.clearAll();  // Remove default capabilities and transports
353         }
354 
355         // Determine the transport type. If someone has tried to define an override transport then
356         // attempt to add it. Since we can only have one override, all errors with it will
357         // gracefully default back to TRANSPORT_ETHERNET and warn the user. VPN is not allowed as an
358         // override type. Wifi Aware and LoWPAN are currently unsupported as well.
359         int transport = NetworkCapabilities.TRANSPORT_ETHERNET;
360         if (!TextUtils.isEmpty(overrideTransport)) {
361             try {
362                 int parsedTransport = Integer.valueOf(overrideTransport);
363                 if (parsedTransport == NetworkCapabilities.TRANSPORT_VPN
364                         || parsedTransport == NetworkCapabilities.TRANSPORT_WIFI_AWARE
365                         || parsedTransport == NetworkCapabilities.TRANSPORT_LOWPAN) {
366                     Log.e(TAG, "Override transport '" + parsedTransport + "' is not supported. "
367                             + "Defaulting to TRANSPORT_ETHERNET");
368                 } else {
369                     transport = parsedTransport;
370                 }
371             } catch (NumberFormatException nfe) {
372                 Log.e(TAG, "Override transport type '" + overrideTransport + "' "
373                         + "could not be parsed. Defaulting to TRANSPORT_ETHERNET");
374             }
375         }
376 
377         // Apply the transport. If the user supplied a valid number that is not a valid transport
378         // then adding will throw an exception. Default back to TRANSPORT_ETHERNET if that happens
379         try {
380             nc.addTransportType(transport);
381         } catch (IllegalArgumentException iae) {
382             Log.e(TAG, transport + " is not a valid NetworkCapability.TRANSPORT_* value. "
383                     + "Defaulting to TRANSPORT_ETHERNET");
384             nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
385         }
386 
387         nc.setLinkUpstreamBandwidthKbps(100 * 1000);
388         nc.setLinkDownstreamBandwidthKbps(100 * 1000);
389 
390         if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
391             for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
392                 if (!TextUtils.isEmpty(strNetworkCapability)) {
393                     try {
394                         nc.addCapability(Integer.valueOf(strNetworkCapability));
395                     } catch (NumberFormatException nfe) {
396                         Log.e(TAG, "Capability '" + strNetworkCapability + "' could not be parsed");
397                     } catch (IllegalArgumentException iae) {
398                         Log.e(TAG, strNetworkCapability + " is not a valid "
399                                 + "NetworkCapability.NET_CAPABILITY_* value");
400                     }
401                 }
402             }
403         }
404 
405         return nc;
406     }
407 
408     /**
409      * Parses static IP configuration.
410      *
411      * @param staticIpConfig represents static IP configuration in the following format: {@code
412      * ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
413      *     domains=<comma-sep-domains>}
414      */
415     @VisibleForTesting
parseStaticIpConfiguration(String staticIpConfig)416     static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
417         StaticIpConfiguration ipConfig = new StaticIpConfiguration();
418 
419         for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
420             if (TextUtils.isEmpty(keyValueAsString)) continue;
421 
422             String[] pair = keyValueAsString.split("=");
423             if (pair.length != 2) {
424                 throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
425                         + " in " + staticIpConfig);
426             }
427 
428             String key = pair[0];
429             String value = pair[1];
430 
431             switch (key) {
432                 case "ip":
433                     ipConfig.ipAddress = new LinkAddress(value);
434                     break;
435                 case "domains":
436                     ipConfig.domains = value;
437                     break;
438                 case "gateway":
439                     ipConfig.gateway = InetAddress.parseNumericAddress(value);
440                     break;
441                 case "dns": {
442                     ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
443                     for (String address: value.split(",")) {
444                         dnsAddresses.add(InetAddress.parseNumericAddress(address));
445                     }
446                     ipConfig.dnsServers.addAll(dnsAddresses);
447                     break;
448                 }
449                 default : {
450                     throw new IllegalArgumentException("Unexpected key: " + key
451                             + " in " + staticIpConfig);
452                 }
453             }
454         }
455         return new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, ipConfig, null);
456     }
457 
createDefaultIpConfiguration()458     private static IpConfiguration createDefaultIpConfiguration() {
459         return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
460     }
461 
postAndWaitForRunnable(Runnable r)462     private void postAndWaitForRunnable(Runnable r) {
463         mHandler.runWithScissors(r, 2000L /* timeout */);
464     }
465 
dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args)466     void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
467         postAndWaitForRunnable(() -> {
468             pw.println(getClass().getSimpleName());
469             pw.println("Ethernet interface name filter: " + mIfaceMatch);
470             pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
471             pw.println("IP Configurations:");
472             pw.increaseIndent();
473             for (String iface : mIpConfigurations.keySet()) {
474                 pw.println(iface + ": " + mIpConfigurations.get(iface));
475             }
476             pw.decreaseIndent();
477             pw.println();
478 
479             pw.println("Network Capabilities:");
480             pw.increaseIndent();
481             for (String iface : mNetworkCapabilities.keySet()) {
482                 pw.println(iface + ": " + mNetworkCapabilities.get(iface));
483             }
484             pw.decreaseIndent();
485             pw.println();
486 
487             mFactory.dump(fd, pw, args);
488         });
489     }
490 }
491