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