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