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