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