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