Home | History | Annotate | Download | only in dhcp
      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 
     17 package android.net.dhcp;
     18 
     19 import com.android.internal.util.HexDump;
     20 import com.android.internal.util.Protocol;
     21 import com.android.internal.util.State;
     22 import com.android.internal.util.MessageUtils;
     23 import com.android.internal.util.StateMachine;
     24 import com.android.internal.util.WakeupMessage;
     25 
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.net.DhcpResults;
     30 import android.net.InterfaceConfiguration;
     31 import android.net.LinkAddress;
     32 import android.net.NetworkUtils;
     33 import android.net.metrics.IpConnectivityLog;
     34 import android.net.metrics.DhcpClientEvent;
     35 import android.net.metrics.DhcpErrorEvent;
     36 import android.os.Message;
     37 import android.os.RemoteException;
     38 import android.os.ServiceManager;
     39 import android.os.SystemClock;
     40 import android.system.ErrnoException;
     41 import android.system.Os;
     42 import android.system.PacketSocketAddress;
     43 import android.util.Log;
     44 import android.util.SparseArray;
     45 import android.util.TimeUtils;
     46 
     47 import java.io.FileDescriptor;
     48 import java.io.IOException;
     49 import java.lang.Thread;
     50 import java.net.Inet4Address;
     51 import java.net.NetworkInterface;
     52 import java.net.SocketException;
     53 import java.nio.ByteBuffer;
     54 import java.util.Arrays;
     55 import java.util.Random;
     56 
     57 import libcore.io.IoBridge;
     58 
     59 import static android.system.OsConstants.*;
     60 import 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  */
     85 public 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