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