DhcpClient.java revision adacedb1a8b10e0e29c7c223251069c8e6ef564e
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.metrics.DhcpClientEvent;
34import android.net.metrics.DhcpErrorEvent;
35import android.os.Message;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.os.SystemClock;
39import android.system.ErrnoException;
40import android.system.Os;
41import android.system.PacketSocketAddress;
42import android.util.Log;
43import android.util.SparseArray;
44import android.util.TimeUtils;
45
46import java.io.FileDescriptor;
47import java.io.IOException;
48import java.lang.Thread;
49import java.net.Inet4Address;
50import java.net.NetworkInterface;
51import java.net.SocketException;
52import java.nio.ByteBuffer;
53import java.util.Arrays;
54import java.util.Random;
55
56import libcore.io.IoBridge;
57
58import static android.system.OsConstants.*;
59import 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 */
84public class DhcpClient extends StateMachine {
85
86    private static final String TAG = "DhcpClient";
87    private static final boolean DBG = false;
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 notification */
129    public static final int DHCP_SUCCESS = 1;
130    public static final int DHCP_FAILURE = 2;
131
132    // 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
139    // For message logging.
140    private static final Class[] sMessageClasses = { DhcpClient.class };
141    private static final SparseArray<String> sMessageNames =
142            MessageUtils.findMessageNames(sMessageClasses);
143
144    // DHCP parameters that we request.
145    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
146        DHCP_SUBNET_MASK,
147        DHCP_ROUTER,
148        DHCP_DNS_SERVER,
149        DHCP_DOMAIN_NAME,
150        DHCP_MTU,
151        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
152        DHCP_LEASE_TIME,
153        DHCP_RENEWAL_TIME,
154        DHCP_REBINDING_TIME,
155        DHCP_VENDOR_INFO,
156    };
157
158    // DHCP flag that means "yes, we support unicast."
159    private static final boolean DO_UNICAST   = false;
160
161    // System services / libraries we use.
162    private final Context mContext;
163    private final Random mRandom;
164
165    // Sockets.
166    // - We use a packet socket to receive, because servers send us packets bound for IP addresses
167    //   which we have not yet configured, and the kernel protocol stack drops these.
168    // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
169    //   be off-link as well as on-link).
170    private FileDescriptor mPacketSock;
171    private FileDescriptor mUdpSock;
172    private ReceiveThread mReceiveThread;
173
174    // State variables.
175    private final StateMachine mController;
176    private final WakeupMessage mKickAlarm;
177    private final WakeupMessage mTimeoutAlarm;
178    private final WakeupMessage mRenewAlarm;
179    private final String mIfaceName;
180
181    private boolean mRegisteredForPreDhcpNotification;
182    private NetworkInterface mIface;
183    private byte[] mHwAddr;
184    private PacketSocketAddress mInterfaceBroadcastAddr;
185    private int mTransactionId;
186    private long mTransactionStartMillis;
187    private DhcpResults mDhcpLease;
188    private long mDhcpLeaseExpiry;
189    private DhcpResults mOffer;
190
191    // States.
192    private State mStoppedState = new StoppedState();
193    private State mDhcpState = new DhcpState();
194    private State mDhcpInitState = new DhcpInitState();
195    private State mDhcpSelectingState = new DhcpSelectingState();
196    private State mDhcpRequestingState = new DhcpRequestingState();
197    private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
198    private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
199    private State mDhcpBoundState = new DhcpBoundState();
200    private State mDhcpRenewingState = new DhcpRenewingState();
201    private State mDhcpRebindingState = new DhcpRebindingState();
202    private State mDhcpInitRebootState = new DhcpInitRebootState();
203    private State mDhcpRebootingState = new DhcpRebootingState();
204    private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
205    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
206
207    private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
208        cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
209        return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
210    }
211
212    private DhcpClient(Context context, StateMachine controller, String iface) {
213        super(TAG);
214
215        mContext = context;
216        mController = controller;
217        mIfaceName = iface;
218
219        addState(mStoppedState);
220        addState(mDhcpState);
221            addState(mDhcpInitState, mDhcpState);
222            addState(mWaitBeforeStartState, mDhcpState);
223            addState(mDhcpSelectingState, mDhcpState);
224            addState(mDhcpRequestingState, mDhcpState);
225            addState(mDhcpHaveLeaseState, mDhcpState);
226                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
227                addState(mDhcpBoundState, mDhcpHaveLeaseState);
228                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
229                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
230                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
231            addState(mDhcpInitRebootState, mDhcpState);
232            addState(mDhcpRebootingState, mDhcpState);
233
234        setInitialState(mStoppedState);
235
236        mRandom = new Random();
237
238        // Used to schedule packet retransmissions.
239        mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
240        // Used to time out PacketRetransmittingStates.
241        mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
242        // Used to schedule DHCP renews.
243        mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
244    }
245
246    public void registerForPreDhcpNotification() {
247        mRegisteredForPreDhcpNotification = true;
248    }
249
250    public static DhcpClient makeDhcpClient(
251            Context context, StateMachine controller, String intf) {
252        DhcpClient client = new DhcpClient(context, controller, intf);
253        client.start();
254        return client;
255    }
256
257    private boolean initInterface() {
258        try {
259            mIface = NetworkInterface.getByName(mIfaceName);
260            mHwAddr = mIface.getHardwareAddress();
261            mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(),
262                    DhcpPacket.ETHER_BROADCAST);
263            return true;
264        } catch(SocketException | NullPointerException e) {
265            Log.e(TAG, "Can't determine ifindex or MAC address for " + mIfaceName, e);
266            return false;
267        }
268    }
269
270    private void startNewTransaction() {
271        mTransactionId = mRandom.nextInt();
272        mTransactionStartMillis = SystemClock.elapsedRealtime();
273    }
274
275    private boolean initSockets() {
276        try {
277            mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
278            PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
279            Os.bind(mPacketSock, addr);
280            NetworkUtils.attachDhcpFilter(mPacketSock);
281        } catch(SocketException|ErrnoException e) {
282            Log.e(TAG, "Error creating packet socket", e);
283            return false;
284        }
285        try {
286            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
287            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
288            Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
289            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
290            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
291            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
292            NetworkUtils.protectFromVpn(mUdpSock);
293        } catch(SocketException|ErrnoException e) {
294            Log.e(TAG, "Error creating UDP socket", e);
295            return false;
296        }
297        return true;
298    }
299
300    private boolean connectUdpSock(Inet4Address to) {
301        try {
302            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
303            return true;
304        } catch (SocketException|ErrnoException e) {
305            Log.e(TAG, "Error connecting UDP socket", e);
306            return false;
307        }
308    }
309
310    private static void closeQuietly(FileDescriptor fd) {
311        try {
312            IoBridge.closeAndSignalBlockedThreads(fd);
313        } catch (IOException ignored) {}
314    }
315
316    private void closeSockets() {
317        closeQuietly(mUdpSock);
318        closeQuietly(mPacketSock);
319    }
320
321    class ReceiveThread extends Thread {
322
323        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
324        private volatile boolean mStopped = false;
325
326        public void halt() {
327            mStopped = true;
328            closeSockets();  // Interrupts the read() call the thread is blocked in.
329        }
330
331        @Override
332        public void run() {
333            if (DBG) Log.d(TAG, "Receive thread started");
334            while (!mStopped) {
335                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
336                try {
337                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
338                    DhcpPacket packet = null;
339                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
340                    if (DBG) Log.d(TAG, "Received packet: " + packet);
341                    sendMessage(CMD_RECEIVED_PACKET, packet);
342                } catch (IOException|ErrnoException e) {
343                    if (!mStopped) {
344                        Log.e(TAG, "Read error", e);
345                        DhcpErrorEvent.logReceiveError(mIfaceName);
346                    }
347                } catch (DhcpPacket.ParseException e) {
348                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
349                    if (PACKET_DBG) {
350                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
351                    }
352                    DhcpErrorEvent.logParseError(mIfaceName, e.errorCode);
353                }
354            }
355            if (DBG) Log.d(TAG, "Receive thread stopped");
356        }
357    }
358
359    private short getSecs() {
360        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
361    }
362
363    private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) {
364        try {
365            if (to.equals(INADDR_BROADCAST)) {
366                if (DBG) Log.d(TAG, "Broadcasting " + description);
367                Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
368            } else {
369                // It's safe to call getpeername here, because we only send unicast packets if we
370                // have an IP address, and we connect the UDP socket before
371                // ConfiguringInterfaceState#exit.
372                if (DBG) Log.d(TAG, "Unicasting " + description + " to " + Os.getpeername(mUdpSock));
373                Os.write(mUdpSock, buf);
374            }
375        } catch(ErrnoException|IOException e) {
376            Log.e(TAG, "Can't send packet: ", e);
377            return false;
378        }
379        return true;
380    }
381
382    private boolean sendDiscoverPacket() {
383        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
384                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
385                DO_UNICAST, REQUESTED_PARAMS);
386        return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST);
387    }
388
389    private boolean sendRequestPacket(
390            Inet4Address clientAddress, Inet4Address requestedAddress,
391            Inet4Address serverAddress, Inet4Address to) {
392        // TODO: should we use the transaction ID from the server?
393        int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
394
395        ByteBuffer packet = DhcpPacket.buildRequestPacket(
396                encap, mTransactionId, getSecs(), clientAddress,
397                DO_UNICAST, mHwAddr, requestedAddress,
398                serverAddress, REQUESTED_PARAMS, null);
399        String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null;
400        String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
401                             " request=" + requestedAddress.getHostAddress() +
402                             " serverid=" + serverStr;
403        return transmitPacket(packet, description, to);
404    }
405
406    private void scheduleRenew() {
407        if (mDhcpLeaseExpiry != 0) {
408            long now = SystemClock.elapsedRealtime();
409            long alarmTime = (now + mDhcpLeaseExpiry) / 2;
410            mRenewAlarm.schedule(alarmTime);
411            Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s");
412        } else {
413            Log.d(TAG, "Infinite lease, no renewal needed");
414        }
415    }
416
417    private void notifySuccess() {
418        mController.sendMessage(
419                CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
420    }
421
422    private void notifyFailure() {
423        mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null);
424    }
425
426    private void clearDhcpState() {
427        mDhcpLease = null;
428        mDhcpLeaseExpiry = 0;
429        mOffer = null;
430    }
431
432    /**
433     * Quit the DhcpStateMachine.
434     *
435     * @hide
436     */
437    public void doQuit() {
438        Log.d(TAG, "doQuit");
439        quit();
440    }
441
442    @Override
443    protected void onQuitting() {
444        Log.d(TAG, "onQuitting");
445        mController.sendMessage(CMD_ON_QUIT);
446    }
447
448    abstract class LoggingState extends State {
449        @Override
450        public void enter() {
451            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
452            DhcpClientEvent.logStateEvent(mIfaceName, getName());
453        }
454
455        private String messageName(int what) {
456            return sMessageNames.get(what, Integer.toString(what));
457        }
458
459        private String messageToString(Message message) {
460            long now = SystemClock.uptimeMillis();
461            StringBuilder b = new StringBuilder(" ");
462            TimeUtils.formatDuration(message.getWhen() - now, b);
463            b.append(" ").append(messageName(message.what))
464                    .append(" ").append(message.arg1)
465                    .append(" ").append(message.arg2)
466                    .append(" ").append(message.obj);
467            return b.toString();
468        }
469
470        @Override
471        public boolean processMessage(Message message) {
472            if (MSG_DBG) {
473                Log.d(TAG, getName() + messageToString(message));
474            }
475            return NOT_HANDLED;
476        }
477    }
478
479    // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
480    // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
481    abstract class WaitBeforeOtherState extends LoggingState {
482        protected State mOtherState;
483
484        @Override
485        public void enter() {
486            super.enter();
487            mController.sendMessage(CMD_PRE_DHCP_ACTION);
488        }
489
490        @Override
491        public boolean processMessage(Message message) {
492            super.processMessage(message);
493            switch (message.what) {
494                case CMD_PRE_DHCP_ACTION_COMPLETE:
495                    transitionTo(mOtherState);
496                    return HANDLED;
497                default:
498                    return NOT_HANDLED;
499            }
500        }
501    }
502
503    class StoppedState extends LoggingState {
504        @Override
505        public boolean processMessage(Message message) {
506            super.processMessage(message);
507            switch (message.what) {
508                case CMD_START_DHCP:
509                    if (mRegisteredForPreDhcpNotification) {
510                        transitionTo(mWaitBeforeStartState);
511                    } else {
512                        transitionTo(mDhcpInitState);
513                    }
514                    return HANDLED;
515                default:
516                    return NOT_HANDLED;
517            }
518        }
519    }
520
521    class WaitBeforeStartState extends WaitBeforeOtherState {
522        public WaitBeforeStartState(State otherState) {
523            super();
524            mOtherState = otherState;
525        }
526    }
527
528    class WaitBeforeRenewalState extends WaitBeforeOtherState {
529        public WaitBeforeRenewalState(State otherState) {
530            super();
531            mOtherState = otherState;
532        }
533    }
534
535    class DhcpState extends LoggingState {
536        @Override
537        public void enter() {
538            super.enter();
539            clearDhcpState();
540            if (initInterface() && initSockets()) {
541                mReceiveThread = new ReceiveThread();
542                mReceiveThread.start();
543            } else {
544                notifyFailure();
545                transitionTo(mStoppedState);
546            }
547        }
548
549        @Override
550        public void exit() {
551            if (mReceiveThread != null) {
552                mReceiveThread.halt();  // Also closes sockets.
553                mReceiveThread = null;
554            }
555            clearDhcpState();
556        }
557
558        @Override
559        public boolean processMessage(Message message) {
560            super.processMessage(message);
561            switch (message.what) {
562                case CMD_STOP_DHCP:
563                    transitionTo(mStoppedState);
564                    return HANDLED;
565                default:
566                    return NOT_HANDLED;
567            }
568        }
569    }
570
571    public boolean isValidPacket(DhcpPacket packet) {
572        // TODO: check checksum.
573        int xid = packet.getTransactionId();
574        if (xid != mTransactionId) {
575            Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
576            return false;
577        }
578        if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
579            Log.d(TAG, "MAC addr mismatch: got " +
580                    HexDump.toHexString(packet.getClientMac()) + ", expected " +
581                    HexDump.toHexString(packet.getClientMac()));
582            return false;
583        }
584        return true;
585    }
586
587    public void setDhcpLeaseExpiry(DhcpPacket packet) {
588        long leaseTimeMillis = packet.getLeaseTimeMillis();
589        mDhcpLeaseExpiry =
590                (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
591    }
592
593    /**
594     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
595     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
596     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
597     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
598     * state.
599     *
600     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
601     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
602     * sent by the receive thread. They may also set mTimeout and implement timeout.
603     */
604    abstract class PacketRetransmittingState extends LoggingState {
605
606        private int mTimer;
607        protected int mTimeout = 0;
608
609        @Override
610        public void enter() {
611            super.enter();
612            initTimer();
613            maybeInitTimeout();
614            sendMessage(CMD_KICK);
615        }
616
617        @Override
618        public boolean processMessage(Message message) {
619            super.processMessage(message);
620            switch (message.what) {
621                case CMD_KICK:
622                    sendPacket();
623                    scheduleKick();
624                    return HANDLED;
625                case CMD_RECEIVED_PACKET:
626                    receivePacket((DhcpPacket) message.obj);
627                    return HANDLED;
628                case CMD_TIMEOUT:
629                    timeout();
630                    return HANDLED;
631                default:
632                    return NOT_HANDLED;
633            }
634        }
635
636        public void exit() {
637            mKickAlarm.cancel();
638            mTimeoutAlarm.cancel();
639        }
640
641        abstract protected boolean sendPacket();
642        abstract protected void receivePacket(DhcpPacket packet);
643        protected void timeout() {}
644
645        protected void initTimer() {
646            mTimer = FIRST_TIMEOUT_MS;
647        }
648
649        protected int jitterTimer(int baseTimer) {
650            int maxJitter = baseTimer / 10;
651            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
652            return baseTimer + jitter;
653        }
654
655        protected void scheduleKick() {
656            long now = SystemClock.elapsedRealtime();
657            long timeout = jitterTimer(mTimer);
658            long alarmTime = now + timeout;
659            mKickAlarm.schedule(alarmTime);
660            mTimer *= 2;
661            if (mTimer > MAX_TIMEOUT_MS) {
662                mTimer = MAX_TIMEOUT_MS;
663            }
664        }
665
666        protected void maybeInitTimeout() {
667            if (mTimeout > 0) {
668                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
669                mTimeoutAlarm.schedule(alarmTime);
670            }
671        }
672    }
673
674    class DhcpInitState extends PacketRetransmittingState {
675        public DhcpInitState() {
676            super();
677        }
678
679        @Override
680        public void enter() {
681            super.enter();
682            startNewTransaction();
683        }
684
685        protected boolean sendPacket() {
686            return sendDiscoverPacket();
687        }
688
689        protected void receivePacket(DhcpPacket packet) {
690            if (!isValidPacket(packet)) return;
691            if (!(packet instanceof DhcpOfferPacket)) return;
692            mOffer = packet.toDhcpResults();
693            if (mOffer != null) {
694                Log.d(TAG, "Got pending lease: " + mOffer);
695                transitionTo(mDhcpRequestingState);
696            }
697        }
698    }
699
700    // Not implemented. We request the first offer we receive.
701    class DhcpSelectingState extends LoggingState {
702    }
703
704    class DhcpRequestingState extends PacketRetransmittingState {
705        public DhcpRequestingState() {
706            super();
707            mTimeout = DHCP_TIMEOUT_MS / 2;
708        }
709
710        protected boolean sendPacket() {
711            return sendRequestPacket(
712                    INADDR_ANY,                                    // ciaddr
713                    (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
714                    (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
715                    INADDR_BROADCAST);                             // packet destination address
716        }
717
718        protected void receivePacket(DhcpPacket packet) {
719            if (!isValidPacket(packet)) return;
720            if ((packet instanceof DhcpAckPacket)) {
721                DhcpResults results = packet.toDhcpResults();
722                if (results != null) {
723                    mDhcpLease = results;
724                    mOffer = null;
725                    Log.d(TAG, "Confirmed lease: " + mDhcpLease);
726                    setDhcpLeaseExpiry(packet);
727                    notifySuccess();
728                    transitionTo(mConfiguringInterfaceState);
729                }
730            } else if (packet instanceof DhcpNakPacket) {
731                // TODO: Wait a while before returning into INIT state.
732                Log.d(TAG, "Received NAK, returning to INIT");
733                mOffer = null;
734                transitionTo(mDhcpInitState);
735            }
736        }
737
738        @Override
739        protected void timeout() {
740            // After sending REQUESTs unsuccessfully for a while, go back to init.
741            transitionTo(mDhcpInitState);
742        }
743    }
744
745    class DhcpHaveLeaseState extends LoggingState {
746        @Override
747        public void enter() {
748            super.enter();
749        }
750
751        @Override
752        public void exit() {
753            // Tell IpManager to clear the IPv4 address. There is no need to
754            // wait for confirmation since any subsequent packets are sent from
755            // INADDR_ANY anyway (DISCOVER, REQUEST).
756            mController.sendMessage(CMD_CLEAR_LINKADDRESS);
757        }
758    }
759
760    class ConfiguringInterfaceState extends LoggingState {
761        @Override
762        public void enter() {
763            super.enter();
764            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
765        }
766
767        @Override
768        public boolean processMessage(Message message) {
769            super.processMessage(message);
770            switch (message.what) {
771                case EVENT_LINKADDRESS_CONFIGURED:
772                    if (mDhcpLease.serverAddress != null &&
773                            !connectUdpSock(mDhcpLease.serverAddress)) {
774                        // There's likely no point in going into DhcpInitState here, we'll probably
775                        // just repeat the transaction, get the same IP address as before, and fail.
776                        //
777                        // NOTE: It is observed that connectUdpSock() basically never fails, due to
778                        // SO_BINDTODEVICE. Examining the local socket address shows it will happily
779                        // return an IPv4 address from another interface, or even return "0.0.0.0".
780                        //
781                        // TODO: Consider deleting this check, following testing on several kernels.
782                        notifyFailure();
783                        transitionTo(mStoppedState);
784                    } else {
785                        transitionTo(mDhcpBoundState);
786                    }
787                    return HANDLED;
788                default:
789                    return NOT_HANDLED;
790            }
791        }
792    }
793
794    class DhcpBoundState extends LoggingState {
795        @Override
796        public void enter() {
797            super.enter();
798            // TODO: DhcpStateMachine only supported renewing at 50% of the lease time,
799            // and did not support rebinding. Now that the legacy DHCP client is gone, fix this.
800            scheduleRenew();
801        }
802
803        @Override
804        public boolean processMessage(Message message) {
805            super.processMessage(message);
806            switch (message.what) {
807                case CMD_RENEW_DHCP:
808                    if (mRegisteredForPreDhcpNotification) {
809                        transitionTo(mWaitBeforeRenewalState);
810                    } else {
811                        transitionTo(mDhcpRenewingState);
812                    }
813                    return HANDLED;
814                default:
815                    return NOT_HANDLED;
816            }
817        }
818
819        @Override
820        public void exit() {
821            mRenewAlarm.cancel();
822        }
823    }
824
825    class DhcpRenewingState extends PacketRetransmittingState {
826        public DhcpRenewingState() {
827            super();
828            mTimeout = DHCP_TIMEOUT_MS;
829        }
830
831        @Override
832        public void enter() {
833            super.enter();
834            startNewTransaction();
835        }
836
837        protected boolean sendPacket() {
838            // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
839            // http://b/25343517 . Try to make things work anyway by using broadcast renews.
840            Inet4Address to = (mDhcpLease.serverAddress != null) ?
841                    mDhcpLease.serverAddress : INADDR_BROADCAST;
842            return sendRequestPacket(
843                    (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
844                    INADDR_ANY,                                        // DHCP_REQUESTED_IP
845                    null,                                              // DHCP_SERVER_IDENTIFIER
846                    to);                                               // packet destination address
847        }
848
849        protected void receivePacket(DhcpPacket packet) {
850            if (!isValidPacket(packet)) return;
851            if ((packet instanceof DhcpAckPacket)) {
852                setDhcpLeaseExpiry(packet);
853                notifySuccess();
854                transitionTo(mDhcpBoundState);
855            } else if (packet instanceof DhcpNakPacket) {
856                transitionTo(mDhcpInitState);
857            }
858        }
859
860        @Override
861        protected void timeout() {
862            transitionTo(mDhcpInitState);
863            notifyFailure();
864        }
865    }
866
867    // Not implemented. DhcpStateMachine did not implement it either.
868    class DhcpRebindingState extends LoggingState {
869    }
870
871    class DhcpInitRebootState extends LoggingState {
872    }
873
874    class DhcpRebootingState extends LoggingState {
875    }
876}
877