• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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