1 /* 2 * Copyright (C) 2015 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 android.net.dhcp; 18 19 import com.android.internal.util.HexDump; 20 import com.android.internal.util.Protocol; 21 import com.android.internal.util.State; 22 import com.android.internal.util.MessageUtils; 23 import com.android.internal.util.StateMachine; 24 import com.android.internal.util.WakeupMessage; 25 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.net.DhcpResults; 30 import android.net.InterfaceConfiguration; 31 import android.net.LinkAddress; 32 import android.net.NetworkUtils; 33 import android.net.TrafficStats; 34 import android.net.metrics.IpConnectivityLog; 35 import android.net.metrics.DhcpClientEvent; 36 import android.net.metrics.DhcpErrorEvent; 37 import android.net.util.InterfaceParams; 38 import android.os.Message; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.os.SystemClock; 42 import android.system.ErrnoException; 43 import android.system.Os; 44 import android.system.PacketSocketAddress; 45 import android.util.EventLog; 46 import android.util.Log; 47 import android.util.SparseArray; 48 import android.util.TimeUtils; 49 50 import java.io.FileDescriptor; 51 import java.io.IOException; 52 import java.lang.Thread; 53 import java.net.Inet4Address; 54 import java.net.SocketException; 55 import java.nio.ByteBuffer; 56 import java.util.Arrays; 57 import java.util.Random; 58 59 import libcore.io.IoBridge; 60 61 import static android.system.OsConstants.*; 62 import static android.net.dhcp.DhcpPacket.*; 63 64 /** 65 * A DHCPv4 client. 66 * 67 * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android 68 * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. 69 * 70 * TODO: 71 * 72 * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). 73 * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not 74 * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a 75 * given SSID), it requests the last-leased IP address on the same interface, causing a delay if 76 * the server NAKs or a timeout if it doesn't. 77 * 78 * Known differences from current behaviour: 79 * 80 * - Does not request the "static routes" option. 81 * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. 82 * - Requests the "broadcast" option, but does nothing with it. 83 * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). 84 * 85 * @hide 86 */ 87 public class DhcpClient extends StateMachine { 88 89 private static final String TAG = "DhcpClient"; 90 private static final boolean DBG = true; 91 private static final boolean STATE_DBG = false; 92 private static final boolean MSG_DBG = false; 93 private static final boolean PACKET_DBG = false; 94 95 // Timers and timeouts. 96 private static final int SECONDS = 1000; 97 private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; 98 private static final int MAX_TIMEOUT_MS = 128 * SECONDS; 99 100 // This is not strictly needed, since the client is asynchronous and implements exponential 101 // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was 102 // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at 103 // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. 104 private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; 105 106 private static final int PUBLIC_BASE = Protocol.BASE_DHCP; 107 108 /* Commands from controller to start/stop DHCP */ 109 public static final int CMD_START_DHCP = PUBLIC_BASE + 1; 110 public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; 111 112 /* Notification from DHCP state machine prior to DHCP discovery/renewal */ 113 public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; 114 /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates 115 * success/failure */ 116 public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; 117 /* Notification from DHCP state machine before quitting */ 118 public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; 119 120 /* Command from controller to indicate DHCP discovery/renewal can continue 121 * after pre DHCP action is complete */ 122 public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; 123 124 /* Command and event notification to/from IpManager requesting the setting 125 * (or clearing) of an IPv4 LinkAddress. 126 */ 127 public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; 128 public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; 129 public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; 130 131 /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ 132 public static final int DHCP_SUCCESS = 1; 133 public static final int DHCP_FAILURE = 2; 134 135 // Internal messages. 136 private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100; 137 private static final int CMD_KICK = PRIVATE_BASE + 1; 138 private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; 139 private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; 140 private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; 141 private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; 142 private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; 143 144 // For message logging. 145 private static final Class[] sMessageClasses = { DhcpClient.class }; 146 private static final SparseArray<String> sMessageNames = 147 MessageUtils.findMessageNames(sMessageClasses); 148 149 // DHCP parameters that we request. 150 /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { 151 DHCP_SUBNET_MASK, 152 DHCP_ROUTER, 153 DHCP_DNS_SERVER, 154 DHCP_DOMAIN_NAME, 155 DHCP_MTU, 156 DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. 157 DHCP_LEASE_TIME, 158 DHCP_RENEWAL_TIME, 159 DHCP_REBINDING_TIME, 160 DHCP_VENDOR_INFO, 161 }; 162 163 // DHCP flag that means "yes, we support unicast." 164 private static final boolean DO_UNICAST = false; 165 166 // System services / libraries we use. 167 private final Context mContext; 168 private final Random mRandom; 169 private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); 170 171 // Sockets. 172 // - We use a packet socket to receive, because servers send us packets bound for IP addresses 173 // which we have not yet configured, and the kernel protocol stack drops these. 174 // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can 175 // be off-link as well as on-link). 176 private FileDescriptor mPacketSock; 177 private FileDescriptor mUdpSock; 178 private ReceiveThread mReceiveThread; 179 180 // State variables. 181 private final StateMachine mController; 182 private final WakeupMessage mKickAlarm; 183 private final WakeupMessage mTimeoutAlarm; 184 private final WakeupMessage mRenewAlarm; 185 private final WakeupMessage mRebindAlarm; 186 private final WakeupMessage mExpiryAlarm; 187 private final String mIfaceName; 188 189 private boolean mRegisteredForPreDhcpNotification; 190 private InterfaceParams mIface; 191 // TODO: MacAddress-ify more of this class hierarchy. 192 private byte[] mHwAddr; 193 private PacketSocketAddress mInterfaceBroadcastAddr; 194 private int mTransactionId; 195 private long mTransactionStartMillis; 196 private DhcpResults mDhcpLease; 197 private long mDhcpLeaseExpiry; 198 private DhcpResults mOffer; 199 200 // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. 201 private long mLastInitEnterTime; 202 private long mLastBoundExitTime; 203 204 // States. 205 private State mStoppedState = new StoppedState(); 206 private State mDhcpState = new DhcpState(); 207 private State mDhcpInitState = new DhcpInitState(); 208 private State mDhcpSelectingState = new DhcpSelectingState(); 209 private State mDhcpRequestingState = new DhcpRequestingState(); 210 private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); 211 private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); 212 private State mDhcpBoundState = new DhcpBoundState(); 213 private State mDhcpRenewingState = new DhcpRenewingState(); 214 private State mDhcpRebindingState = new DhcpRebindingState(); 215 private State mDhcpInitRebootState = new DhcpInitRebootState(); 216 private State mDhcpRebootingState = new DhcpRebootingState(); 217 private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); 218 private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); 219 220 private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { 221 cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; 222 return new WakeupMessage(mContext, getHandler(), cmdName, cmd); 223 } 224 225 // TODO: Take an InterfaceParams instance instead of an interface name String. 226 private DhcpClient(Context context, StateMachine controller, String iface) { 227 super(TAG, controller.getHandler()); 228 229 mContext = context; 230 mController = controller; 231 mIfaceName = iface; 232 233 addState(mStoppedState); 234 addState(mDhcpState); 235 addState(mDhcpInitState, mDhcpState); 236 addState(mWaitBeforeStartState, mDhcpState); 237 addState(mDhcpSelectingState, mDhcpState); 238 addState(mDhcpRequestingState, mDhcpState); 239 addState(mDhcpHaveLeaseState, mDhcpState); 240 addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); 241 addState(mDhcpBoundState, mDhcpHaveLeaseState); 242 addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); 243 addState(mDhcpRenewingState, mDhcpHaveLeaseState); 244 addState(mDhcpRebindingState, mDhcpHaveLeaseState); 245 addState(mDhcpInitRebootState, mDhcpState); 246 addState(mDhcpRebootingState, mDhcpState); 247 248 setInitialState(mStoppedState); 249 250 mRandom = new Random(); 251 252 // Used to schedule packet retransmissions. 253 mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); 254 // Used to time out PacketRetransmittingStates. 255 mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); 256 // Used to schedule DHCP reacquisition. 257 mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); 258 mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); 259 mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); 260 } 261 262 public void registerForPreDhcpNotification() { 263 mRegisteredForPreDhcpNotification = true; 264 } 265 266 public static DhcpClient makeDhcpClient( 267 Context context, StateMachine controller, InterfaceParams ifParams) { 268 DhcpClient client = new DhcpClient(context, controller, ifParams.name); 269 client.mIface = ifParams; 270 client.start(); 271 return client; 272 } 273 274 private boolean initInterface() { 275 if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); 276 if (mIface == null) { 277 Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); 278 return false; 279 } 280 281 mHwAddr = mIface.macAddr.toByteArray(); 282 mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); 283 return true; 284 } 285 286 private void startNewTransaction() { 287 mTransactionId = mRandom.nextInt(); 288 mTransactionStartMillis = SystemClock.elapsedRealtime(); 289 } 290 291 private boolean initSockets() { 292 return initPacketSocket() && initUdpSocket(); 293 } 294 295 private boolean initPacketSocket() { 296 try { 297 mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); 298 PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.index); 299 Os.bind(mPacketSock, addr); 300 NetworkUtils.attachDhcpFilter(mPacketSock); 301 } catch(SocketException|ErrnoException e) { 302 Log.e(TAG, "Error creating packet socket", e); 303 return false; 304 } 305 return true; 306 } 307 308 private boolean initUdpSocket() { 309 final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); 310 try { 311 mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 312 Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); 313 Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName); 314 Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); 315 Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); 316 Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); 317 NetworkUtils.protectFromVpn(mUdpSock); 318 } catch(SocketException|ErrnoException e) { 319 Log.e(TAG, "Error creating UDP socket", e); 320 return false; 321 } finally { 322 TrafficStats.setThreadStatsTag(oldTag); 323 } 324 return true; 325 } 326 327 private boolean connectUdpSock(Inet4Address to) { 328 try { 329 Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); 330 return true; 331 } catch (SocketException|ErrnoException e) { 332 Log.e(TAG, "Error connecting UDP socket", e); 333 return false; 334 } 335 } 336 337 private static void closeQuietly(FileDescriptor fd) { 338 try { 339 IoBridge.closeAndSignalBlockedThreads(fd); 340 } catch (IOException ignored) {} 341 } 342 343 private void closeSockets() { 344 closeQuietly(mUdpSock); 345 closeQuietly(mPacketSock); 346 } 347 348 class ReceiveThread extends Thread { 349 350 private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; 351 private volatile boolean mStopped = false; 352 353 public void halt() { 354 mStopped = true; 355 closeSockets(); // Interrupts the read() call the thread is blocked in. 356 } 357 358 @Override 359 public void run() { 360 if (DBG) Log.d(TAG, "Receive thread started"); 361 while (!mStopped) { 362 int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. 363 try { 364 length = Os.read(mPacketSock, mPacket, 0, mPacket.length); 365 DhcpPacket packet = null; 366 packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); 367 if (DBG) Log.d(TAG, "Received packet: " + packet); 368 sendMessage(CMD_RECEIVED_PACKET, packet); 369 } catch (IOException|ErrnoException e) { 370 if (!mStopped) { 371 Log.e(TAG, "Read error", e); 372 logError(DhcpErrorEvent.RECEIVE_ERROR); 373 } 374 } catch (DhcpPacket.ParseException e) { 375 Log.e(TAG, "Can't parse packet: " + e.getMessage()); 376 if (PACKET_DBG) { 377 Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); 378 } 379 if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { 380 int snetTagId = 0x534e4554; 381 String bugId = "31850211"; 382 int uid = -1; 383 String data = DhcpPacket.ParseException.class.getName(); 384 EventLog.writeEvent(snetTagId, bugId, uid, data); 385 } 386 logError(e.errorCode); 387 } 388 } 389 if (DBG) Log.d(TAG, "Receive thread stopped"); 390 } 391 } 392 393 private short getSecs() { 394 return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); 395 } 396 397 private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { 398 try { 399 if (encap == DhcpPacket.ENCAP_L2) { 400 if (DBG) Log.d(TAG, "Broadcasting " + description); 401 Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); 402 } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { 403 if (DBG) Log.d(TAG, "Broadcasting " + description); 404 // We only send L3-encapped broadcasts in DhcpRebindingState, 405 // where we have an IP address and an unconnected UDP socket. 406 // 407 // N.B.: We only need this codepath because DhcpRequestPacket 408 // hardcodes the source IP address to 0.0.0.0. We could reuse 409 // the packet socket if this ever changes. 410 Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); 411 } else { 412 // It's safe to call getpeername here, because we only send unicast packets if we 413 // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. 414 if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", 415 description, Os.getpeername(mUdpSock))); 416 Os.write(mUdpSock, buf); 417 } 418 } catch(ErrnoException|IOException e) { 419 Log.e(TAG, "Can't send packet: ", e); 420 return false; 421 } 422 return true; 423 } 424 425 private boolean sendDiscoverPacket() { 426 ByteBuffer packet = DhcpPacket.buildDiscoverPacket( 427 DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, 428 DO_UNICAST, REQUESTED_PARAMS); 429 return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); 430 } 431 432 private boolean sendRequestPacket( 433 Inet4Address clientAddress, Inet4Address requestedAddress, 434 Inet4Address serverAddress, Inet4Address to) { 435 // TODO: should we use the transaction ID from the server? 436 final int encap = INADDR_ANY.equals(clientAddress) 437 ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; 438 439 ByteBuffer packet = DhcpPacket.buildRequestPacket( 440 encap, mTransactionId, getSecs(), clientAddress, 441 DO_UNICAST, mHwAddr, requestedAddress, 442 serverAddress, REQUESTED_PARAMS, null); 443 String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; 444 String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + 445 " request=" + requestedAddress.getHostAddress() + 446 " serverid=" + serverStr; 447 return transmitPacket(packet, description, encap, to); 448 } 449 450 private void scheduleLeaseTimers() { 451 if (mDhcpLeaseExpiry == 0) { 452 Log.d(TAG, "Infinite lease, no timer scheduling needed"); 453 return; 454 } 455 456 final long now = SystemClock.elapsedRealtime(); 457 458 // TODO: consider getting the renew and rebind timers from T1 and T2. 459 // See also: 460 // https://tools.ietf.org/html/rfc2131#section-4.4.5 461 // https://tools.ietf.org/html/rfc1533#section-9.9 462 // https://tools.ietf.org/html/rfc1533#section-9.10 463 final long remainingDelay = mDhcpLeaseExpiry - now; 464 final long renewDelay = remainingDelay / 2; 465 final long rebindDelay = remainingDelay * 7 / 8; 466 mRenewAlarm.schedule(now + renewDelay); 467 mRebindAlarm.schedule(now + rebindDelay); 468 mExpiryAlarm.schedule(now + remainingDelay); 469 Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); 470 Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); 471 Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); 472 } 473 474 private void notifySuccess() { 475 mController.sendMessage( 476 CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); 477 } 478 479 private void notifyFailure() { 480 mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); 481 } 482 483 private void acceptDhcpResults(DhcpResults results, String msg) { 484 mDhcpLease = results; 485 mOffer = null; 486 Log.d(TAG, msg + " lease: " + mDhcpLease); 487 notifySuccess(); 488 } 489 490 private void clearDhcpState() { 491 mDhcpLease = null; 492 mDhcpLeaseExpiry = 0; 493 mOffer = null; 494 } 495 496 /** 497 * Quit the DhcpStateMachine. 498 * 499 * @hide 500 */ 501 public void doQuit() { 502 Log.d(TAG, "doQuit"); 503 quit(); 504 } 505 506 @Override 507 protected void onQuitting() { 508 Log.d(TAG, "onQuitting"); 509 mController.sendMessage(CMD_ON_QUIT); 510 } 511 512 abstract class LoggingState extends State { 513 private long mEnterTimeMs; 514 515 @Override 516 public void enter() { 517 if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); 518 mEnterTimeMs = SystemClock.elapsedRealtime(); 519 } 520 521 @Override 522 public void exit() { 523 long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; 524 logState(getName(), (int) durationMs); 525 } 526 527 private String messageName(int what) { 528 return sMessageNames.get(what, Integer.toString(what)); 529 } 530 531 private String messageToString(Message message) { 532 long now = SystemClock.uptimeMillis(); 533 StringBuilder b = new StringBuilder(" "); 534 TimeUtils.formatDuration(message.getWhen() - now, b); 535 b.append(" ").append(messageName(message.what)) 536 .append(" ").append(message.arg1) 537 .append(" ").append(message.arg2) 538 .append(" ").append(message.obj); 539 return b.toString(); 540 } 541 542 @Override 543 public boolean processMessage(Message message) { 544 if (MSG_DBG) { 545 Log.d(TAG, getName() + messageToString(message)); 546 } 547 return NOT_HANDLED; 548 } 549 550 @Override 551 public String getName() { 552 // All DhcpClient's states are inner classes with a well defined name. 553 // Use getSimpleName() and avoid super's getName() creating new String instances. 554 return getClass().getSimpleName(); 555 } 556 } 557 558 // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with 559 // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. 560 abstract class WaitBeforeOtherState extends LoggingState { 561 protected State mOtherState; 562 563 @Override 564 public void enter() { 565 super.enter(); 566 mController.sendMessage(CMD_PRE_DHCP_ACTION); 567 } 568 569 @Override 570 public boolean processMessage(Message message) { 571 super.processMessage(message); 572 switch (message.what) { 573 case CMD_PRE_DHCP_ACTION_COMPLETE: 574 transitionTo(mOtherState); 575 return HANDLED; 576 default: 577 return NOT_HANDLED; 578 } 579 } 580 } 581 582 class StoppedState extends State { 583 @Override 584 public boolean processMessage(Message message) { 585 switch (message.what) { 586 case CMD_START_DHCP: 587 if (mRegisteredForPreDhcpNotification) { 588 transitionTo(mWaitBeforeStartState); 589 } else { 590 transitionTo(mDhcpInitState); 591 } 592 return HANDLED; 593 default: 594 return NOT_HANDLED; 595 } 596 } 597 } 598 599 class WaitBeforeStartState extends WaitBeforeOtherState { 600 public WaitBeforeStartState(State otherState) { 601 super(); 602 mOtherState = otherState; 603 } 604 } 605 606 class WaitBeforeRenewalState extends WaitBeforeOtherState { 607 public WaitBeforeRenewalState(State otherState) { 608 super(); 609 mOtherState = otherState; 610 } 611 } 612 613 class DhcpState extends State { 614 @Override 615 public void enter() { 616 clearDhcpState(); 617 if (initInterface() && initSockets()) { 618 mReceiveThread = new ReceiveThread(); 619 mReceiveThread.start(); 620 } else { 621 notifyFailure(); 622 transitionTo(mStoppedState); 623 } 624 } 625 626 @Override 627 public void exit() { 628 if (mReceiveThread != null) { 629 mReceiveThread.halt(); // Also closes sockets. 630 mReceiveThread = null; 631 } 632 clearDhcpState(); 633 } 634 635 @Override 636 public boolean processMessage(Message message) { 637 super.processMessage(message); 638 switch (message.what) { 639 case CMD_STOP_DHCP: 640 transitionTo(mStoppedState); 641 return HANDLED; 642 default: 643 return NOT_HANDLED; 644 } 645 } 646 } 647 648 public boolean isValidPacket(DhcpPacket packet) { 649 // TODO: check checksum. 650 int xid = packet.getTransactionId(); 651 if (xid != mTransactionId) { 652 Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); 653 return false; 654 } 655 if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { 656 Log.d(TAG, "MAC addr mismatch: got " + 657 HexDump.toHexString(packet.getClientMac()) + ", expected " + 658 HexDump.toHexString(packet.getClientMac())); 659 return false; 660 } 661 return true; 662 } 663 664 public void setDhcpLeaseExpiry(DhcpPacket packet) { 665 long leaseTimeMillis = packet.getLeaseTimeMillis(); 666 mDhcpLeaseExpiry = 667 (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; 668 } 669 670 /** 671 * Retransmits packets using jittered exponential backoff with an optional timeout. Packet 672 * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass 673 * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout 674 * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the 675 * state. 676 * 677 * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a 678 * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET 679 * sent by the receive thread. They may also set mTimeout and implement timeout. 680 */ 681 abstract class PacketRetransmittingState extends LoggingState { 682 683 private int mTimer; 684 protected int mTimeout = 0; 685 686 @Override 687 public void enter() { 688 super.enter(); 689 initTimer(); 690 maybeInitTimeout(); 691 sendMessage(CMD_KICK); 692 } 693 694 @Override 695 public boolean processMessage(Message message) { 696 super.processMessage(message); 697 switch (message.what) { 698 case CMD_KICK: 699 sendPacket(); 700 scheduleKick(); 701 return HANDLED; 702 case CMD_RECEIVED_PACKET: 703 receivePacket((DhcpPacket) message.obj); 704 return HANDLED; 705 case CMD_TIMEOUT: 706 timeout(); 707 return HANDLED; 708 default: 709 return NOT_HANDLED; 710 } 711 } 712 713 @Override 714 public void exit() { 715 super.exit(); 716 mKickAlarm.cancel(); 717 mTimeoutAlarm.cancel(); 718 } 719 720 abstract protected boolean sendPacket(); 721 abstract protected void receivePacket(DhcpPacket packet); 722 protected void timeout() {} 723 724 protected void initTimer() { 725 mTimer = FIRST_TIMEOUT_MS; 726 } 727 728 protected int jitterTimer(int baseTimer) { 729 int maxJitter = baseTimer / 10; 730 int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; 731 return baseTimer + jitter; 732 } 733 734 protected void scheduleKick() { 735 long now = SystemClock.elapsedRealtime(); 736 long timeout = jitterTimer(mTimer); 737 long alarmTime = now + timeout; 738 mKickAlarm.schedule(alarmTime); 739 mTimer *= 2; 740 if (mTimer > MAX_TIMEOUT_MS) { 741 mTimer = MAX_TIMEOUT_MS; 742 } 743 } 744 745 protected void maybeInitTimeout() { 746 if (mTimeout > 0) { 747 long alarmTime = SystemClock.elapsedRealtime() + mTimeout; 748 mTimeoutAlarm.schedule(alarmTime); 749 } 750 } 751 } 752 753 class DhcpInitState extends PacketRetransmittingState { 754 public DhcpInitState() { 755 super(); 756 } 757 758 @Override 759 public void enter() { 760 super.enter(); 761 startNewTransaction(); 762 mLastInitEnterTime = SystemClock.elapsedRealtime(); 763 } 764 765 protected boolean sendPacket() { 766 return sendDiscoverPacket(); 767 } 768 769 protected void receivePacket(DhcpPacket packet) { 770 if (!isValidPacket(packet)) return; 771 if (!(packet instanceof DhcpOfferPacket)) return; 772 mOffer = packet.toDhcpResults(); 773 if (mOffer != null) { 774 Log.d(TAG, "Got pending lease: " + mOffer); 775 transitionTo(mDhcpRequestingState); 776 } 777 } 778 } 779 780 // Not implemented. We request the first offer we receive. 781 class DhcpSelectingState extends LoggingState { 782 } 783 784 class DhcpRequestingState extends PacketRetransmittingState { 785 public DhcpRequestingState() { 786 mTimeout = DHCP_TIMEOUT_MS / 2; 787 } 788 789 protected boolean sendPacket() { 790 return sendRequestPacket( 791 INADDR_ANY, // ciaddr 792 (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP 793 (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER 794 INADDR_BROADCAST); // packet destination address 795 } 796 797 protected void receivePacket(DhcpPacket packet) { 798 if (!isValidPacket(packet)) return; 799 if ((packet instanceof DhcpAckPacket)) { 800 DhcpResults results = packet.toDhcpResults(); 801 if (results != null) { 802 setDhcpLeaseExpiry(packet); 803 acceptDhcpResults(results, "Confirmed"); 804 transitionTo(mConfiguringInterfaceState); 805 } 806 } else if (packet instanceof DhcpNakPacket) { 807 // TODO: Wait a while before returning into INIT state. 808 Log.d(TAG, "Received NAK, returning to INIT"); 809 mOffer = null; 810 transitionTo(mDhcpInitState); 811 } 812 } 813 814 @Override 815 protected void timeout() { 816 // After sending REQUESTs unsuccessfully for a while, go back to init. 817 transitionTo(mDhcpInitState); 818 } 819 } 820 821 class DhcpHaveLeaseState extends State { 822 @Override 823 public boolean processMessage(Message message) { 824 switch (message.what) { 825 case CMD_EXPIRE_DHCP: 826 Log.d(TAG, "Lease expired!"); 827 notifyFailure(); 828 transitionTo(mDhcpInitState); 829 return HANDLED; 830 default: 831 return NOT_HANDLED; 832 } 833 } 834 835 @Override 836 public void exit() { 837 // Clear any extant alarms. 838 mRenewAlarm.cancel(); 839 mRebindAlarm.cancel(); 840 mExpiryAlarm.cancel(); 841 clearDhcpState(); 842 // Tell IpManager to clear the IPv4 address. There is no need to 843 // wait for confirmation since any subsequent packets are sent from 844 // INADDR_ANY anyway (DISCOVER, REQUEST). 845 mController.sendMessage(CMD_CLEAR_LINKADDRESS); 846 } 847 } 848 849 class ConfiguringInterfaceState extends LoggingState { 850 @Override 851 public void enter() { 852 super.enter(); 853 mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); 854 } 855 856 @Override 857 public boolean processMessage(Message message) { 858 super.processMessage(message); 859 switch (message.what) { 860 case EVENT_LINKADDRESS_CONFIGURED: 861 transitionTo(mDhcpBoundState); 862 return HANDLED; 863 default: 864 return NOT_HANDLED; 865 } 866 } 867 } 868 869 class DhcpBoundState extends LoggingState { 870 @Override 871 public void enter() { 872 super.enter(); 873 if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { 874 // There's likely no point in going into DhcpInitState here, we'll probably 875 // just repeat the transaction, get the same IP address as before, and fail. 876 // 877 // NOTE: It is observed that connectUdpSock() basically never fails, due to 878 // SO_BINDTODEVICE. Examining the local socket address shows it will happily 879 // return an IPv4 address from another interface, or even return "0.0.0.0". 880 // 881 // TODO: Consider deleting this check, following testing on several kernels. 882 notifyFailure(); 883 transitionTo(mStoppedState); 884 } 885 886 scheduleLeaseTimers(); 887 logTimeToBoundState(); 888 } 889 890 @Override 891 public void exit() { 892 super.exit(); 893 mLastBoundExitTime = SystemClock.elapsedRealtime(); 894 } 895 896 @Override 897 public boolean processMessage(Message message) { 898 super.processMessage(message); 899 switch (message.what) { 900 case CMD_RENEW_DHCP: 901 if (mRegisteredForPreDhcpNotification) { 902 transitionTo(mWaitBeforeRenewalState); 903 } else { 904 transitionTo(mDhcpRenewingState); 905 } 906 return HANDLED; 907 default: 908 return NOT_HANDLED; 909 } 910 } 911 912 private void logTimeToBoundState() { 913 long now = SystemClock.elapsedRealtime(); 914 if (mLastBoundExitTime > mLastInitEnterTime) { 915 logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); 916 } else { 917 logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); 918 } 919 } 920 } 921 922 abstract class DhcpReacquiringState extends PacketRetransmittingState { 923 protected String mLeaseMsg; 924 925 @Override 926 public void enter() { 927 super.enter(); 928 startNewTransaction(); 929 } 930 931 abstract protected Inet4Address packetDestination(); 932 933 protected boolean sendPacket() { 934 return sendRequestPacket( 935 (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr 936 INADDR_ANY, // DHCP_REQUESTED_IP 937 null, // DHCP_SERVER_IDENTIFIER 938 packetDestination()); // packet destination address 939 } 940 941 protected void receivePacket(DhcpPacket packet) { 942 if (!isValidPacket(packet)) return; 943 if ((packet instanceof DhcpAckPacket)) { 944 final DhcpResults results = packet.toDhcpResults(); 945 if (results != null) { 946 if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { 947 Log.d(TAG, "Renewed lease not for our current IP address!"); 948 notifyFailure(); 949 transitionTo(mDhcpInitState); 950 } 951 setDhcpLeaseExpiry(packet); 952 // Updating our notion of DhcpResults here only causes the 953 // DNS servers and routes to be updated in LinkProperties 954 // in IpManager and by any overridden relevant handlers of 955 // the registered IpManager.Callback. IP address changes 956 // are not supported here. 957 acceptDhcpResults(results, mLeaseMsg); 958 transitionTo(mDhcpBoundState); 959 } 960 } else if (packet instanceof DhcpNakPacket) { 961 Log.d(TAG, "Received NAK, returning to INIT"); 962 notifyFailure(); 963 transitionTo(mDhcpInitState); 964 } 965 } 966 } 967 968 class DhcpRenewingState extends DhcpReacquiringState { 969 public DhcpRenewingState() { 970 mLeaseMsg = "Renewed"; 971 } 972 973 @Override 974 public boolean processMessage(Message message) { 975 if (super.processMessage(message) == HANDLED) { 976 return HANDLED; 977 } 978 979 switch (message.what) { 980 case CMD_REBIND_DHCP: 981 transitionTo(mDhcpRebindingState); 982 return HANDLED; 983 default: 984 return NOT_HANDLED; 985 } 986 } 987 988 @Override 989 protected Inet4Address packetDestination() { 990 // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... 991 // http://b/25343517 . Try to make things work anyway by using broadcast renews. 992 return (mDhcpLease.serverAddress != null) ? 993 mDhcpLease.serverAddress : INADDR_BROADCAST; 994 } 995 } 996 997 class DhcpRebindingState extends DhcpReacquiringState { 998 public DhcpRebindingState() { 999 mLeaseMsg = "Rebound"; 1000 } 1001 1002 @Override 1003 public void enter() { 1004 super.enter(); 1005 1006 // We need to broadcast and possibly reconnect the socket to a 1007 // completely different server. 1008 closeQuietly(mUdpSock); 1009 if (!initUdpSocket()) { 1010 Log.e(TAG, "Failed to recreate UDP socket"); 1011 transitionTo(mDhcpInitState); 1012 } 1013 } 1014 1015 @Override 1016 protected Inet4Address packetDestination() { 1017 return INADDR_BROADCAST; 1018 } 1019 } 1020 1021 class DhcpInitRebootState extends LoggingState { 1022 } 1023 1024 class DhcpRebootingState extends LoggingState { 1025 } 1026 1027 private void logError(int errorCode) { 1028 mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode)); 1029 } 1030 1031 private void logState(String name, int durationMs) { 1032 mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs)); 1033 } 1034 } 1035