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