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