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