[go: nahoru, domu]

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