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