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