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.StateMachine;
23 
24 import android.app.AlarmManager;
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.net.DhcpResults;
31 import android.net.BaseDhcpStateMachine;
32 import android.net.DhcpStateMachine;
33 import android.net.InterfaceConfiguration;
34 import android.net.LinkAddress;
35 import android.net.NetworkUtils;
36 import android.os.IBinder;
37 import android.os.INetworkManagementService;
38 import android.os.Message;
39 import android.os.PowerManager;
40 import android.os.RemoteException;
41 import android.os.ServiceManager;
42 import android.os.SystemClock;
43 import android.system.ErrnoException;
44 import android.system.Os;
45 import android.system.PacketSocketAddress;
46 import android.util.Log;
47 import android.util.TimeUtils;
48 
49 import java.io.FileDescriptor;
50 import java.io.IOException;
51 import java.lang.Thread;
52 import java.net.Inet4Address;
53 import java.net.InetSocketAddress;
54 import java.net.NetworkInterface;
55 import java.net.SocketException;
56 import java.nio.BufferUnderflowException;
57 import java.nio.ByteBuffer;
58 import java.util.Arrays;
59 import java.util.Random;
60 
61 import libcore.io.IoBridge;
62 
63 import static android.system.OsConstants.*;
64 import static android.net.dhcp.DhcpPacket.*;
65 
66 /**
67  * A DHCPv4 client.
68  *
69  * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
70  * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
71  *
72  * TODO:
73  *
74  * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
75  * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
76  *   do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
77  *   given SSID), it requests the last-leased IP address on the same interface, causing a delay if
78  *   the server NAKs or a timeout if it doesn't.
79  *
80  * Known differences from current behaviour:
81  *
82  * - Does not request the "static routes" option.
83  * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
84  * - Requests the "broadcast" option, but does nothing with it.
85  * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
86  *
87  * @hide
88  */
89 public class DhcpClient extends BaseDhcpStateMachine {
90 
91     private static final String TAG = "DhcpClient";
92     private static final boolean DBG = true;
93     private static final boolean STATE_DBG = false;
94     private static final boolean MSG_DBG = false;
95     private static final boolean PACKET_DBG = true;
96 
97     // Timers and timeouts.
98     private static final int SECONDS = 1000;
99     private static final int FIRST_TIMEOUT_MS   =   2 * SECONDS;
100     private static final int MAX_TIMEOUT_MS     = 128 * SECONDS;
101 
102     // This is not strictly needed, since the client is asynchronous and implements exponential
103     // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
104     // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
105     // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
106     private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
107 
108     // Messages.
109     private static final int BASE                 = Protocol.BASE_DHCP + 100;
110     private static final int CMD_KICK             = BASE + 1;
111     private static final int CMD_RECEIVED_PACKET  = BASE + 2;
112     private static final int CMD_TIMEOUT          = BASE + 3;
113     private static final int CMD_ONESHOT_TIMEOUT  = BASE + 4;
114 
115     // DHCP parameters that we request.
116     private static final byte[] REQUESTED_PARAMS = new byte[] {
117         DHCP_SUBNET_MASK,
118         DHCP_ROUTER,
119         DHCP_DNS_SERVER,
120         DHCP_DOMAIN_NAME,
121         DHCP_MTU,
122         DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
123         DHCP_LEASE_TIME,
124         DHCP_RENEWAL_TIME,
125         DHCP_REBINDING_TIME,
126     };
127 
128     // DHCP flag that means "yes, we support unicast."
129     private static final boolean DO_UNICAST   = false;
130 
131     // System services / libraries we use.
132     private final Context mContext;
133     private final AlarmManager mAlarmManager;
134     private final Random mRandom;
135     private final INetworkManagementService mNMService;
136 
137     // Sockets.
138     // - We use a packet socket to receive, because servers send us packets bound for IP addresses
139     //   which we have not yet configured, and the kernel protocol stack drops these.
140     // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
141     //   be off-link as well as on-link).
142     private FileDescriptor mPacketSock;
143     private FileDescriptor mUdpSock;
144     private ReceiveThread mReceiveThread;
145 
146     // State variables.
147     private final StateMachine mController;
148     private final PendingIntent mKickIntent;
149     private final PendingIntent mTimeoutIntent;
150     private final PendingIntent mRenewIntent;
151     private final PendingIntent mOneshotTimeoutIntent;
152     private final String mIfaceName;
153 
154     private boolean mRegisteredForPreDhcpNotification;
155     private NetworkInterface mIface;
156     private byte[] mHwAddr;
157     private PacketSocketAddress mInterfaceBroadcastAddr;
158     private int mTransactionId;
159     private long mTransactionStartMillis;
160     private DhcpResults mDhcpLease;
161     private long mDhcpLeaseExpiry;
162     private DhcpResults mOffer;
163 
164     // States.
165     private State mStoppedState = new StoppedState();
166     private State mDhcpState = new DhcpState();
167     private State mDhcpInitState = new DhcpInitState();
168     private State mDhcpSelectingState = new DhcpSelectingState();
169     private State mDhcpRequestingState = new DhcpRequestingState();
170     private State mDhcpHaveAddressState = new DhcpHaveAddressState();
171     private State mDhcpBoundState = new DhcpBoundState();
172     private State mDhcpRenewingState = new DhcpRenewingState();
173     private State mDhcpRebindingState = new DhcpRebindingState();
174     private State mDhcpInitRebootState = new DhcpInitRebootState();
175     private State mDhcpRebootingState = new DhcpRebootingState();
176     private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
177     private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
178 
DhcpClient(Context context, StateMachine controller, String iface)179     private DhcpClient(Context context, StateMachine controller, String iface) {
180         super(TAG);
181 
182         mContext = context;
183         mController = controller;
184         mIfaceName = iface;
185 
186         addState(mStoppedState);
187         addState(mDhcpState);
188             addState(mDhcpInitState, mDhcpState);
189             addState(mWaitBeforeStartState, mDhcpState);
190             addState(mDhcpSelectingState, mDhcpState);
191             addState(mDhcpRequestingState, mDhcpState);
192             addState(mDhcpHaveAddressState, mDhcpState);
193                 addState(mDhcpBoundState, mDhcpHaveAddressState);
194                 addState(mWaitBeforeRenewalState, mDhcpHaveAddressState);
195                 addState(mDhcpRenewingState, mDhcpHaveAddressState);
196                 addState(mDhcpRebindingState, mDhcpHaveAddressState);
197             addState(mDhcpInitRebootState, mDhcpState);
198             addState(mDhcpRebootingState, mDhcpState);
199 
200         setInitialState(mStoppedState);
201 
202         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
203         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
204         mNMService = INetworkManagementService.Stub.asInterface(b);
205 
206         mRandom = new Random();
207 
208         // Used to schedule packet retransmissions.
209         mKickIntent = createStateMachineCommandIntent("KICK", CMD_KICK);
210         // Used to time out PacketRetransmittingStates.
211         mTimeoutIntent = createStateMachineCommandIntent("TIMEOUT", CMD_TIMEOUT);
212         // Used to schedule DHCP renews.
213         mRenewIntent = createStateMachineCommandIntent("RENEW", DhcpStateMachine.CMD_RENEW_DHCP);
214         // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out.
215         // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and
216         // remove this.
217         mOneshotTimeoutIntent = createStateMachineCommandIntent("ONESHOT_TIMEOUT",
218                 CMD_ONESHOT_TIMEOUT);
219     }
220 
221     @Override
registerForPreDhcpNotification()222     public void registerForPreDhcpNotification() {
223         mRegisteredForPreDhcpNotification = true;
224     }
225 
makeDhcpStateMachine( Context context, StateMachine controller, String intf)226     public static BaseDhcpStateMachine makeDhcpStateMachine(
227             Context context, StateMachine controller, String intf) {
228         DhcpClient client = new DhcpClient(context, controller, intf);
229         client.start();
230         return client;
231     }
232 
233     /**
234      * Constructs a PendingIntent that sends the specified command to the state machine. This is
235      * implemented by creating an Intent with the specified parameters, and creating and registering
236      * a BroadcastReceiver for it. The broadcast must be sent by a process that holds the
237      * {@code CONNECTIVITY_INTERNAL} permission.
238      *
239      * @param cmdName the name of the command. The intent's action will be
240      *         {@code android.net.dhcp.DhcpClient.<mIfaceName>.<cmdName>}
241      * @param cmd the command to send to the state machine when the PendingIntent is triggered.
242      * @return the PendingIntent
243      */
createStateMachineCommandIntent(final String cmdName, final int cmd)244     private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) {
245         String action = DhcpClient.class.getName() + "." + mIfaceName + "." + cmdName;
246 
247         Intent intent = new Intent(action, null)
248                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
249         // TODO: The intent's package covers the whole of the system server, so it's pretty generic.
250         // Consider adding some sort of token as well.
251         intent.setPackage(mContext.getPackageName());
252         PendingIntent pendingIntent =  PendingIntent.getBroadcast(mContext, cmd, intent, 0);
253 
254         mContext.registerReceiver(
255             new BroadcastReceiver() {
256                 @Override
257                 public void onReceive(Context context, Intent intent) {
258                     sendMessage(cmd);
259                 }
260             },
261             new IntentFilter(action),
262             android.Manifest.permission.CONNECTIVITY_INTERNAL,
263             null);
264 
265         return pendingIntent;
266     }
267 
initInterface()268     private boolean initInterface() {
269         try {
270             mIface = NetworkInterface.getByName(mIfaceName);
271             mHwAddr = mIface.getHardwareAddress();
272             mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(),
273                     DhcpPacket.ETHER_BROADCAST);
274             return true;
275         } catch(SocketException e) {
276             Log.wtf(TAG, "Can't determine ifindex or MAC address for " + mIfaceName);
277             return false;
278         }
279     }
280 
startNewTransaction()281     private void startNewTransaction() {
282         mTransactionId = mRandom.nextInt();
283         mTransactionStartMillis = SystemClock.elapsedRealtime();
284     }
285 
initSockets()286     private boolean initSockets() {
287         try {
288             mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
289             PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
290             Os.bind(mPacketSock, addr);
291             NetworkUtils.attachDhcpFilter(mPacketSock);
292         } catch(SocketException|ErrnoException e) {
293             Log.e(TAG, "Error creating packet socket", e);
294             return false;
295         }
296         try {
297             mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
298             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
299             Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
300             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
301             Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
302             NetworkUtils.protectFromVpn(mUdpSock);
303         } catch(SocketException|ErrnoException e) {
304             Log.e(TAG, "Error creating UDP socket", e);
305             return false;
306         }
307         return true;
308     }
309 
closeQuietly(FileDescriptor fd)310     private static void closeQuietly(FileDescriptor fd) {
311         try {
312             IoBridge.closeAndSignalBlockedThreads(fd);
313         } catch (IOException ignored) {}
314     }
315 
closeSockets()316     private void closeSockets() {
317         closeQuietly(mUdpSock);
318         closeQuietly(mPacketSock);
319     }
320 
setIpAddress(LinkAddress address)321     private boolean setIpAddress(LinkAddress address) {
322         InterfaceConfiguration ifcg = new InterfaceConfiguration();
323         ifcg.setLinkAddress(address);
324         try {
325             mNMService.setInterfaceConfig(mIfaceName, ifcg);
326         } catch (RemoteException|IllegalStateException e) {
327             Log.e(TAG, "Error configuring IP address : " + e);
328             return false;
329         }
330         return true;
331     }
332 
333     class ReceiveThread extends Thread {
334 
335         private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
336         private boolean stopped = false;
337 
halt()338         public void halt() {
339             stopped = true;
340             closeSockets();  // Interrupts the read() call the thread is blocked in.
341         }
342 
343         @Override
run()344         public void run() {
345             maybeLog("Receive thread started");
346             while (!stopped) {
347                 try {
348                     int length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
349                     DhcpPacket packet = null;
350                     packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
351                     if (packet != null) {
352                         maybeLog("Received packet: " + packet);
353                         sendMessage(CMD_RECEIVED_PACKET, packet);
354                     } else if (PACKET_DBG) {
355                         Log.d(TAG,
356                                 "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length));
357                     }
358                 } catch (IOException|ErrnoException e) {
359                     if (!stopped) {
360                         Log.e(TAG, "Read error", e);
361                     }
362                 }
363             }
364             maybeLog("Receive thread stopped");
365         }
366     }
367 
getSecs()368     private short getSecs() {
369         return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
370     }
371 
transmitPacket(ByteBuffer buf, String description, Inet4Address to)372     private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) {
373         try {
374             if (to.equals(INADDR_BROADCAST)) {
375                 maybeLog("Broadcasting " + description);
376                 Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
377             } else {
378                 maybeLog("Unicasting " + description + " to " + to.getHostAddress());
379                 Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
380             }
381         } catch(ErrnoException|IOException e) {
382             Log.e(TAG, "Can't send packet: ", e);
383             return false;
384         }
385         return true;
386     }
387 
sendDiscoverPacket()388     private boolean sendDiscoverPacket() {
389         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
390                 DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
391                 DO_UNICAST, REQUESTED_PARAMS);
392         return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST);
393     }
394 
sendRequestPacket( Inet4Address clientAddress, Inet4Address requestedAddress, Inet4Address serverAddress, Inet4Address to)395     private boolean sendRequestPacket(
396             Inet4Address clientAddress, Inet4Address requestedAddress,
397             Inet4Address serverAddress, Inet4Address to) {
398         // TODO: should we use the transaction ID from the server?
399         int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
400 
401         ByteBuffer packet = DhcpPacket.buildRequestPacket(
402                 encap, mTransactionId, getSecs(), clientAddress,
403                 DO_UNICAST, mHwAddr, requestedAddress,
404                 serverAddress, REQUESTED_PARAMS, null);
405         String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
406                              " request=" + requestedAddress.getHostAddress() +
407                              " to=" + serverAddress.getHostAddress();
408         return transmitPacket(packet, description, to);
409     }
410 
scheduleRenew()411     private void scheduleRenew() {
412         mAlarmManager.cancel(mRenewIntent);
413         if (mDhcpLeaseExpiry != 0) {
414             long now = SystemClock.elapsedRealtime();
415             long alarmTime = (now + mDhcpLeaseExpiry) / 2;
416             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mRenewIntent);
417             Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s");
418         } else {
419             Log.d(TAG, "Infinite lease, no renewal needed");
420         }
421     }
422 
notifySuccess()423     private void notifySuccess() {
424         mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
425                 DhcpStateMachine.DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
426     }
427 
notifyFailure()428     private void notifyFailure() {
429         mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
430                 DhcpStateMachine.DHCP_FAILURE, 0, null);
431     }
432 
clearDhcpState()433     private void clearDhcpState() {
434         mDhcpLease = null;
435         mDhcpLeaseExpiry = 0;
436         mOffer = null;
437     }
438 
439     /**
440      * Quit the DhcpStateMachine.
441      *
442      * @hide
443      */
444     @Override
doQuit()445     public void doQuit() {
446         Log.d(TAG, "doQuit");
447         quit();
448     }
449 
onQuitting()450     protected void onQuitting() {
451         Log.d(TAG, "onQuitting");
452         mController.sendMessage(DhcpStateMachine.CMD_ON_QUIT);
453     }
454 
maybeLog(String msg)455     private void maybeLog(String msg) {
456         if (DBG) Log.d(TAG, msg);
457     }
458 
459     abstract class LoggingState extends State {
enter()460         public void enter() {
461             if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
462         }
463 
messageName(int what)464         private String messageName(int what) {
465             switch (what) {
466                 case DhcpStateMachine.CMD_START_DHCP:
467                     return "CMD_START_DHCP";
468                 case DhcpStateMachine.CMD_STOP_DHCP:
469                     return "CMD_STOP_DHCP";
470                 case DhcpStateMachine.CMD_RENEW_DHCP:
471                     return "CMD_RENEW_DHCP";
472                 case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
473                     return "CMD_PRE_DHCP_ACTION";
474                 case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
475                     return "CMD_PRE_DHCP_ACTION_COMPLETE";
476                 case DhcpStateMachine.CMD_POST_DHCP_ACTION:
477                     return "CMD_POST_DHCP_ACTION";
478                 case CMD_KICK:
479                     return "CMD_KICK";
480                 case CMD_RECEIVED_PACKET:
481                     return "CMD_RECEIVED_PACKET";
482                 case CMD_TIMEOUT:
483                     return "CMD_TIMEOUT";
484                 case CMD_ONESHOT_TIMEOUT:
485                     return "CMD_ONESHOT_TIMEOUT";
486                 default:
487                     return Integer.toString(what);
488             }
489         }
490 
messageToString(Message message)491         private String messageToString(Message message) {
492             long now = SystemClock.uptimeMillis();
493             StringBuilder b = new StringBuilder(" ");
494             TimeUtils.formatDuration(message.getWhen() - now, b);
495             b.append(" ").append(messageName(message.what))
496                     .append(" ").append(message.arg1)
497                     .append(" ").append(message.arg2)
498                     .append(" ").append(message.obj);
499             return b.toString();
500         }
501 
502         @Override
processMessage(Message message)503         public boolean processMessage(Message message) {
504             if (MSG_DBG) {
505                 Log.d(TAG, getName() + messageToString(message));
506             }
507             return NOT_HANDLED;
508         }
509     }
510 
511     // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
512     // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
513     abstract class WaitBeforeOtherState extends LoggingState {
514         protected State mOtherState;
515 
516         @Override
enter()517         public void enter() {
518             super.enter();
519             mController.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION);
520         }
521 
522         @Override
processMessage(Message message)523         public boolean processMessage(Message message) {
524             super.processMessage(message);
525             switch (message.what) {
526                 case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
527                     transitionTo(mOtherState);
528                     return HANDLED;
529                 default:
530                     return NOT_HANDLED;
531             }
532         }
533     }
534 
535     // The one-shot timeout is used to implement the timeout for CMD_START_DHCP. We can't use a
536     // state timeout to do this because obtaining an IP address involves passing through more than
537     // one state (specifically, it passes at least once through DhcpInitState and once through
538     // DhcpRequestingState). The one-shot timeout is created when CMD_START_DHCP is received, and is
539     // cancelled when exiting DhcpState (either due to a CMD_STOP_DHCP, or because of an error), or
540     // when we get an IP address (when entering DhcpBoundState). If it fires, we send ourselves
541     // CMD_ONESHOT_TIMEOUT and notify the caller that DHCP failed, but we take no other action. For
542     // example, if we're in DhcpInitState and sending DISCOVERs, we continue to do so.
543     //
544     // The one-shot timeout is not used for CMD_RENEW_DHCP because that is implemented using only
545     // one state, so we can just use the state timeout.
scheduleOneshotTimeout()546     private void scheduleOneshotTimeout() {
547         final long alarmTime = SystemClock.elapsedRealtime() + DHCP_TIMEOUT_MS;
548         mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
549                 mOneshotTimeoutIntent);
550     }
551 
cancelOneshotTimeout()552     private void cancelOneshotTimeout() {
553         mAlarmManager.cancel(mOneshotTimeoutIntent);
554     }
555 
556     class StoppedState extends LoggingState {
557         @Override
processMessage(Message message)558         public boolean processMessage(Message message) {
559             super.processMessage(message);
560             switch (message.what) {
561                 case DhcpStateMachine.CMD_START_DHCP:
562                     scheduleOneshotTimeout();
563                     if (mRegisteredForPreDhcpNotification) {
564                         transitionTo(mWaitBeforeStartState);
565                     } else {
566                         transitionTo(mDhcpInitState);
567                     }
568                     return HANDLED;
569                 default:
570                     return NOT_HANDLED;
571             }
572         }
573     }
574 
575     class WaitBeforeStartState extends WaitBeforeOtherState {
WaitBeforeStartState(State otherState)576         public WaitBeforeStartState(State otherState) {
577             super();
578             mOtherState = otherState;
579         }
580     }
581 
582     class WaitBeforeRenewalState extends WaitBeforeOtherState {
WaitBeforeRenewalState(State otherState)583         public WaitBeforeRenewalState(State otherState) {
584             super();
585             mOtherState = otherState;
586         }
587     }
588 
589     class DhcpState extends LoggingState {
590         @Override
enter()591         public void enter() {
592             super.enter();
593             clearDhcpState();
594             if (initInterface() && initSockets()) {
595                 mReceiveThread = new ReceiveThread();
596                 mReceiveThread.start();
597             } else {
598                 notifyFailure();
599                 transitionTo(mStoppedState);
600             }
601         }
602 
603         @Override
exit()604         public void exit() {
605             cancelOneshotTimeout();
606             if (mReceiveThread != null) {
607                 mReceiveThread.halt();  // Also closes sockets.
608                 mReceiveThread = null;
609             }
610             clearDhcpState();
611         }
612 
613         @Override
processMessage(Message message)614         public boolean processMessage(Message message) {
615             super.processMessage(message);
616             switch (message.what) {
617                 case DhcpStateMachine.CMD_STOP_DHCP:
618                     transitionTo(mStoppedState);
619                     return HANDLED;
620                 case CMD_ONESHOT_TIMEOUT:
621                     maybeLog("Timed out");
622                     notifyFailure();
623                     return HANDLED;
624                 default:
625                     return NOT_HANDLED;
626             }
627         }
628     }
629 
isValidPacket(DhcpPacket packet)630     public boolean isValidPacket(DhcpPacket packet) {
631         // TODO: check checksum.
632         int xid = packet.getTransactionId();
633         if (xid != mTransactionId) {
634             Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
635             return false;
636         }
637         if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
638             Log.d(TAG, "MAC addr mismatch: got " +
639                     HexDump.toHexString(packet.getClientMac()) + ", expected " +
640                     HexDump.toHexString(packet.getClientMac()));
641             return false;
642         }
643         return true;
644     }
645 
setDhcpLeaseExpiry(DhcpPacket packet)646     public void setDhcpLeaseExpiry(DhcpPacket packet) {
647         long leaseTimeMillis = packet.getLeaseTimeMillis();
648         mDhcpLeaseExpiry =
649                 (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
650     }
651 
652     /**
653      * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
654      * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
655      * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
656      * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
657      * state.
658      *
659      * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
660      * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
661      * sent by the receive thread. They may also set mTimeout and implement timeout.
662      */
663     abstract class PacketRetransmittingState extends LoggingState {
664 
665         private int mTimer;
666         protected int mTimeout = 0;
667 
668         @Override
enter()669         public void enter() {
670             super.enter();
671             initTimer();
672             maybeInitTimeout();
673             sendMessage(CMD_KICK);
674         }
675 
676         @Override
processMessage(Message message)677         public boolean processMessage(Message message) {
678             super.processMessage(message);
679             switch (message.what) {
680                 case CMD_KICK:
681                     sendPacket();
682                     scheduleKick();
683                     return HANDLED;
684                 case CMD_RECEIVED_PACKET:
685                     receivePacket((DhcpPacket) message.obj);
686                     return HANDLED;
687                 case CMD_TIMEOUT:
688                     timeout();
689                     return HANDLED;
690                 default:
691                     return NOT_HANDLED;
692             }
693         }
694 
exit()695         public void exit() {
696             mAlarmManager.cancel(mKickIntent);
697             mAlarmManager.cancel(mTimeoutIntent);
698         }
699 
sendPacket()700         abstract protected boolean sendPacket();
receivePacket(DhcpPacket packet)701         abstract protected void receivePacket(DhcpPacket packet);
timeout()702         protected void timeout() {}
703 
initTimer()704         protected void initTimer() {
705             mTimer = FIRST_TIMEOUT_MS;
706         }
707 
jitterTimer(int baseTimer)708         protected int jitterTimer(int baseTimer) {
709             int maxJitter = baseTimer / 10;
710             int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
711             return baseTimer + jitter;
712         }
713 
scheduleKick()714         protected void scheduleKick() {
715             long now = SystemClock.elapsedRealtime();
716             long timeout = jitterTimer(mTimer);
717             long alarmTime = now + timeout;
718             mAlarmManager.cancel(mKickIntent);
719             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mKickIntent);
720             mTimer *= 2;
721             if (mTimer > MAX_TIMEOUT_MS) {
722                 mTimer = MAX_TIMEOUT_MS;
723             }
724         }
725 
maybeInitTimeout()726         protected void maybeInitTimeout() {
727             if (mTimeout > 0) {
728                 long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
729                 mAlarmManager.setExact(
730                         AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mTimeoutIntent);
731             }
732         }
733     }
734 
735     class DhcpInitState extends PacketRetransmittingState {
DhcpInitState()736         public DhcpInitState() {
737             super();
738         }
739 
740         @Override
enter()741         public void enter() {
742             super.enter();
743             startNewTransaction();
744         }
745 
sendPacket()746         protected boolean sendPacket() {
747             return sendDiscoverPacket();
748         }
749 
receivePacket(DhcpPacket packet)750         protected void receivePacket(DhcpPacket packet) {
751             if (!isValidPacket(packet)) return;
752             if (!(packet instanceof DhcpOfferPacket)) return;
753             mOffer = packet.toDhcpResults();
754             if (mOffer != null) {
755                 Log.d(TAG, "Got pending lease: " + mOffer);
756                 transitionTo(mDhcpRequestingState);
757             }
758         }
759     }
760 
761     // Not implemented. We request the first offer we receive.
762     class DhcpSelectingState extends LoggingState {
763     }
764 
765     class DhcpRequestingState extends PacketRetransmittingState {
DhcpRequestingState()766         public DhcpRequestingState() {
767             super();
768             mTimeout = DHCP_TIMEOUT_MS / 2;
769         }
770 
sendPacket()771         protected boolean sendPacket() {
772             return sendRequestPacket(
773                     INADDR_ANY,                                    // ciaddr
774                     (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
775                     (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
776                     INADDR_BROADCAST);                             // packet destination address
777         }
778 
receivePacket(DhcpPacket packet)779         protected void receivePacket(DhcpPacket packet) {
780             if (!isValidPacket(packet)) return;
781             if ((packet instanceof DhcpAckPacket)) {
782                 DhcpResults results = packet.toDhcpResults();
783                 if (results != null) {
784                     mDhcpLease = results;
785                     mOffer = null;
786                     Log.d(TAG, "Confirmed lease: " + mDhcpLease);
787                     setDhcpLeaseExpiry(packet);
788                     transitionTo(mDhcpBoundState);
789                 }
790             } else if (packet instanceof DhcpNakPacket) {
791                 Log.d(TAG, "Received NAK, returning to INIT");
792                 mOffer = null;
793                 transitionTo(mDhcpInitState);
794             }
795         }
796 
797         @Override
timeout()798         protected void timeout() {
799             // After sending REQUESTs unsuccessfully for a while, go back to init.
800             transitionTo(mDhcpInitState);
801         }
802     }
803 
804     class DhcpHaveAddressState extends LoggingState {
805         @Override
enter()806         public void enter() {
807             super.enter();
808             if (setIpAddress(mDhcpLease.ipAddress)) {
809                 maybeLog("Configured IP address " + mDhcpLease.ipAddress);
810             } else {
811                 Log.e(TAG, "Failed to configure IP address " + mDhcpLease.ipAddress);
812                 notifyFailure();
813                 // There's likely no point in going into DhcpInitState here, we'll probably just
814                 // repeat the transaction, get the same IP address as before, and fail.
815                 transitionTo(mStoppedState);
816             }
817         }
818 
819         @Override
exit()820         public void exit() {
821             maybeLog("Clearing IP address");
822             setIpAddress(new LinkAddress("0.0.0.0/0"));
823         }
824     }
825 
826     class DhcpBoundState extends LoggingState {
827         @Override
enter()828         public void enter() {
829             super.enter();
830             cancelOneshotTimeout();
831             notifySuccess();
832             // TODO: DhcpStateMachine only supports renewing at 50% of the lease time, and does not
833             // support rebinding. Once the legacy DHCP client is gone, fix this.
834             scheduleRenew();
835         }
836 
837         @Override
processMessage(Message message)838         public boolean processMessage(Message message) {
839             super.processMessage(message);
840             switch (message.what) {
841                 case DhcpStateMachine.CMD_RENEW_DHCP:
842                     if (mRegisteredForPreDhcpNotification) {
843                         transitionTo(mWaitBeforeRenewalState);
844                     } else {
845                         transitionTo(mDhcpRenewingState);
846                     }
847                     return HANDLED;
848                 default:
849                     return NOT_HANDLED;
850             }
851         }
852     }
853 
854     class DhcpRenewingState extends PacketRetransmittingState {
DhcpRenewingState()855         public DhcpRenewingState() {
856             super();
857             mTimeout = DHCP_TIMEOUT_MS;
858         }
859 
860         @Override
enter()861         public void enter() {
862             super.enter();
863             startNewTransaction();
864         }
865 
sendPacket()866         protected boolean sendPacket() {
867             return sendRequestPacket(
868                     (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
869                     INADDR_ANY,                                        // DHCP_REQUESTED_IP
870                     INADDR_ANY,                                        // DHCP_SERVER_IDENTIFIER
871                     (Inet4Address) mDhcpLease.serverAddress);          // packet destination address
872         }
873 
receivePacket(DhcpPacket packet)874         protected void receivePacket(DhcpPacket packet) {
875             if (!isValidPacket(packet)) return;
876             if ((packet instanceof DhcpAckPacket)) {
877                 setDhcpLeaseExpiry(packet);
878                 transitionTo(mDhcpBoundState);
879             } else if (packet instanceof DhcpNakPacket) {
880                 transitionTo(mDhcpInitState);
881             }
882         }
883 
884         @Override
timeout()885         protected void timeout() {
886             transitionTo(mDhcpInitState);
887             sendMessage(CMD_ONESHOT_TIMEOUT);
888         }
889     }
890 
891     // Not implemented. DhcpStateMachine does not implement it either.
892     class DhcpRebindingState extends LoggingState {
893     }
894 
895     class DhcpInitRebootState extends LoggingState {
896     }
897 
898     class DhcpRebootingState extends LoggingState {
899     }
900 }
901