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.StateMachine;
23
24import android.app.AlarmManager;
25import android.app.PendingIntent;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.net.DhcpResults;
31import android.net.BaseDhcpStateMachine;
32import android.net.DhcpStateMachine;
33import android.net.InterfaceConfiguration;
34import android.net.LinkAddress;
35import android.net.NetworkUtils;
36import android.os.IBinder;
37import android.os.INetworkManagementService;
38import android.os.Message;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.SystemClock;
43import android.system.ErrnoException;
44import android.system.Os;
45import android.system.PacketSocketAddress;
46import android.util.Log;
47import android.util.TimeUtils;
48
49import java.io.FileDescriptor;
50import java.io.IOException;
51import java.lang.Thread;
52import java.net.Inet4Address;
53import java.net.InetSocketAddress;
54import java.net.NetworkInterface;
55import java.net.SocketException;
56import java.nio.BufferUnderflowException;
57import java.nio.ByteBuffer;
58import java.util.Arrays;
59import java.util.Random;
60
61import libcore.io.IoBridge;
62
63import static android.system.OsConstants.*;
64import static android.net.dhcp.DhcpPacket.*;
65
66/**
67 * A DHCPv4 client.
68 *
69 * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
70 * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
71 *
72 * TODO:
73 *
74 * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
75 * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
76 *   do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
77 *   given SSID), it requests the last-leased IP address on the same interface, causing a delay if
78 *   the server NAKs or a timeout if it doesn't.
79 *
80 * Known differences from current behaviour:
81 *
82 * - Does not request the "static routes" option.
83 * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
84 * - Requests the "broadcast" option, but does nothing with it.
85 * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
86 *
87 * @hide
88 */
89public class DhcpClient extends BaseDhcpStateMachine {
90
91    private static final String TAG = "DhcpClient";
92    private static final boolean DBG = true;
93    private static final boolean STATE_DBG = false;
94    private static final boolean MSG_DBG = false;
95    private static final boolean PACKET_DBG = true;
96
97    // Timers and timeouts.
98    private static final int SECONDS = 1000;
99    private static final int FIRST_TIMEOUT_MS   =   2 * SECONDS;
100    private static final int MAX_TIMEOUT_MS     = 128 * SECONDS;
101
102    // This is not strictly needed, since the client is asynchronous and implements exponential
103    // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
104    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
105    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
106    private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
107
108    // Messages.
109    private static final int BASE                 = Protocol.BASE_DHCP + 100;
110    private static final int CMD_KICK             = BASE + 1;
111    private static final int CMD_RECEIVED_PACKET  = BASE + 2;
112    private static final int CMD_TIMEOUT          = BASE + 3;
113    private static final int CMD_ONESHOT_TIMEOUT  = BASE + 4;
114
115    // DHCP parameters that we request.
116    private static final byte[] REQUESTED_PARAMS = new byte[] {
117        DHCP_SUBNET_MASK,
118        DHCP_ROUTER,
119        DHCP_DNS_SERVER,
120        DHCP_DOMAIN_NAME,
121        DHCP_MTU,
122        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
123        DHCP_LEASE_TIME,
124        DHCP_RENEWAL_TIME,
125        DHCP_REBINDING_TIME,
126    };
127
128    // DHCP flag that means "yes, we support unicast."
129    private static final boolean DO_UNICAST   = false;
130
131    // System services / libraries we use.
132    private final Context mContext;
133    private final AlarmManager mAlarmManager;
134    private final Random mRandom;
135    private final INetworkManagementService mNMService;
136
137    // Sockets.
138    // - We use a packet socket to receive, because servers send us packets bound for IP addresses
139    //   which we have not yet configured, and the kernel protocol stack drops these.
140    // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
141    //   be off-link as well as on-link).
142    private FileDescriptor mPacketSock;
143    private FileDescriptor mUdpSock;
144    private ReceiveThread mReceiveThread;
145
146    // State variables.
147    private final StateMachine mController;
148    private final PendingIntent mKickIntent;
149    private final PendingIntent mTimeoutIntent;
150    private final PendingIntent mRenewIntent;
151    private final PendingIntent mOneshotTimeoutIntent;
152    private final String mIfaceName;
153
154    private boolean mRegisteredForPreDhcpNotification;
155    private NetworkInterface mIface;
156    private byte[] mHwAddr;
157    private PacketSocketAddress mInterfaceBroadcastAddr;
158    private int mTransactionId;
159    private long mTransactionStartMillis;
160    private DhcpResults mDhcpLease;
161    private long mDhcpLeaseExpiry;
162    private DhcpResults mOffer;
163
164    // States.
165    private State mStoppedState = new StoppedState();
166    private State mDhcpState = new DhcpState();
167    private State mDhcpInitState = new DhcpInitState();
168    private State mDhcpSelectingState = new DhcpSelectingState();
169    private State mDhcpRequestingState = new DhcpRequestingState();
170    private State mDhcpHaveAddressState = new DhcpHaveAddressState();
171    private State mDhcpBoundState = new DhcpBoundState();
172    private State mDhcpRenewingState = new DhcpRenewingState();
173    private State mDhcpRebindingState = new DhcpRebindingState();
174    private State mDhcpInitRebootState = new DhcpInitRebootState();
175    private State mDhcpRebootingState = new DhcpRebootingState();
176    private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
177    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
178
179    private DhcpClient(Context context, StateMachine controller, String iface) {
180        super(TAG);
181
182        mContext = context;
183        mController = controller;
184        mIfaceName = iface;
185
186        addState(mStoppedState);
187        addState(mDhcpState);
188            addState(mDhcpInitState, mDhcpState);
189            addState(mWaitBeforeStartState, mDhcpState);
190            addState(mDhcpSelectingState, mDhcpState);
191            addState(mDhcpRequestingState, mDhcpState);
192            addState(mDhcpHaveAddressState, mDhcpState);
193                addState(mDhcpBoundState, mDhcpHaveAddressState);
194                addState(mWaitBeforeRenewalState, mDhcpHaveAddressState);
195                addState(mDhcpRenewingState, mDhcpHaveAddressState);
196                addState(mDhcpRebindingState, mDhcpHaveAddressState);
197            addState(mDhcpInitRebootState, mDhcpState);
198            addState(mDhcpRebootingState, mDhcpState);
199
200        setInitialState(mStoppedState);
201
202        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
203        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
204        mNMService = INetworkManagementService.Stub.asInterface(b);
205
206        mRandom = new Random();
207
208        // Used to schedule packet retransmissions.
209        mKickIntent = createStateMachineCommandIntent("KICK", CMD_KICK);
210        // Used to time out PacketRetransmittingStates.
211        mTimeoutIntent = createStateMachineCommandIntent("TIMEOUT", CMD_TIMEOUT);
212        // Used to schedule DHCP renews.
213        mRenewIntent = createStateMachineCommandIntent("RENEW", DhcpStateMachine.CMD_RENEW_DHCP);
214        // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out.
215        // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and
216        // remove this.
217        mOneshotTimeoutIntent = createStateMachineCommandIntent("ONESHOT_TIMEOUT",
218                CMD_ONESHOT_TIMEOUT);
219    }
220
221    @Override
222    public void registerForPreDhcpNotification() {
223        mRegisteredForPreDhcpNotification = true;
224    }
225
226    public static BaseDhcpStateMachine makeDhcpStateMachine(
227            Context context, StateMachine controller, String intf) {
228        DhcpClient client = new DhcpClient(context, controller, intf);
229        client.start();
230        return client;
231    }
232
233    /**
234     * Constructs a PendingIntent that sends the specified command to the state machine. This is
235     * implemented by creating an Intent with the specified parameters, and creating and registering
236     * a BroadcastReceiver for it. The broadcast must be sent by a process that holds the
237     * {@code CONNECTIVITY_INTERNAL} permission.
238     *
239     * @param cmdName the name of the command. The intent's action will be
240     *         {@code android.net.dhcp.DhcpClient.<mIfaceName>.<cmdName>}
241     * @param cmd the command to send to the state machine when the PendingIntent is triggered.
242     * @return the PendingIntent
243     */
244    private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) {
245        String action = DhcpClient.class.getName() + "." + mIfaceName + "." + cmdName;
246
247        Intent intent = new Intent(action, null)
248                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
249        // TODO: The intent's package covers the whole of the system server, so it's pretty generic.
250        // Consider adding some sort of token as well.
251        intent.setPackage(mContext.getPackageName());
252        PendingIntent pendingIntent =  PendingIntent.getBroadcast(mContext, cmd, intent, 0);
253
254        mContext.registerReceiver(
255            new BroadcastReceiver() {
256                @Override
257                public void onReceive(Context context, Intent intent) {
258                    sendMessage(cmd);
259                }
260            },
261            new IntentFilter(action),
262            android.Manifest.permission.CONNECTIVITY_INTERNAL,
263            null);
264
265        return pendingIntent;
266    }
267
268    private boolean initInterface() {
269        try {
270            mIface = NetworkInterface.getByName(mIfaceName);
271            mHwAddr = mIface.getHardwareAddress();
272            mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(),
273                    DhcpPacket.ETHER_BROADCAST);
274            return true;
275        } catch(SocketException e) {
276            Log.wtf(TAG, "Can't determine ifindex or MAC address for " + mIfaceName);
277            return false;
278        }
279    }
280
281    private void startNewTransaction() {
282        mTransactionId = mRandom.nextInt();
283        mTransactionStartMillis = SystemClock.elapsedRealtime();
284    }
285
286    private boolean initSockets() {
287        try {
288            mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
289            PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
290            Os.bind(mPacketSock, addr);
291            NetworkUtils.attachDhcpFilter(mPacketSock);
292        } catch(SocketException|ErrnoException e) {
293            Log.e(TAG, "Error creating packet socket", e);
294            return false;
295        }
296        try {
297            mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
298            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
299            Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
300            Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
301            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
302            NetworkUtils.protectFromVpn(mUdpSock);
303        } catch(SocketException|ErrnoException e) {
304            Log.e(TAG, "Error creating UDP socket", e);
305            return false;
306        }
307        return true;
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    private boolean setIpAddress(LinkAddress address) {
322        InterfaceConfiguration ifcg = new InterfaceConfiguration();
323        ifcg.setLinkAddress(address);
324        try {
325            mNMService.setInterfaceConfig(mIfaceName, ifcg);
326        } catch (RemoteException|IllegalStateException e) {
327            Log.e(TAG, "Error configuring IP address : " + e);
328            return false;
329        }
330        return true;
331    }
332
333    class ReceiveThread extends Thread {
334
335        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
336        private boolean stopped = false;
337
338        public void halt() {
339            stopped = true;
340            closeSockets();  // Interrupts the read() call the thread is blocked in.
341        }
342
343        @Override
344        public void run() {
345            maybeLog("Receive thread started");
346            while (!stopped) {
347                try {
348                    int length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
349                    DhcpPacket packet = null;
350                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
351                    if (packet != null) {
352                        maybeLog("Received packet: " + packet);
353                        sendMessage(CMD_RECEIVED_PACKET, packet);
354                    } else if (PACKET_DBG) {
355                        Log.d(TAG,
356                                "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length));
357                    }
358                } catch (IOException|ErrnoException e) {
359                    if (!stopped) {
360                        Log.e(TAG, "Read error", e);
361                    }
362                }
363            }
364            maybeLog("Receive thread stopped");
365        }
366    }
367
368    private short getSecs() {
369        return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
370    }
371
372    private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) {
373        try {
374            if (to.equals(INADDR_BROADCAST)) {
375                maybeLog("Broadcasting " + description);
376                Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
377            } else {
378                maybeLog("Unicasting " + description + " to " + to.getHostAddress());
379                Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
380            }
381        } catch(ErrnoException|IOException e) {
382            Log.e(TAG, "Can't send packet: ", e);
383            return false;
384        }
385        return true;
386    }
387
388    private boolean sendDiscoverPacket() {
389        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
390                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
391                DO_UNICAST, REQUESTED_PARAMS);
392        return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST);
393    }
394
395    private boolean sendRequestPacket(
396            Inet4Address clientAddress, Inet4Address requestedAddress,
397            Inet4Address serverAddress, Inet4Address to) {
398        // TODO: should we use the transaction ID from the server?
399        int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
400
401        ByteBuffer packet = DhcpPacket.buildRequestPacket(
402                encap, mTransactionId, getSecs(), clientAddress,
403                DO_UNICAST, mHwAddr, requestedAddress,
404                serverAddress, REQUESTED_PARAMS, null);
405        String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
406                             " request=" + requestedAddress.getHostAddress() +
407                             " to=" + serverAddress.getHostAddress();
408        return transmitPacket(packet, description, to);
409    }
410
411    private void scheduleRenew() {
412        mAlarmManager.cancel(mRenewIntent);
413        if (mDhcpLeaseExpiry != 0) {
414            long now = SystemClock.elapsedRealtime();
415            long alarmTime = (now + mDhcpLeaseExpiry) / 2;
416            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mRenewIntent);
417            Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s");
418        } else {
419            Log.d(TAG, "Infinite lease, no renewal needed");
420        }
421    }
422
423    private void notifySuccess() {
424        mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
425                DhcpStateMachine.DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease));
426    }
427
428    private void notifyFailure() {
429        mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
430                DhcpStateMachine.DHCP_FAILURE, 0, null);
431    }
432
433    private void clearDhcpState() {
434        mDhcpLease = null;
435        mDhcpLeaseExpiry = 0;
436        mOffer = null;
437    }
438
439    /**
440     * Quit the DhcpStateMachine.
441     *
442     * @hide
443     */
444    @Override
445    public void doQuit() {
446        Log.d(TAG, "doQuit");
447        quit();
448    }
449
450    protected void onQuitting() {
451        Log.d(TAG, "onQuitting");
452        mController.sendMessage(DhcpStateMachine.CMD_ON_QUIT);
453    }
454
455    private void maybeLog(String msg) {
456        if (DBG) Log.d(TAG, msg);
457    }
458
459    abstract class LoggingState extends State {
460        public void enter() {
461            if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
462        }
463
464        private String messageName(int what) {
465            switch (what) {
466                case DhcpStateMachine.CMD_START_DHCP:
467                    return "CMD_START_DHCP";
468                case DhcpStateMachine.CMD_STOP_DHCP:
469                    return "CMD_STOP_DHCP";
470                case DhcpStateMachine.CMD_RENEW_DHCP:
471                    return "CMD_RENEW_DHCP";
472                case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
473                    return "CMD_PRE_DHCP_ACTION";
474                case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
475                    return "CMD_PRE_DHCP_ACTION_COMPLETE";
476                case DhcpStateMachine.CMD_POST_DHCP_ACTION:
477                    return "CMD_POST_DHCP_ACTION";
478                case CMD_KICK:
479                    return "CMD_KICK";
480                case CMD_RECEIVED_PACKET:
481                    return "CMD_RECEIVED_PACKET";
482                case CMD_TIMEOUT:
483                    return "CMD_TIMEOUT";
484                case CMD_ONESHOT_TIMEOUT:
485                    return "CMD_ONESHOT_TIMEOUT";
486                default:
487                    return Integer.toString(what);
488            }
489        }
490
491        private String messageToString(Message message) {
492            long now = SystemClock.uptimeMillis();
493            StringBuilder b = new StringBuilder(" ");
494            TimeUtils.formatDuration(message.getWhen() - now, b);
495            b.append(" ").append(messageName(message.what))
496                    .append(" ").append(message.arg1)
497                    .append(" ").append(message.arg2)
498                    .append(" ").append(message.obj);
499            return b.toString();
500        }
501
502        @Override
503        public boolean processMessage(Message message) {
504            if (MSG_DBG) {
505                Log.d(TAG, getName() + messageToString(message));
506            }
507            return NOT_HANDLED;
508        }
509    }
510
511    // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
512    // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
513    abstract class WaitBeforeOtherState extends LoggingState {
514        protected State mOtherState;
515
516        @Override
517        public void enter() {
518            super.enter();
519            mController.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION);
520        }
521
522        @Override
523        public boolean processMessage(Message message) {
524            super.processMessage(message);
525            switch (message.what) {
526                case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
527                    transitionTo(mOtherState);
528                    return HANDLED;
529                default:
530                    return NOT_HANDLED;
531            }
532        }
533    }
534
535    // The one-shot timeout is used to implement the timeout for CMD_START_DHCP. We can't use a
536    // state timeout to do this because obtaining an IP address involves passing through more than
537    // one state (specifically, it passes at least once through DhcpInitState and once through
538    // DhcpRequestingState). The one-shot timeout is created when CMD_START_DHCP is received, and is
539    // cancelled when exiting DhcpState (either due to a CMD_STOP_DHCP, or because of an error), or
540    // when we get an IP address (when entering DhcpBoundState). If it fires, we send ourselves
541    // CMD_ONESHOT_TIMEOUT and notify the caller that DHCP failed, but we take no other action. For
542    // example, if we're in DhcpInitState and sending DISCOVERs, we continue to do so.
543    //
544    // The one-shot timeout is not used for CMD_RENEW_DHCP because that is implemented using only
545    // one state, so we can just use the state timeout.
546    private void scheduleOneshotTimeout() {
547        final long alarmTime = SystemClock.elapsedRealtime() + DHCP_TIMEOUT_MS;
548        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
549                mOneshotTimeoutIntent);
550    }
551
552    private void cancelOneshotTimeout() {
553        mAlarmManager.cancel(mOneshotTimeoutIntent);
554    }
555
556    class StoppedState extends LoggingState {
557        @Override
558        public boolean processMessage(Message message) {
559            super.processMessage(message);
560            switch (message.what) {
561                case DhcpStateMachine.CMD_START_DHCP:
562                    scheduleOneshotTimeout();
563                    if (mRegisteredForPreDhcpNotification) {
564                        transitionTo(mWaitBeforeStartState);
565                    } else {
566                        transitionTo(mDhcpInitState);
567                    }
568                    return HANDLED;
569                default:
570                    return NOT_HANDLED;
571            }
572        }
573    }
574
575    class WaitBeforeStartState extends WaitBeforeOtherState {
576        public WaitBeforeStartState(State otherState) {
577            super();
578            mOtherState = otherState;
579        }
580    }
581
582    class WaitBeforeRenewalState extends WaitBeforeOtherState {
583        public WaitBeforeRenewalState(State otherState) {
584            super();
585            mOtherState = otherState;
586        }
587    }
588
589    class DhcpState extends LoggingState {
590        @Override
591        public void enter() {
592            super.enter();
593            clearDhcpState();
594            if (initInterface() && initSockets()) {
595                mReceiveThread = new ReceiveThread();
596                mReceiveThread.start();
597            } else {
598                notifyFailure();
599                transitionTo(mStoppedState);
600            }
601        }
602
603        @Override
604        public void exit() {
605            cancelOneshotTimeout();
606            if (mReceiveThread != null) {
607                mReceiveThread.halt();  // Also closes sockets.
608                mReceiveThread = null;
609            }
610            clearDhcpState();
611        }
612
613        @Override
614        public boolean processMessage(Message message) {
615            super.processMessage(message);
616            switch (message.what) {
617                case DhcpStateMachine.CMD_STOP_DHCP:
618                    transitionTo(mStoppedState);
619                    return HANDLED;
620                case CMD_ONESHOT_TIMEOUT:
621                    maybeLog("Timed out");
622                    notifyFailure();
623                    return HANDLED;
624                default:
625                    return NOT_HANDLED;
626            }
627        }
628    }
629
630    public boolean isValidPacket(DhcpPacket packet) {
631        // TODO: check checksum.
632        int xid = packet.getTransactionId();
633        if (xid != mTransactionId) {
634            Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
635            return false;
636        }
637        if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
638            Log.d(TAG, "MAC addr mismatch: got " +
639                    HexDump.toHexString(packet.getClientMac()) + ", expected " +
640                    HexDump.toHexString(packet.getClientMac()));
641            return false;
642        }
643        return true;
644    }
645
646    public void setDhcpLeaseExpiry(DhcpPacket packet) {
647        long leaseTimeMillis = packet.getLeaseTimeMillis();
648        mDhcpLeaseExpiry =
649                (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0;
650    }
651
652    /**
653     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
654     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
655     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
656     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
657     * state.
658     *
659     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
660     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
661     * sent by the receive thread. They may also set mTimeout and implement timeout.
662     */
663    abstract class PacketRetransmittingState extends LoggingState {
664
665        private int mTimer;
666        protected int mTimeout = 0;
667
668        @Override
669        public void enter() {
670            super.enter();
671            initTimer();
672            maybeInitTimeout();
673            sendMessage(CMD_KICK);
674        }
675
676        @Override
677        public boolean processMessage(Message message) {
678            super.processMessage(message);
679            switch (message.what) {
680                case CMD_KICK:
681                    sendPacket();
682                    scheduleKick();
683                    return HANDLED;
684                case CMD_RECEIVED_PACKET:
685                    receivePacket((DhcpPacket) message.obj);
686                    return HANDLED;
687                case CMD_TIMEOUT:
688                    timeout();
689                    return HANDLED;
690                default:
691                    return NOT_HANDLED;
692            }
693        }
694
695        public void exit() {
696            mAlarmManager.cancel(mKickIntent);
697            mAlarmManager.cancel(mTimeoutIntent);
698        }
699
700        abstract protected boolean sendPacket();
701        abstract protected void receivePacket(DhcpPacket packet);
702        protected void timeout() {}
703
704        protected void initTimer() {
705            mTimer = FIRST_TIMEOUT_MS;
706        }
707
708        protected int jitterTimer(int baseTimer) {
709            int maxJitter = baseTimer / 10;
710            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
711            return baseTimer + jitter;
712        }
713
714        protected void scheduleKick() {
715            long now = SystemClock.elapsedRealtime();
716            long timeout = jitterTimer(mTimer);
717            long alarmTime = now + timeout;
718            mAlarmManager.cancel(mKickIntent);
719            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mKickIntent);
720            mTimer *= 2;
721            if (mTimer > MAX_TIMEOUT_MS) {
722                mTimer = MAX_TIMEOUT_MS;
723            }
724        }
725
726        protected void maybeInitTimeout() {
727            if (mTimeout > 0) {
728                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
729                mAlarmManager.setExact(
730                        AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mTimeoutIntent);
731            }
732        }
733    }
734
735    class DhcpInitState extends PacketRetransmittingState {
736        public DhcpInitState() {
737            super();
738        }
739
740        @Override
741        public void enter() {
742            super.enter();
743            startNewTransaction();
744        }
745
746        protected boolean sendPacket() {
747            return sendDiscoverPacket();
748        }
749
750        protected void receivePacket(DhcpPacket packet) {
751            if (!isValidPacket(packet)) return;
752            if (!(packet instanceof DhcpOfferPacket)) return;
753            mOffer = packet.toDhcpResults();
754            if (mOffer != null) {
755                Log.d(TAG, "Got pending lease: " + mOffer);
756                transitionTo(mDhcpRequestingState);
757            }
758        }
759    }
760
761    // Not implemented. We request the first offer we receive.
762    class DhcpSelectingState extends LoggingState {
763    }
764
765    class DhcpRequestingState extends PacketRetransmittingState {
766        public DhcpRequestingState() {
767            super();
768            mTimeout = DHCP_TIMEOUT_MS / 2;
769        }
770
771        protected boolean sendPacket() {
772            return sendRequestPacket(
773                    INADDR_ANY,                                    // ciaddr
774                    (Inet4Address) mOffer.ipAddress.getAddress(),  // DHCP_REQUESTED_IP
775                    (Inet4Address) mOffer.serverAddress,           // DHCP_SERVER_IDENTIFIER
776                    INADDR_BROADCAST);                             // packet destination address
777        }
778
779        protected void receivePacket(DhcpPacket packet) {
780            if (!isValidPacket(packet)) return;
781            if ((packet instanceof DhcpAckPacket)) {
782                DhcpResults results = packet.toDhcpResults();
783                if (results != null) {
784                    mDhcpLease = results;
785                    mOffer = null;
786                    Log.d(TAG, "Confirmed lease: " + mDhcpLease);
787                    setDhcpLeaseExpiry(packet);
788                    transitionTo(mDhcpBoundState);
789                }
790            } else if (packet instanceof DhcpNakPacket) {
791                Log.d(TAG, "Received NAK, returning to INIT");
792                mOffer = null;
793                transitionTo(mDhcpInitState);
794            }
795        }
796
797        @Override
798        protected void timeout() {
799            // After sending REQUESTs unsuccessfully for a while, go back to init.
800            transitionTo(mDhcpInitState);
801        }
802    }
803
804    class DhcpHaveAddressState extends LoggingState {
805        @Override
806        public void enter() {
807            super.enter();
808            if (setIpAddress(mDhcpLease.ipAddress)) {
809                maybeLog("Configured IP address " + mDhcpLease.ipAddress);
810            } else {
811                Log.e(TAG, "Failed to configure IP address " + mDhcpLease.ipAddress);
812                notifyFailure();
813                // There's likely no point in going into DhcpInitState here, we'll probably just
814                // repeat the transaction, get the same IP address as before, and fail.
815                transitionTo(mStoppedState);
816            }
817        }
818
819        @Override
820        public void exit() {
821            maybeLog("Clearing IP address");
822            setIpAddress(new LinkAddress("0.0.0.0/0"));
823        }
824    }
825
826    class DhcpBoundState extends LoggingState {
827        @Override
828        public void enter() {
829            super.enter();
830            cancelOneshotTimeout();
831            notifySuccess();
832            // TODO: DhcpStateMachine only supports renewing at 50% of the lease time, and does not
833            // support rebinding. Once the legacy DHCP client is gone, fix this.
834            scheduleRenew();
835        }
836
837        @Override
838        public boolean processMessage(Message message) {
839            super.processMessage(message);
840            switch (message.what) {
841                case DhcpStateMachine.CMD_RENEW_DHCP:
842                    if (mRegisteredForPreDhcpNotification) {
843                        transitionTo(mWaitBeforeRenewalState);
844                    } else {
845                        transitionTo(mDhcpRenewingState);
846                    }
847                    return HANDLED;
848                default:
849                    return NOT_HANDLED;
850            }
851        }
852    }
853
854    class DhcpRenewingState extends PacketRetransmittingState {
855        public DhcpRenewingState() {
856            super();
857            mTimeout = DHCP_TIMEOUT_MS;
858        }
859
860        @Override
861        public void enter() {
862            super.enter();
863            startNewTransaction();
864        }
865
866        protected boolean sendPacket() {
867            return sendRequestPacket(
868                    (Inet4Address) mDhcpLease.ipAddress.getAddress(),  // ciaddr
869                    INADDR_ANY,                                        // DHCP_REQUESTED_IP
870                    INADDR_ANY,                                        // DHCP_SERVER_IDENTIFIER
871                    (Inet4Address) mDhcpLease.serverAddress);          // packet destination address
872        }
873
874        protected void receivePacket(DhcpPacket packet) {
875            if (!isValidPacket(packet)) return;
876            if ((packet instanceof DhcpAckPacket)) {
877                setDhcpLeaseExpiry(packet);
878                transitionTo(mDhcpBoundState);
879            } else if (packet instanceof DhcpNakPacket) {
880                transitionTo(mDhcpInitState);
881            }
882        }
883
884        @Override
885        protected void timeout() {
886            transitionTo(mDhcpInitState);
887            sendMessage(CMD_ONESHOT_TIMEOUT);
888        }
889    }
890
891    // Not implemented. DhcpStateMachine does not implement it either.
892    class DhcpRebindingState extends LoggingState {
893    }
894
895    class DhcpInitRebootState extends LoggingState {
896    }
897
898    class DhcpRebootingState extends LoggingState {
899    }
900}
901