diff options
Diffstat (limited to 'services/net')
-rw-r--r-- | services/net/Android.mk | 10 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpAckPacket.java | 99 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpClient.java | 807 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpDeclinePacket.java | 58 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpDiscoverPacket.java | 59 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpInformPacket.java | 65 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpNakPacket.java | 64 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpOfferPacket.java | 90 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpPacket.java | 1065 | ||||
-rw-r--r-- | services/net/java/android/net/dhcp/DhcpRequestPacket.java | 77 |
10 files changed, 2394 insertions, 0 deletions
diff --git a/services/net/Android.mk b/services/net/Android.mk new file mode 100644 index 0000000..336bc45 --- /dev/null +++ b/services/net/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := services.net + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,java) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java new file mode 100644 index 0000000..25b8093 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-ACK packet. + */ +class DhcpAckPacket extends DhcpPacket { + + /** + * The address of the server which sent this packet. + */ + private final Inet4Address mSrcIp; + + DhcpAckPacket(int transId, boolean broadcast, Inet4Address serverAddress, + Inet4Address clientIp, byte[] clientMac) { + super(transId, INADDR_ANY, clientIp, serverAddress, INADDR_ANY, clientMac, broadcast); + mBroadcast = broadcast; + mSrcIp = serverAddress; + } + + public String toString() { + String s = super.toString(); + String dnsServers = " DNS servers: "; + + for (Inet4Address dnsServer: mDnsServers) { + dnsServers += dnsServer.toString() + " "; + } + + return s + " ACK: your new IP " + mYourIp + + ", netmask " + mSubnetMask + + ", gateway " + mGateway + dnsServers + + ", lease time " + mLeaseTime; + } + + /** + * Fills in a packet with the requested ACK parameters. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp; + Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp; + + fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, + DHCP_BOOTREPLY, mBroadcast); + result.flip(); + return result; + } + + /** + * Adds the optional parameters to the client-generated ACK packet. + */ + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK); + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime); + + // the client should renew at 1/2 the lease-expiry interval + if (mLeaseTime != null) { + addTlv(buffer, DHCP_RENEWAL_TIME, + Integer.valueOf(mLeaseTime.intValue() / 2)); + } + + addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); + addTlv(buffer, DHCP_ROUTER, mGateway); + addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); + addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); + addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); + addTlvEnd(buffer); + } + + /** + * Un-boxes an Integer, returning 0 if a null reference is supplied. + */ + private static final int getInt(Integer v) { + if (v == null) { + return 0; + } else { + return v.intValue(); + } + } +} 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.<cmdName>} + * @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 { + } +} diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java new file mode 100644 index 0000000..9d985ac --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-DECLINE packet. + */ +class DhcpDeclinePacket extends DhcpPacket { + /** + * Generates a DECLINE packet with the specified parameters. + */ + DhcpDeclinePacket(int transId, Inet4Address clientIp, Inet4Address yourIp, + Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac) { + super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false); + } + + public String toString() { + String s = super.toString(); + return s + " DECLINE"; + } + + /** + * Fills in a packet with the requested DECLINE attributes. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + + fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result, + DHCP_BOOTREQUEST, false); + result.flip(); + return result; + } + + /** + * Adds optional parameters to the DECLINE packet. + */ + void finishPacket(ByteBuffer buffer) { + // None needed + } +} diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java new file mode 100644 index 0000000..a031080 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-DISCOVER packet. + */ +class DhcpDiscoverPacket extends DhcpPacket { + /** + * Generates a DISCOVER packet with the specified parameters. + */ + DhcpDiscoverPacket(int transId, byte[] clientMac, boolean broadcast) { + super(transId, INADDR_ANY, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + } + + public String toString() { + String s = super.toString(); + return s + " DISCOVER " + + (mBroadcast ? "broadcast " : "unicast "); + } + + /** + * Fills in a packet with the requested DISCOVER parameters. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, + srcUdp, result, DHCP_BOOTREQUEST, mBroadcast); + result.flip(); + return result; + } + + /** + * Adds optional parameters to a DISCOVER packet. + */ + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER); + addCommonClientTlvs(buffer); + addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/services/net/java/android/net/dhcp/DhcpInformPacket.java new file mode 100644 index 0000000..8bc7cdd --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpInformPacket.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the (unused) DHCP-INFORM packet. + */ +class DhcpInformPacket extends DhcpPacket { + /** + * Generates an INFORM packet with the specified parameters. + */ + DhcpInformPacket(int transId, Inet4Address clientIp, Inet4Address yourIp, + Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac) { + super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false); + } + + public String toString() { + String s = super.toString(); + return s + " INFORM"; + } + + /** + * Builds an INFORM packet. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + + fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result, + DHCP_BOOTREQUEST, false); + result.flip(); + return result; + } + + /** + * Adds additional parameters to the INFORM packet. + */ + void finishPacket(ByteBuffer buffer) { + byte[] clientId = new byte[7]; + + clientId[0] = CLIENT_ID_ETHER; + System.arraycopy(mClientMac, 0, clientId, 1, 6); + + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); + addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java new file mode 100644 index 0000000..1390ea7 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-NAK packet. + */ +class DhcpNakPacket extends DhcpPacket { + /** + * Generates a NAK packet with the specified parameters. + */ + DhcpNakPacket(int transId, Inet4Address clientIp, Inet4Address yourIp, + Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac) { + super(transId, INADDR_ANY, INADDR_ANY, nextIp, relayIp, + clientMac, false); + } + + public String toString() { + String s = super.toString(); + return s + " NAK, reason " + (mMessage == null ? "(none)" : mMessage); + } + + /** + * Fills in a packet with the requested NAK attributes. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + Inet4Address destIp = mClientIp; + Inet4Address srcIp = mYourIp; + + fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, + DHCP_BOOTREPLY, mBroadcast); + result.flip(); + return result; + } + + /** + * Adds the optional parameters to the client-generated NAK packet. + */ + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_NAK); + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + addTlv(buffer, DHCP_MESSAGE, mMessage); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java new file mode 100644 index 0000000..b1f3bbd --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 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 java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-OFFER packet. + */ +class DhcpOfferPacket extends DhcpPacket { + /** + * The IP address of the server which sent this packet. + */ + private final Inet4Address mSrcIp; + + /** + * Generates a OFFER packet with the specified parameters. + */ + DhcpOfferPacket(int transId, boolean broadcast, Inet4Address serverAddress, + Inet4Address clientIp, byte[] clientMac) { + super(transId, INADDR_ANY, clientIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + mSrcIp = serverAddress; + } + + public String toString() { + String s = super.toString(); + String dnsServers = ", DNS servers: "; + + if (mDnsServers != null) { + for (Inet4Address dnsServer: mDnsServers) { + dnsServers += dnsServer + " "; + } + } + + return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask + + dnsServers + ", gateway " + mGateway + + " lease time " + mLeaseTime + ", domain " + mDomainName; + } + + /** + * Fills in a packet with the specified OFFER attributes. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp; + Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp; + + fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, + DHCP_BOOTREPLY, mBroadcast); + result.flip(); + return result; + } + + /** + * Adds the optional parameters to the server-generated OFFER packet. + */ + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER); + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime); + + // the client should renew at 1/2 the lease-expiry interval + if (mLeaseTime != null) { + addTlv(buffer, DHCP_RENEWAL_TIME, + Integer.valueOf(mLeaseTime.intValue() / 2)); + } + + addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); + addTlv(buffer, DHCP_ROUTER, mGateway); + addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); + addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); + addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java new file mode 100644 index 0000000..a232a6e --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -0,0 +1,1065 @@ +package android.net.dhcp; + +import android.net.DhcpResults; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.os.Build; +import android.os.SystemProperties; +import android.system.OsConstants; + +import java.io.UnsupportedEncodingException; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.ShortBuffer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Defines basic data and operations needed to build and use packets for the + * DHCP protocol. Subclasses create the specific packets used at each + * stage of the negotiation. + */ +abstract class DhcpPacket { + protected static final String TAG = "DhcpPacket"; + + public static final Inet4Address INADDR_ANY = (Inet4Address) Inet4Address.ANY; + public static final Inet4Address INADDR_BROADCAST = (Inet4Address) Inet4Address.ALL; + public static final byte[] ETHER_BROADCAST = new byte[] { + (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, + }; + + /** + * Packet encapsulations. + */ + public static final int ENCAP_L2 = 0; // EthernetII header included + public static final int ENCAP_L3 = 1; // IP/UDP header included + public static final int ENCAP_BOOTP = 2; // BOOTP contents only + + /** + * Minimum length of a DHCP packet, excluding options, in the above encapsulations. + */ + public static final int MIN_PACKET_LENGTH_BOOTP = 236; // See diagram in RFC 2131, section 2. + public static final int MIN_PACKET_LENGTH_L3 = MIN_PACKET_LENGTH_BOOTP + 20 + 8; + public static final int MIN_PACKET_LENGTH_L2 = MIN_PACKET_LENGTH_L3 + 14; + + public static final int MAX_OPTION_LEN = 255; + /** + * IP layer definitions. + */ + private static final byte IP_TYPE_UDP = (byte) 0x11; + + /** + * IP: Version 4, Header Length 20 bytes + */ + private static final byte IP_VERSION_HEADER_LEN = (byte) 0x45; + + /** + * IP: Flags 0, Fragment Offset 0, Don't Fragment + */ + private static final short IP_FLAGS_OFFSET = (short) 0x4000; + + /** + * IP: TOS + */ + private static final byte IP_TOS_LOWDELAY = (byte) 0x10; + + /** + * IP: TTL -- use default 64 from RFC1340 + */ + private static final byte IP_TTL = (byte) 0x40; + + /** + * The client DHCP port. + */ + static final short DHCP_CLIENT = (short) 68; + + /** + * The server DHCP port. + */ + static final short DHCP_SERVER = (short) 67; + + /** + * The message op code indicating a request from a client. + */ + protected static final byte DHCP_BOOTREQUEST = (byte) 1; + + /** + * The message op code indicating a response from the server. + */ + protected static final byte DHCP_BOOTREPLY = (byte) 2; + + /** + * The code type used to identify an Ethernet MAC address in the + * Client-ID field. + */ + protected static final byte CLIENT_ID_ETHER = (byte) 1; + + /** + * The maximum length of a packet that can be constructed. + */ + protected static final int MAX_LENGTH = 1500; + + /** + * DHCP Optional Type: DHCP Subnet Mask + */ + protected static final byte DHCP_SUBNET_MASK = 1; + protected Inet4Address mSubnetMask; + + /** + * DHCP Optional Type: DHCP Router + */ + protected static final byte DHCP_ROUTER = 3; + protected Inet4Address mGateway; + + /** + * DHCP Optional Type: DHCP DNS Server + */ + protected static final byte DHCP_DNS_SERVER = 6; + protected List<Inet4Address> mDnsServers; + + /** + * DHCP Optional Type: DHCP Host Name + */ + protected static final byte DHCP_HOST_NAME = 12; + protected String mHostName; + + /** + * DHCP Optional Type: DHCP DOMAIN NAME + */ + protected static final byte DHCP_DOMAIN_NAME = 15; + protected String mDomainName; + + /** + * DHCP Optional Type: DHCP Interface MTU + */ + protected static final byte DHCP_MTU = 26; + protected Short mMtu; + + /** + * DHCP Optional Type: DHCP BROADCAST ADDRESS + */ + protected static final byte DHCP_BROADCAST_ADDRESS = 28; + protected Inet4Address mBroadcastAddress; + + /** + * DHCP Optional Type: DHCP Requested IP Address + */ + protected static final byte DHCP_REQUESTED_IP = 50; + protected Inet4Address mRequestedIp; + + /** + * DHCP Optional Type: DHCP Lease Time + */ + protected static final byte DHCP_LEASE_TIME = 51; + protected Integer mLeaseTime; + + /** + * DHCP Optional Type: DHCP Message Type + */ + protected static final byte DHCP_MESSAGE_TYPE = 53; + // the actual type values + protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1; + protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2; + protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3; + protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; + protected static final byte DHCP_MESSAGE_TYPE_ACK = 5; + protected static final byte DHCP_MESSAGE_TYPE_NAK = 6; + protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8; + + /** + * DHCP Optional Type: DHCP Server Identifier + */ + protected static final byte DHCP_SERVER_IDENTIFIER = 54; + protected Inet4Address mServerIdentifier; + + /** + * DHCP Optional Type: DHCP Parameter List + */ + protected static final byte DHCP_PARAMETER_LIST = 55; + protected byte[] mRequestedParams; + + /** + * DHCP Optional Type: DHCP MESSAGE + */ + protected static final byte DHCP_MESSAGE = 56; + protected String mMessage; + + /** + * DHCP Optional Type: Maximum DHCP Message Size + */ + protected static final byte DHCP_MAX_MESSAGE_SIZE = 57; + protected Short mMaxMessageSize; + + /** + * DHCP Optional Type: DHCP Renewal Time Value + */ + protected static final byte DHCP_RENEWAL_TIME = 58; + protected Integer mT1; + + /** + * DHCP Optional Type: Rebinding Time Value + */ + protected static final byte DHCP_REBINDING_TIME = 59; + protected Integer mT2; + + /** + * DHCP Optional Type: Vendor Class Identifier + */ + protected static final byte DHCP_VENDOR_CLASS_ID = 60; + protected String mVendorId; + + /** + * DHCP Optional Type: DHCP Client Identifier + */ + protected static final byte DHCP_CLIENT_IDENTIFIER = 61; + + /** + * The transaction identifier used in this particular DHCP negotiation + */ + protected final int mTransId; + + /** + * The IP address of the client host. This address is typically + * proposed by the client (from an earlier DHCP negotiation) or + * supplied by the server. + */ + protected final Inet4Address mClientIp; + protected final Inet4Address mYourIp; + private final Inet4Address mNextIp; + private final Inet4Address mRelayIp; + + /** + * Does the client request a broadcast response? + */ + protected boolean mBroadcast; + + /** + * The six-octet MAC of the client. + */ + protected final byte[] mClientMac; + + /** + * Asks the packet object to create a ByteBuffer serialization of + * the packet for transmission. + */ + public abstract ByteBuffer buildPacket(int encap, short destUdp, + short srcUdp); + + /** + * Allows the concrete class to fill in packet-type-specific details, + * typically optional parameters at the end of the packet. + */ + abstract void finishPacket(ByteBuffer buffer); + + protected DhcpPacket(int transId, Inet4Address clientIp, Inet4Address yourIp, + Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac, boolean broadcast) { + mTransId = transId; + mClientIp = clientIp; + mYourIp = yourIp; + mNextIp = nextIp; + mRelayIp = relayIp; + mClientMac = clientMac; + mBroadcast = broadcast; + } + + /** + * Returns the transaction ID. + */ + public int getTransactionId() { + return mTransId; + } + + /** + * Returns the client MAC. + */ + public byte[] getClientMac() { + return mClientMac; + } + + /** + * Creates a new L3 packet (including IP header) containing the + * DHCP udp packet. This method relies upon the delegated method + * finishPacket() to insert the per-packet contents. + */ + protected void fillInPacket(int encap, Inet4Address destIp, + Inet4Address srcIp, short destUdp, short srcUdp, ByteBuffer buf, + byte requestCode, boolean broadcast) { + byte[] destIpArray = destIp.getAddress(); + byte[] srcIpArray = srcIp.getAddress(); + int ipHeaderOffset = 0; + int ipLengthOffset = 0; + int ipChecksumOffset = 0; + int endIpHeader = 0; + int udpHeaderOffset = 0; + int udpLengthOffset = 0; + int udpChecksumOffset = 0; + + buf.clear(); + buf.order(ByteOrder.BIG_ENDIAN); + + if (encap == ENCAP_L2) { + buf.put(ETHER_BROADCAST); + buf.put(mClientMac); + buf.putShort((short) OsConstants.ETH_P_IP); + } + + // if a full IP packet needs to be generated, put the IP & UDP + // headers in place, and pre-populate with artificial values + // needed to seed the IP checksum. + if (encap <= ENCAP_L3) { + ipHeaderOffset = buf.position(); + buf.put(IP_VERSION_HEADER_LEN); + buf.put(IP_TOS_LOWDELAY); // tos: IPTOS_LOWDELAY + ipLengthOffset = buf.position(); + buf.putShort((short)0); // length + buf.putShort((short)0); // id + buf.putShort(IP_FLAGS_OFFSET); // ip offset: don't fragment + buf.put(IP_TTL); // TTL: use default 64 from RFC1340 + buf.put(IP_TYPE_UDP); + ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // checksum + + buf.put(srcIpArray); + buf.put(destIpArray); + endIpHeader = buf.position(); + + // UDP header + udpHeaderOffset = buf.position(); + buf.putShort(srcUdp); + buf.putShort(destUdp); + udpLengthOffset = buf.position(); + buf.putShort((short) 0); // length + udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum -- initially zero + } + + // DHCP payload + buf.put(requestCode); + buf.put((byte) 1); // Hardware Type: Ethernet + buf.put((byte) mClientMac.length); // Hardware Address Length + buf.put((byte) 0); // Hop Count + buf.putInt(mTransId); // Transaction ID + buf.putShort((short) 0); // Elapsed Seconds + + if (broadcast) { + buf.putShort((short) 0x8000); // Flags + } else { + buf.putShort((short) 0x0000); // Flags + } + + buf.put(mClientIp.getAddress()); + buf.put(mYourIp.getAddress()); + buf.put(mNextIp.getAddress()); + buf.put(mRelayIp.getAddress()); + buf.put(mClientMac); + buf.position(buf.position() + + (16 - mClientMac.length) // pad addr to 16 bytes + + 64 // empty server host name (64 bytes) + + 128); // empty boot file name (128 bytes) + buf.putInt(0x63825363); // magic number + finishPacket(buf); + + // round up to an even number of octets + if ((buf.position() & 1) == 1) { + buf.put((byte) 0); + } + + // If an IP packet is being built, the IP & UDP checksums must be + // computed. + if (encap <= ENCAP_L3) { + // fix UDP header: insert length + short udpLen = (short)(buf.position() - udpHeaderOffset); + buf.putShort(udpLengthOffset, udpLen); + // fix UDP header: checksum + // checksum for UDP at udpChecksumOffset + int udpSeed = 0; + + // apply IPv4 pseudo-header. Read IP address src and destination + // values from the IP header and accumulate checksum. + udpSeed += intAbs(buf.getShort(ipChecksumOffset + 2)); + udpSeed += intAbs(buf.getShort(ipChecksumOffset + 4)); + udpSeed += intAbs(buf.getShort(ipChecksumOffset + 6)); + udpSeed += intAbs(buf.getShort(ipChecksumOffset + 8)); + + // accumulate extra data for the pseudo-header + udpSeed += IP_TYPE_UDP; + udpSeed += udpLen; + // and compute UDP checksum + buf.putShort(udpChecksumOffset, (short) checksum(buf, udpSeed, + udpHeaderOffset, + buf.position())); + // fix IP header: insert length + buf.putShort(ipLengthOffset, (short)(buf.position() - ipHeaderOffset)); + // fixup IP-header checksum + buf.putShort(ipChecksumOffset, + (short) checksum(buf, 0, ipHeaderOffset, endIpHeader)); + } + } + + /** + * Converts a signed short value to an unsigned int value. Needed + * because Java does not have unsigned types. + */ + private static int intAbs(short v) { + return v & 0xFFFF; + } + + /** + * Performs an IP checksum (used in IP header and across UDP + * payload) on the specified portion of a ByteBuffer. The seed + * allows the checksum to commence with a specified value. + */ + private int checksum(ByteBuffer buf, int seed, int start, int end) { + int sum = seed; + int bufPosition = buf.position(); + + // set position of original ByteBuffer, so that the ShortBuffer + // will be correctly initialized + buf.position(start); + ShortBuffer shortBuf = buf.asShortBuffer(); + + // re-set ByteBuffer position + buf.position(bufPosition); + + short[] shortArray = new short[(end - start) / 2]; + shortBuf.get(shortArray); + + for (short s : shortArray) { + sum += intAbs(s); + } + + start += shortArray.length * 2; + + // see if a singleton byte remains + if (end != start) { + short b = buf.get(start); + + // make it unsigned + if (b < 0) { + b += 256; + } + + sum += b * 256; + } + + sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); + sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF); + int negated = ~sum; + return intAbs((short) negated); + } + + /** + * Adds an optional parameter containing a single byte value. + */ + protected static void addTlv(ByteBuffer buf, byte type, byte value) { + buf.put(type); + buf.put((byte) 1); + buf.put(value); + } + + /** + * Adds an optional parameter containing an array of bytes. + */ + protected static void addTlv(ByteBuffer buf, byte type, byte[] payload) { + if (payload != null) { + if (payload.length > MAX_OPTION_LEN) { + throw new IllegalArgumentException("DHCP option too long: " + + payload.length + " vs. " + MAX_OPTION_LEN); + } + buf.put(type); + buf.put((byte) payload.length); + buf.put(payload); + } + } + + /** + * Adds an optional parameter containing an IP address. + */ + protected static void addTlv(ByteBuffer buf, byte type, Inet4Address addr) { + if (addr != null) { + addTlv(buf, type, addr.getAddress()); + } + } + + /** + * Adds an optional parameter containing a list of IP addresses. + */ + protected static void addTlv(ByteBuffer buf, byte type, List<Inet4Address> addrs) { + if (addrs == null || addrs.size() == 0) return; + + int optionLen = 4 * addrs.size(); + if (optionLen > MAX_OPTION_LEN) { + throw new IllegalArgumentException("DHCP option too long: " + + optionLen + " vs. " + MAX_OPTION_LEN); + } + + buf.put(type); + buf.put((byte)(optionLen)); + + for (Inet4Address addr : addrs) { + buf.put(addr.getAddress()); + } + } + + /** + * Adds an optional parameter containing a short integer + */ + protected static void addTlv(ByteBuffer buf, byte type, Short value) { + if (value != null) { + buf.put(type); + buf.put((byte) 2); + buf.putShort(value.shortValue()); + } + } + + /** + * Adds an optional parameter containing a simple integer + */ + protected static void addTlv(ByteBuffer buf, byte type, Integer value) { + if (value != null) { + buf.put(type); + buf.put((byte) 4); + buf.putInt(value.intValue()); + } + } + + /** + * Adds an optional parameter containing an ASCII string. + */ + protected static void addTlv(ByteBuffer buf, byte type, String str) { + try { + addTlv(buf, type, str.getBytes("US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("String is not US-ASCII: " + str); + } + } + + /** + * Adds the special end-of-optional-parameters indicator. + */ + protected static void addTlvEnd(ByteBuffer buf) { + buf.put((byte) 0xFF); + } + + /** + * Adds common client TLVs. + * + * TODO: Does this belong here? The alternative would be to modify all the buildXyzPacket + * methods to take them. + */ + protected void addCommonClientTlvs(ByteBuffer buf) { + addTlv(buf, DHCP_MAX_MESSAGE_SIZE, (short) MAX_LENGTH); + addTlv(buf, DHCP_VENDOR_CLASS_ID, "android-dhcp-" + Build.VERSION.RELEASE); + addTlv(buf, DHCP_HOST_NAME, SystemProperties.get("net.hostname")); + } + + /** + * Converts a MAC from an array of octets to an ASCII string. + */ + public static String macToString(byte[] mac) { + String macAddr = ""; + + for (int i = 0; i < mac.length; i++) { + String hexString = "0" + Integer.toHexString(mac[i]); + + // substring operation grabs the last 2 digits: this + // allows signed bytes to be converted correctly. + macAddr += hexString.substring(hexString.length() - 2); + + if (i != (mac.length - 1)) { + macAddr += ":"; + } + } + + return macAddr; + } + + public String toString() { + String macAddr = macToString(mClientMac); + + return macAddr; + } + + /** + * Reads a four-octet value from a ByteBuffer and construct + * an IPv4 address from that value. + */ + private static Inet4Address readIpAddress(ByteBuffer packet) { + Inet4Address result = null; + byte[] ipAddr = new byte[4]; + packet.get(ipAddr); + + try { + result = (Inet4Address) Inet4Address.getByAddress(ipAddr); + } catch (UnknownHostException ex) { + // ipAddr is numeric, so this should not be + // triggered. However, if it is, just nullify + result = null; + } + + return result; + } + + /** + * Reads a string of specified length from the buffer. + */ + private static String readAsciiString(ByteBuffer buf, int byteCount) { + byte[] bytes = new byte[byteCount]; + buf.get(bytes); + return new String(bytes, 0, bytes.length, StandardCharsets.US_ASCII); + } + + /** + * Creates a concrete DhcpPacket from the supplied ByteBuffer. The + * buffer may have an L2 encapsulation (which is the full EthernetII + * format starting with the source-address MAC) or an L3 encapsulation + * (which starts with the IP header). + * <br> + * A subset of the optional parameters are parsed and are stored + * in object fields. + */ + public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) + { + // bootp parameters + int transactionId; + Inet4Address clientIp; + Inet4Address yourIp; + Inet4Address nextIp; + Inet4Address relayIp; + byte[] clientMac; + List<Inet4Address> dnsServers = new ArrayList<Inet4Address>(); + Inet4Address gateway = null; // aka router + Inet4Address serverIdentifier = null; + Inet4Address netMask = null; + String message = null; + String vendorId = null; + byte[] expectedParams = null; + String hostName = null; + String domainName = null; + Inet4Address ipSrc = null; + Inet4Address ipDst = null; + Inet4Address bcAddr = null; + Inet4Address requestedIp = null; + + // The following are all unsigned integers. Internally we store them as signed integers of + // the same length because that way we're guaranteed that they can't be out of the range of + // the unsigned field in the packet. Callers wanting to pass in an unsigned value will need + // to cast it. + Short mtu = null; + Short maxMessageSize = null; + Integer leaseTime = null; + Integer T1 = null; + Integer T2 = null; + + // dhcp options + byte dhcpType = (byte) 0xFF; + + packet.order(ByteOrder.BIG_ENDIAN); + + // check to see if we need to parse L2, IP, and UDP encaps + if (pktType == ENCAP_L2) { + if (packet.remaining() < MIN_PACKET_LENGTH_L2) { + return null; + } + + byte[] l2dst = new byte[6]; + byte[] l2src = new byte[6]; + + packet.get(l2dst); + packet.get(l2src); + + short l2type = packet.getShort(); + + if (l2type != OsConstants.ETH_P_IP) + return null; + } + + if (pktType <= ENCAP_L3) { + if (packet.remaining() < MIN_PACKET_LENGTH_L3) { + return null; + } + + byte ipTypeAndLength = packet.get(); + int ipVersion = (ipTypeAndLength & 0xf0) >> 4; + if (ipVersion != 4) { + return null; + } + + // System.out.println("ipType is " + ipType); + byte ipDiffServicesField = packet.get(); + short ipTotalLength = packet.getShort(); + short ipIdentification = packet.getShort(); + byte ipFlags = packet.get(); + byte ipFragOffset = packet.get(); + byte ipTTL = packet.get(); + byte ipProto = packet.get(); + short ipChksm = packet.getShort(); + + ipSrc = readIpAddress(packet); + ipDst = readIpAddress(packet); + + if (ipProto != IP_TYPE_UDP) // UDP + return null; + + // Skip options. This cannot cause us to read beyond the end of the buffer because the + // IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than + // MIN_PACKET_LENGTH_L3. + int optionWords = ((ipTypeAndLength & 0x0f) - 5); + for (int i = 0; i < optionWords; i++) { + packet.getInt(); + } + + // assume UDP + short udpSrcPort = packet.getShort(); + short udpDstPort = packet.getShort(); + short udpLen = packet.getShort(); + short udpChkSum = packet.getShort(); + + if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT)) + return null; + } + + // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length. + if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) { + return null; + } + + byte type = packet.get(); + byte hwType = packet.get(); + byte addrLen = packet.get(); + byte hops = packet.get(); + transactionId = packet.getInt(); + short elapsed = packet.getShort(); + short bootpFlags = packet.getShort(); + boolean broadcast = (bootpFlags & 0x8000) != 0; + byte[] ipv4addr = new byte[4]; + + try { + packet.get(ipv4addr); + clientIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr); + packet.get(ipv4addr); + yourIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr); + packet.get(ipv4addr); + nextIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr); + packet.get(ipv4addr); + relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr); + } catch (UnknownHostException ex) { + return null; + } + + clientMac = new byte[addrLen]; + packet.get(clientMac); + + // skip over address padding (16 octets allocated) + packet.position(packet.position() + (16 - addrLen) + + 64 // skip server host name (64 chars) + + 128); // skip boot file name (128 chars) + + int dhcpMagicCookie = packet.getInt(); + + if (dhcpMagicCookie != 0x63825363) + return null; + + // parse options + boolean notFinishedOptions = true; + + while ((packet.position() < packet.limit()) && notFinishedOptions) { + try { + byte optionType = packet.get(); + + if (optionType == (byte) 0xFF) { + notFinishedOptions = false; + } else { + int optionLen = packet.get() & 0xFF; + int expectedLen = 0; + + switch(optionType) { + case DHCP_SUBNET_MASK: + netMask = readIpAddress(packet); + expectedLen = 4; + break; + case DHCP_ROUTER: + gateway = readIpAddress(packet); + expectedLen = 4; + break; + case DHCP_DNS_SERVER: + for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) { + dnsServers.add(readIpAddress(packet)); + } + break; + case DHCP_HOST_NAME: + expectedLen = optionLen; + hostName = readAsciiString(packet, optionLen); + break; + case DHCP_MTU: + expectedLen = 2; + mtu = Short.valueOf(packet.getShort()); + break; + case DHCP_DOMAIN_NAME: + expectedLen = optionLen; + domainName = readAsciiString(packet, optionLen); + break; + case DHCP_BROADCAST_ADDRESS: + bcAddr = readIpAddress(packet); + expectedLen = 4; + break; + case DHCP_REQUESTED_IP: + requestedIp = readIpAddress(packet); + expectedLen = 4; + break; + case DHCP_LEASE_TIME: + leaseTime = Integer.valueOf(packet.getInt()); + expectedLen = 4; + break; + case DHCP_MESSAGE_TYPE: + dhcpType = packet.get(); + expectedLen = 1; + break; + case DHCP_SERVER_IDENTIFIER: + serverIdentifier = readIpAddress(packet); + expectedLen = 4; + break; + case DHCP_PARAMETER_LIST: + expectedParams = new byte[optionLen]; + packet.get(expectedParams); + expectedLen = optionLen; + break; + case DHCP_MESSAGE: + expectedLen = optionLen; + message = readAsciiString(packet, optionLen); + break; + case DHCP_MAX_MESSAGE_SIZE: + expectedLen = 2; + maxMessageSize = Short.valueOf(packet.getShort()); + break; + case DHCP_RENEWAL_TIME: + expectedLen = 4; + T1 = Integer.valueOf(packet.getInt()); + break; + case DHCP_REBINDING_TIME: + expectedLen = 4; + T2 = Integer.valueOf(packet.getInt()); + break; + case DHCP_VENDOR_CLASS_ID: + expectedLen = optionLen; + vendorId = readAsciiString(packet, optionLen); + break; + case DHCP_CLIENT_IDENTIFIER: { // Client identifier + byte[] id = new byte[optionLen]; + packet.get(id); + expectedLen = optionLen; + } break; + default: + // ignore any other parameters + for (int i = 0; i < optionLen; i++) { + expectedLen++; + byte throwaway = packet.get(); + } + } + + if (expectedLen != optionLen) { + return null; + } + } + } catch (BufferUnderflowException e) { + return null; + } + } + + DhcpPacket newPacket; + + switch(dhcpType) { + case -1: return null; + case DHCP_MESSAGE_TYPE_DISCOVER: + newPacket = new DhcpDiscoverPacket( + transactionId, clientMac, broadcast); + break; + case DHCP_MESSAGE_TYPE_OFFER: + newPacket = new DhcpOfferPacket( + transactionId, broadcast, ipSrc, yourIp, clientMac); + break; + case DHCP_MESSAGE_TYPE_REQUEST: + newPacket = new DhcpRequestPacket( + transactionId, clientIp, clientMac, broadcast); + break; + case DHCP_MESSAGE_TYPE_DECLINE: + newPacket = new DhcpDeclinePacket( + transactionId, clientIp, yourIp, nextIp, relayIp, + clientMac); + break; + case DHCP_MESSAGE_TYPE_ACK: + newPacket = new DhcpAckPacket( + transactionId, broadcast, ipSrc, yourIp, clientMac); + break; + case DHCP_MESSAGE_TYPE_NAK: + newPacket = new DhcpNakPacket( + transactionId, clientIp, yourIp, nextIp, relayIp, + clientMac); + break; + case DHCP_MESSAGE_TYPE_INFORM: + newPacket = new DhcpInformPacket( + transactionId, clientIp, yourIp, nextIp, relayIp, + clientMac); + break; + default: + System.out.println("Unimplemented type: " + dhcpType); + return null; + } + + newPacket.mBroadcastAddress = bcAddr; + newPacket.mDnsServers = dnsServers; + newPacket.mDomainName = domainName; + newPacket.mGateway = gateway; + newPacket.mHostName = hostName; + newPacket.mLeaseTime = leaseTime; + newPacket.mMessage = message; + newPacket.mMtu = mtu; + newPacket.mRequestedIp = requestedIp; + newPacket.mRequestedParams = expectedParams; + newPacket.mServerIdentifier = serverIdentifier; + newPacket.mSubnetMask = netMask; + newPacket.mMaxMessageSize = maxMessageSize; + newPacket.mT1 = T1; + newPacket.mT2 = T2; + newPacket.mVendorId = vendorId; + return newPacket; + } + + /** + * Parse a packet from an array of bytes, stopping at the given length. + */ + public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType) + { + ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN); + return decodeFullPacket(buffer, pktType); + } + + /** + * Construct a DhcpResults object from a DHCP reply packet. + */ + public DhcpResults toDhcpResults() { + Inet4Address ipAddress = mYourIp; + if (ipAddress == Inet4Address.ANY) { + ipAddress = mClientIp; + if (ipAddress == Inet4Address.ANY) { + return null; + } + } + + int prefixLength; + if (mSubnetMask != null) { + try { + prefixLength = NetworkUtils.netmaskToPrefixLength(mSubnetMask); + } catch (IllegalArgumentException e) { + // Non-contiguous netmask. + return null; + } + } else { + prefixLength = NetworkUtils.getImplicitNetmask(ipAddress); + } + + DhcpResults results = new DhcpResults(); + try { + results.ipAddress = new LinkAddress(ipAddress, prefixLength); + } catch (IllegalArgumentException e) { + return null; + } + results.gateway = mGateway; + results.dnsServers.addAll(mDnsServers); + results.domains = mDomainName; + results.serverAddress = mServerIdentifier; + results.vendorInfo = mVendorId; + results.leaseDuration = mLeaseTime; + return results; + } + + /** + * Builds a DHCP-DISCOVER packet from the required specified + * parameters. + */ + public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, + byte[] clientMac, boolean broadcast, byte[] expectedParams) { + DhcpPacket pkt = new DhcpDiscoverPacket( + transactionId, clientMac, broadcast); + pkt.mRequestedParams = expectedParams; + return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); + } + + /** + * Builds a DHCP-OFFER packet from the required specified + * parameters. + */ + public static ByteBuffer buildOfferPacket(int encap, int transactionId, + boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr, + byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, + Inet4Address gateway, List<Inet4Address> dnsServers, + Inet4Address dhcpServerIdentifier, String domainName) { + DhcpPacket pkt = new DhcpOfferPacket( + transactionId, broadcast, serverIpAddr, clientIpAddr, mac); + pkt.mGateway = gateway; + pkt.mDnsServers = dnsServers; + pkt.mLeaseTime = timeout; + pkt.mDomainName = domainName; + pkt.mServerIdentifier = dhcpServerIdentifier; + pkt.mSubnetMask = netMask; + pkt.mBroadcastAddress = bcAddr; + return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); + } + + /** + * Builds a DHCP-ACK packet from the required specified parameters. + */ + public static ByteBuffer buildAckPacket(int encap, int transactionId, + boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr, + byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, + Inet4Address gateway, List<Inet4Address> dnsServers, + Inet4Address dhcpServerIdentifier, String domainName) { + DhcpPacket pkt = new DhcpAckPacket( + transactionId, broadcast, serverIpAddr, clientIpAddr, mac); + pkt.mGateway = gateway; + pkt.mDnsServers = dnsServers; + pkt.mLeaseTime = timeout; + pkt.mDomainName = domainName; + pkt.mSubnetMask = netMask; + pkt.mServerIdentifier = dhcpServerIdentifier; + pkt.mBroadcastAddress = bcAddr; + return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); + } + + /** + * Builds a DHCP-NAK packet from the required specified parameters. + */ + public static ByteBuffer buildNakPacket(int encap, int transactionId, + Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac) { + DhcpPacket pkt = new DhcpNakPacket(transactionId, clientIpAddr, + serverIpAddr, serverIpAddr, serverIpAddr, mac); + pkt.mMessage = "requested address not available"; + pkt.mRequestedIp = clientIpAddr; + return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); + } + + /** + * Builds a DHCP-REQUEST packet from the required specified parameters. + */ + public static ByteBuffer buildRequestPacket(int encap, + int transactionId, Inet4Address clientIp, boolean broadcast, + byte[] clientMac, Inet4Address requestedIpAddress, + Inet4Address serverIdentifier, byte[] requestedParams, String hostName) { + DhcpPacket pkt = new DhcpRequestPacket(transactionId, clientIp, + clientMac, broadcast); + pkt.mRequestedIp = requestedIpAddress; + pkt.mServerIdentifier = serverIdentifier; + pkt.mHostName = hostName; + pkt.mRequestedParams = requestedParams; + ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); + return result; + } +} diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java new file mode 100644 index 0000000..42b7b0c --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 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 android.util.Log; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * This class implements the DHCP-REQUEST packet. + */ +class DhcpRequestPacket extends DhcpPacket { + /** + * Generates a REQUEST packet with the specified parameters. + */ + DhcpRequestPacket(int transId, Inet4Address clientIp, byte[] clientMac, + boolean broadcast) { + super(transId, clientIp, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + } + + public String toString() { + String s = super.toString(); + return s + " REQUEST, desired IP " + mRequestedIp + " from host '" + + mHostName + "', param list length " + + (mRequestedParams == null ? 0 : mRequestedParams.length); + } + + /** + * Fills in a packet with the requested REQUEST attributes. + */ + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + + fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, srcUdp, + result, DHCP_BOOTREQUEST, mBroadcast); + result.flip(); + return result; + } + + /** + * Adds the optional parameters to the client-generated REQUEST packet. + */ + void finishPacket(ByteBuffer buffer) { + byte[] clientId = new byte[7]; + + // assemble client identifier + clientId[0] = CLIENT_ID_ETHER; + System.arraycopy(mClientMac, 0, clientId, 1, 6); + + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); + if (!INADDR_ANY.equals(mRequestedIp)) { + addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp); + } + if (!INADDR_ANY.equals(mServerIdentifier)) { + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + } + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId); + addCommonClientTlvs(buffer); + addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); + addTlvEnd(buffer); + } +} |