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
17package android.net.dhcp;
18
19import com.android.internal.util.HexDump;
20import com.android.internal.util.Protocol;
21import com.android.internal.util.State;
22import com.android.internal.util.MessageUtils;
23import com.android.internal.util.StateMachine;
24import com.android.internal.util.WakeupMessage;
25
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.net.DhcpResults;
30import android.net.InterfaceConfiguration;
31import android.net.LinkAddress;
32import android.net.NetworkUtils;
33import android.net.TrafficStats;
34import android.net.metrics.IpConnectivityLog;
35import android.net.metrics.DhcpClientEvent;
36import android.net.metrics.DhcpErrorEvent;
37import android.os.Message;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.SystemClock;
41import android.system.ErrnoException;
42import android.system.Os;
43import android.system.PacketSocketAddress;
44import android.util.EventLog;
45import android.util.Log;
46import android.util.SparseArray;
47import android.util.TimeUtils;
48
49import java.io.FileDescriptor;
50import java.io.IOException;
51import java.lang.Thread;
52import java.net.Inet4Address;
53import java.net.NetworkInterface;
54import java.net.SocketException;
55import java.nio.ByteBuffer;
56import java.util.Arrays;
57import java.util.Random;
58
59import libcore.io.IoBridge;
60
61import static android.system.OsConstants.*;
62import 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 */
87public 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
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
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
260    public void registerForPreDhcpNotification() {
261        mRegisteredForPreDhcpNotification = true;
262    }
263
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
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
284    private void startNewTransaction() {
285        mTransactionId = mRandom.nextInt();
286        mTransactionStartMillis = SystemClock.elapsedRealtime();
287    }
288
289    private boolean initSockets() {
290        return initPacketSocket() && initUdpSocket();
291    }
292
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
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
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
335    private static void closeQuietly(FileDescriptor fd) {
336        try {
337            IoBridge.closeAndSignalBlockedThreads(fd);
338        } catch (IOException ignored) {}
339    }
340
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
351        public void halt() {
352            mStopped = true;
353            closeSockets();  // Interrupts the read() call the thread is blocked in.
354        }
355
356        @Override
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
391    private short getSecs() {
392        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
393    }
394
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
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
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
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
472    private void notifySuccess() {
473        mController.sendMessage(
474                CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
475    }
476
477    private void notifyFailure() {
478        mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
479    }
480
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
488    private void clearDhcpState() {
489        mDhcpLease = null;
490        mDhcpLeaseExpiry = 0;
491        mOffer = null;
492    }
493
494    /**
495     * Quit the DhcpStateMachine.
496     *
497     * @hide
498     */
499    public void doQuit() {
500        Log.d(TAG, "doQuit");
501        quit();
502    }
503
504    @Override
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
514        public void enter() {
515            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
516            mEnterTimeMs = SystemClock.elapsedRealtime();
517        }
518
519        @Override
520        public void exit() {
521            long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
522            logState(getName(), (int) durationMs);
523        }
524
525        private String messageName(int what) {
526            return sMessageNames.get(what, Integer.toString(what));
527        }
528
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
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
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
562        public void enter() {
563            super.enter();
564            mController.sendMessage(CMD_PRE_DHCP_ACTION);
565        }
566
567        @Override
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
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 {
598        public WaitBeforeStartState(State otherState) {
599            super();
600            mOtherState = otherState;
601        }
602    }
603
604    class WaitBeforeRenewalState extends WaitBeforeOtherState {
605        public WaitBeforeRenewalState(State otherState) {
606            super();
607            mOtherState = otherState;
608        }
609    }
610
611    class DhcpState extends State {
612        @Override
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
625        public void exit() {
626            if (mReceiveThread != null) {
627                mReceiveThread.halt();  // Also closes sockets.
628                mReceiveThread = null;
629            }
630            clearDhcpState();
631        }
632
633        @Override
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
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
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
685        public void enter() {
686            super.enter();
687            initTimer();
688            maybeInitTimeout();
689            sendMessage(CMD_KICK);
690        }
691
692        @Override
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
712        public void exit() {
713            super.exit();
714            mKickAlarm.cancel();
715            mTimeoutAlarm.cancel();
716        }
717
718        abstract protected boolean sendPacket();
719        abstract protected void receivePacket(DhcpPacket packet);
720        protected void timeout() {}
721
722        protected void initTimer() {
723            mTimer = FIRST_TIMEOUT_MS;
724        }
725
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
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
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 {
752        public DhcpInitState() {
753            super();
754        }
755
756        @Override
757        public void enter() {
758            super.enter();
759            startNewTransaction();
760            mLastInitEnterTime = SystemClock.elapsedRealtime();
761        }
762
763        protected boolean sendPacket() {
764            return sendDiscoverPacket();
765        }
766
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 {
783        public DhcpRequestingState() {
784            mTimeout = DHCP_TIMEOUT_MS / 2;
785        }
786
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
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
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
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
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
849        public void enter() {
850            super.enter();
851            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
852        }
853
854        @Override
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
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
889        public void exit() {
890            super.exit();
891            mLastBoundExitTime = SystemClock.elapsedRealtime();
892        }
893
894        @Override
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
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
924        public void enter() {
925            super.enter();
926            startNewTransaction();
927        }
928
929        abstract protected Inet4Address packetDestination();
930
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
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 {
967        public DhcpRenewingState() {
968            mLeaseMsg = "Renewed";
969        }
970
971        @Override
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
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 {
996        public DhcpRebindingState() {
997            mLeaseMsg = "Rebound";
998        }
999
1000        @Override
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
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
1025    private void logError(int errorCode) {
1026        mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
1027    }
1028
1029    private void logState(String name, int durationMs) {
1030        mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
1031    }
1032}
1033