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