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