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