From e7d4243be939c5f83cb0c45f52f74c4150d96ca3 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Tue, 10 Mar 2015 01:38:38 +0900 Subject: DHCP: Add a Java DHCP client. The behaviour of the client is intended to mirror the behaviour of the current DhcpStateMachine + dhcpcd combination, except it does not store leases across network changes. Bug: 19704592 Change-Id: I110b58003da2d8293059d48a0181e16f7f7f145c --- services/net/java/android/net/dhcp/DhcpClient.java | 807 +++++++++++++++++++++ 1 file changed, 807 insertions(+) create mode 100644 services/net/java/android/net/dhcp/DhcpClient.java (limited to 'services/net') diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java new file mode 100644 index 0000000..57cc251 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import com.android.internal.util.HexDump; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.DhcpResults; +import android.net.BaseDhcpStateMachine; +import android.net.DhcpStateMachine; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; +import android.system.PacketSocketAddress; +import android.util.Log; +import android.util.TimeUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.lang.Thread; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +import libcore.io.IoUtils; + +import static android.system.OsConstants.*; +import static android.net.dhcp.DhcpPacket.*; + +/** + * A DHCPv4 client. + * + * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android + * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. + * + * TODO: + * + * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). + * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not + * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a + * given SSID), it requests the last-leased IP address on the same interface, causing a delay if + * the server NAKs or a timeout if it doesn't. + * + * Known differences from current behaviour: + * + * - Does not request the "static routes" option. + * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. + * - Requests the "broadcast" option, but does nothing with it. + * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). + * + * @hide + */ +public class DhcpClient extends BaseDhcpStateMachine { + + private static final String TAG = "DhcpClient"; + private static final boolean DBG = true; + private static final boolean STATE_DBG = false; + private static final boolean MSG_DBG = false; + + // Timers and timeouts. + private static final int SECONDS = 1000; + private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; + private static final int MAX_TIMEOUT_MS = 128 * SECONDS; + + // This is not strictly needed, since the client is asynchronous and implements exponential + // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was + // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at + // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. + private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; + + // Messages. + private static final int BASE = Protocol.BASE_DHCP + 100; + private static final int CMD_KICK = BASE + 1; + private static final int CMD_RECEIVED_PACKET = BASE + 2; + private static final int CMD_TIMEOUT = BASE + 3; + + // DHCP parameters that we request. + private static final byte[] REQUESTED_PARAMS = new byte[] { + DHCP_SUBNET_MASK, + DHCP_ROUTER, + DHCP_DNS_SERVER, + DHCP_DOMAIN_NAME, + DHCP_MTU, + DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. + DHCP_LEASE_TIME, + DHCP_RENEWAL_TIME, + DHCP_REBINDING_TIME, + }; + + // DHCP flag that means "yes, we support unicast." + private static final boolean DO_UNICAST = false; + + // System services / libraries we use. + private final Context mContext; + private final AlarmManager mAlarmManager; + private final Random mRandom; + private final INetworkManagementService mNMService; + + // Sockets. + // - We use a packet socket to receive, because servers send us packets bound for IP addresses + // which we have not yet configured, and the kernel protocol stack drops these. + // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can + // be off-link as well as on-link). + private FileDescriptor mPacketSock; + private FileDescriptor mUdpSock; + private ReceiveThread mReceiveThread; + + // State variables. + private final StateMachine mController; + private final PendingIntent mKickIntent; + private final PendingIntent mTimeoutIntent; + private final PendingIntent mRenewIntent; + private final String mIfaceName; + + private boolean mRegisteredForPreDhcpNotification; + private NetworkInterface mIface; + private byte[] mHwAddr; + private PacketSocketAddress mInterfaceBroadcastAddr; + private int mTransactionId; + private DhcpResults mDhcpLease; + private long mDhcpLeaseExpiry; + private DhcpResults mOffer; + + // States. + private State mStoppedState = new StoppedState(); + private State mDhcpState = new DhcpState(); + private State mDhcpInitState = new DhcpInitState(); + private State mDhcpSelectingState = new DhcpSelectingState(); + private State mDhcpRequestingState = new DhcpRequestingState(); + private State mDhcpBoundState = new DhcpBoundState(); + private State mDhcpRenewingState = new DhcpRenewingState(); + private State mDhcpRebindingState = new DhcpRebindingState(); + private State mDhcpInitRebootState = new DhcpInitRebootState(); + private State mDhcpRebootingState = new DhcpRebootingState(); + private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); + private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); + + private DhcpClient(Context context, StateMachine controller, String iface) { + super(TAG); + + mContext = context; + mController = controller; + mIfaceName = iface; + + addState(mStoppedState); + addState(mDhcpState); + addState(mDhcpInitState, mDhcpState); + addState(mWaitBeforeStartState, mDhcpState); + addState(mDhcpSelectingState, mDhcpState); + addState(mDhcpRequestingState, mDhcpState); + addState(mDhcpBoundState, mDhcpState); + addState(mWaitBeforeRenewalState, mDhcpState); + addState(mDhcpRenewingState, mDhcpState); + addState(mDhcpRebindingState, mDhcpState); + addState(mDhcpInitRebootState, mDhcpState); + addState(mDhcpRebootingState, mDhcpState); + + setInitialState(mStoppedState); + + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNMService = INetworkManagementService.Stub.asInterface(b); + + mRandom = new Random(); + + mKickIntent = createStateMachineCommandIntent("KICK", CMD_KICK); + mTimeoutIntent = createStateMachineCommandIntent("TIMEOUT", CMD_TIMEOUT); + mRenewIntent = createStateMachineCommandIntent("RENEW", DhcpStateMachine.CMD_RENEW_DHCP); + } + + @Override + public void registerForPreDhcpNotification() { + mRegisteredForPreDhcpNotification = true; + } + + public static BaseDhcpStateMachine makeDhcpStateMachine( + Context context, StateMachine controller, String intf) { + DhcpClient client = new DhcpClient(context, controller, intf); + client.start(); + return client; + } + + /** + * Constructs a PendingIntent that sends the specified command to the state machine. This is + * implemented by creating an Intent with the specified parameters, and creating and registering + * a BroadcastReceiver for it. The broadcast must be sent by a process that holds the + * {@code CONNECTIVITY_INTERNAL} permission. + * + * @param cmdName the name of the command. The intent's action will be + * {@code android.net.dhcp.DhcpClient.} + * @param cmd the command to send to the state machine when the PendingIntent is triggered. + * @return the PendingIntent + */ + private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) { + String action = DhcpClient.class.getName() + "." + cmdName; + + // TODO: figure out what values to pass to intent.setPackage() and intent.setClass() that + // result in the Intent being received by this class and nowhere else, and use them. + Intent intent = new Intent(action, null) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, cmd, intent, 0); + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + sendMessage(cmd); + } + }, + new IntentFilter(action), + android.Manifest.permission.CONNECTIVITY_INTERNAL, + null); + + return pendingIntent; + } + + private boolean initInterface() { + try { + mIface = NetworkInterface.getByName(mIfaceName); + mHwAddr = mIface.getHardwareAddress(); + mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(), + DhcpPacket.ETHER_BROADCAST); + return true; + } catch(SocketException e) { + Log.wtf(TAG, "Can't determine ifindex or MAC address for " + mIfaceName); + return false; + } + } + + private void initTransactionId() { + mTransactionId = mRandom.nextInt(); + } + + private boolean initSockets() { + try { + mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); + PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex()); + Os.bind(mPacketSock, addr); + NetworkUtils.attachDhcpFilter(mPacketSock); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating packet socket", e); + return false; + } + try { + mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); + Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); + Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); + NetworkUtils.protectFromVpn(mUdpSock); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating UDP socket", e); + return false; + } + return true; + } + + private void closeSockets() { + IoUtils.closeQuietly(mUdpSock); + IoUtils.closeQuietly(mPacketSock); + } + + private boolean setIpAddress(LinkAddress address) { + InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(address); + try { + mNMService.setInterfaceConfig(mIfaceName, ifcg); + } catch (RemoteException|IllegalStateException e) { + Log.e(TAG, "Error configuring IP address : " + e); + return false; + } + return true; + } + + class ReceiveThread extends Thread { + + private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; + private boolean stopped = false; + + public void halt() { + stopped = true; + closeSockets(); // Interrupts the read() call the thread is blocked in. + } + + @Override + public void run() { + maybeLog("Starting receive thread"); + while (!stopped) { + try { + int length = Os.read(mPacketSock, mPacket, 0, mPacket.length); + DhcpPacket packet = null; + packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); + if (packet != null) { + maybeLog("Received packet: " + packet); + sendMessage(CMD_RECEIVED_PACKET, packet); + } + } catch(IOException|ErrnoException e) { + Log.e(TAG, "Read error", e); + } + } + maybeLog("Stopping receive thread"); + } + } + + private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) { + try { + if (to.equals(INADDR_BROADCAST)) { + maybeLog("Broadcasting " + description); + Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); + } else { + maybeLog("Unicasting " + description + " to " + to.getHostAddress()); + Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); + } + } catch(ErrnoException|IOException e) { + Log.e(TAG, "Can't send packet: ", e); + return false; + } + return true; + } + + private boolean sendDiscoverPacket() { + ByteBuffer packet = DhcpPacket.buildDiscoverPacket( + DhcpPacket.ENCAP_L2, mTransactionId, mHwAddr, DO_UNICAST, REQUESTED_PARAMS); + return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST); + } + + private boolean sendRequestPacket( + Inet4Address clientAddress, Inet4Address requestedAddress, + Inet4Address serverAddress, Inet4Address to) { + // TODO: should we use the transaction ID from the server? + int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; + + ByteBuffer packet = DhcpPacket.buildRequestPacket( + encap, mTransactionId, clientAddress, + DO_UNICAST, mHwAddr, requestedAddress, + serverAddress, REQUESTED_PARAMS, null); + String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + + " request=" + requestedAddress.getHostAddress() + + " to=" + serverAddress.getHostAddress(); + return transmitPacket(packet, description, to); + } + + private void scheduleRenew() { + long now = SystemClock.elapsedRealtime(); + long alarmTime = (now + mDhcpLeaseExpiry) / 2; + mAlarmManager.cancel(mRenewIntent); + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mRenewIntent); + Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s"); + } + + private void notifyLease() { + mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, + DhcpStateMachine.DHCP_SUCCESS, 0, mDhcpLease); + } + + private void notifyFailure() { + mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, + DhcpStateMachine.DHCP_FAILURE, 0, null); + } + + private void clearDhcpState() { + mDhcpLease = null; + mDhcpLeaseExpiry = 0; + mOffer = null; + } + + /** + * Quit the DhcpStateMachine. + * + * @hide + */ + @Override + public void doQuit() { + Log.d(TAG, "doQuit"); + quit(); + } + + protected void onQuitting() { + Log.d(TAG, "onQuitting"); + mController.sendMessage(DhcpStateMachine.CMD_ON_QUIT); + } + + private void maybeLog(String msg) { + if (DBG) Log.d(TAG, msg); + } + + abstract class LoggingState extends State { + public void enter() { + if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); + } + + private String messageName(int what) { + switch (what) { + case DhcpStateMachine.CMD_START_DHCP: + return "CMD_START_DHCP"; + case DhcpStateMachine.CMD_STOP_DHCP: + return "CMD_STOP_DHCP"; + case DhcpStateMachine.CMD_RENEW_DHCP: + return "CMD_RENEW_DHCP"; + case DhcpStateMachine.CMD_PRE_DHCP_ACTION: + return "CMD_PRE_DHCP_ACTION"; + case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE: + return "CMD_PRE_DHCP_ACTION_COMPLETE"; + case DhcpStateMachine.CMD_POST_DHCP_ACTION: + return "CMD_POST_DHCP_ACTION"; + case CMD_KICK: + return "CMD_KICK"; + case CMD_RECEIVED_PACKET: + return "CMD_RECEIVED_PACKET"; + default: + return Integer.toString(what); + } + } + + private String messageToString(Message message) { + long now = SystemClock.uptimeMillis(); + StringBuilder b = new StringBuilder(" "); + TimeUtils.formatDuration(message.getWhen() - now, b); + b.append(" ").append(messageName(message.what)) + .append(" ").append(message.arg1) + .append(" ").append(message.arg2) + .append(" ").append(message.obj); + return b.toString(); + } + + @Override + public boolean processMessage(Message message) { + if (MSG_DBG) { + Log.d(TAG, getName() + messageToString(message)); + } + return NOT_HANDLED; + } + } + + // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with + // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. + abstract class WaitBeforeOtherState extends LoggingState { + protected State mOtherState; + + @Override + public void enter() { + super.enter(); + mController.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE: + transitionTo(mOtherState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class StoppedState extends LoggingState { + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case DhcpStateMachine.CMD_START_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeStartState); + } else { + transitionTo(mDhcpInitState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class WaitBeforeStartState extends WaitBeforeOtherState { + public WaitBeforeStartState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class WaitBeforeRenewalState extends WaitBeforeOtherState { + public WaitBeforeRenewalState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class DhcpState extends LoggingState { + @Override + public void enter() { + super.enter(); + clearDhcpState(); + if (initInterface() && initSockets()) { + mReceiveThread = new ReceiveThread(); + mReceiveThread.start(); + } else { + notifyFailure(); + transitionTo(mStoppedState); + } + } + + @Override + public void exit() { + mReceiveThread.halt(); // Also closes sockets. + clearDhcpState(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case DhcpStateMachine.CMD_STOP_DHCP: + transitionTo(mStoppedState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + public boolean isValidPacket(DhcpPacket packet) { + // TODO: check checksum. + int xid = packet.getTransactionId(); + if (xid != mTransactionId) { + Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); + return false; + } + if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { + Log.d(TAG, "MAC addr mismatch: got " + + HexDump.toHexString(packet.getClientMac()) + ", expected " + + HexDump.toHexString(packet.getClientMac())); + return false; + } + return true; + } + + /** + * Retransmits packets using jittered exponential backoff with an optional timeout. Packet + * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. + * + * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a + * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET + * sent by the receive thread. They may implement timeout, which is called when the timeout + * fires. + */ + abstract class PacketRetransmittingState extends LoggingState { + + private int mTimer; + protected int mTimeout = 0; + + @Override + public void enter() { + super.enter(); + initTimer(); + maybeInitTimeout(); + sendMessage(CMD_KICK); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_KICK: + sendPacket(); + scheduleKick(); + return HANDLED; + case CMD_RECEIVED_PACKET: + receivePacket((DhcpPacket) message.obj); + return HANDLED; + case CMD_TIMEOUT: + timeout(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + public void exit() { + mAlarmManager.cancel(mKickIntent); + mAlarmManager.cancel(mTimeoutIntent); + } + + abstract protected boolean sendPacket(); + abstract protected void receivePacket(DhcpPacket packet); + protected void timeout() {} + + protected void initTimer() { + mTimer = FIRST_TIMEOUT_MS; + } + + protected int jitterTimer(int baseTimer) { + int maxJitter = baseTimer / 10; + int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; + return baseTimer + jitter; + } + + protected void scheduleKick() { + long now = SystemClock.elapsedRealtime(); + long timeout = jitterTimer(mTimer); + long alarmTime = now + timeout; + mAlarmManager.cancel(mKickIntent); + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mKickIntent); + mTimer *= 2; + if (mTimer > MAX_TIMEOUT_MS) { + mTimer = MAX_TIMEOUT_MS; + } + } + + protected void maybeInitTimeout() { + if (mTimeout > 0) { + long alarmTime = SystemClock.elapsedRealtime() + mTimeout; + mAlarmManager.setExact( + AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mTimeoutIntent); + } + } + } + + class DhcpInitState extends PacketRetransmittingState { + public DhcpInitState() { + super(); + mTimeout = DHCP_TIMEOUT_MS; + } + + @Override + public void enter() { + super.enter(); + initTransactionId(); + } + + protected boolean sendPacket() { + return sendDiscoverPacket(); + } + + protected void timeout() { + maybeLog("Timeout"); + notifyFailure(); + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if (!(packet instanceof DhcpOfferPacket)) return; + mOffer = packet.toDhcpResults(); + if (mOffer != null) { + Log.d(TAG, "Got pending lease: " + mOffer); + transitionTo(mDhcpRequestingState); + } + } + } + + // Not implemented. We request the first offer we receive. + class DhcpSelectingState extends LoggingState { + } + + class DhcpRequestingState extends PacketRetransmittingState { + public DhcpRequestingState() { + super(); + mTimeout = DHCP_TIMEOUT_MS / 2; + } + + protected boolean sendPacket() { + return sendRequestPacket( + INADDR_ANY, // ciaddr + (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP + (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER + INADDR_BROADCAST); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + DhcpResults results = packet.toDhcpResults(); + if (results != null) { + mDhcpLease = results; + Log.d(TAG, "Confirmed lease: " + mDhcpLease); + mDhcpLeaseExpiry = SystemClock.elapsedRealtime() + + mDhcpLease.leaseDuration * 1000; + mOffer = null; + transitionTo(mDhcpBoundState); + } + } else if (packet instanceof DhcpNakPacket) { + Log.d(TAG, "Received NAK, returning to INIT"); + mOffer = null; + transitionTo(mDhcpInitState); + } + } + + protected void timeout() { + notifyFailure(); + transitionTo(mDhcpInitState); + } + } + + class DhcpBoundState extends LoggingState { + @Override + public void enter() { + super.enter(); + if (!setIpAddress(mDhcpLease.ipAddress)) { + notifyFailure(); + transitionTo(mStoppedState); + } + notifyLease(); + // TODO: DhcpStateMachine only supports renewing at 50% of the lease time, and does not + // support rebinding. Fix this. + scheduleRenew(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case DhcpStateMachine.CMD_RENEW_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeRenewalState); + } else { + transitionTo(mDhcpRenewingState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + // TODO: timeout. + class DhcpRenewingState extends PacketRetransmittingState { + public DhcpRenewingState() { + super(); + mTimeout = DHCP_TIMEOUT_MS; + } + + @Override + public void enter() { + super.enter(); + initTransactionId(); + } + + protected boolean sendPacket() { + return sendRequestPacket( + (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr + INADDR_ANY, // DHCP_REQUESTED_IP + INADDR_ANY, // DHCP_SERVER_IDENTIFIER + (Inet4Address) mDhcpLease.serverAddress); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + DhcpResults results = packet.toDhcpResults(); + mDhcpLease.leaseDuration = results.leaseDuration; + mDhcpLeaseExpiry = SystemClock.elapsedRealtime() + + mDhcpLease.leaseDuration * 1000; + transitionTo(mDhcpBoundState); + } else if (packet instanceof DhcpNakPacket) { + transitionTo(mDhcpInitState); + } + } + } + + // Not implemented. DhcpStateMachine does not implement it either. + class DhcpRebindingState extends LoggingState { + } + + class DhcpInitRebootState extends LoggingState { + } + + class DhcpRebootingState extends LoggingState { + } +} -- cgit v1.1