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