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