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