summaryrefslogtreecommitdiff
path: root/src/android/net/dhcp/DhcpPacket.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/net/dhcp/DhcpPacket.java')
-rw-r--r--src/android/net/dhcp/DhcpPacket.java1364
1 files changed, 1364 insertions, 0 deletions
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
new file mode 100644
index 0000000..ce8b7e7
--- /dev/null
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -0,0 +1,1364 @@
+package android.net.dhcp;
+
+import android.annotation.Nullable;
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.metrics.DhcpErrorEvent;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.system.OsConstants;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+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.ShortBuffer;
+import java.nio.charset.StandardCharsets;
+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.
+ *
+ * @hide
+ */
+public abstract class DhcpPacket {
+ protected static final String TAG = "DhcpPacket";
+
+ // TODO: use NetworkStackConstants.IPV4_MIN_MTU once this class is moved to the network stack.
+ private static final int IPV4_MIN_MTU = 68;
+
+ // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
+ // CPU for anything shorter than 5 minutes. For sanity's sake, this must be higher than the
+ // DHCP client timeout.
+ public static final int MINIMUM_LEASE = 60;
+ public static final int INFINITE_LEASE = (int) 0xffffffff;
+
+ 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 HWADDR_LEN = 16;
+ public static final int MAX_OPTION_LEN = 255;
+
+ /**
+ * The minimum and maximum MTU that we are prepared to use. We set the minimum to the minimum
+ * IPv6 MTU because the IPv6 stack enters unusual codepaths when the link MTU drops below 1280,
+ * and does not recover if the MTU is brought above 1280 again. We set the maximum to 1500
+ * because in general it is risky to assume that the hardware is able to send/receive packets
+ * larger than 1500 bytes even if the network supports it.
+ */
+ private static final int MIN_MTU = 1280;
+ private static final int MAX_MTU = 1500;
+
+ /**
+ * 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;
+
+ /**
+ * The magic cookie that identifies this as a DHCP packet instead of BOOTP.
+ */
+ private static final int DHCP_MAGIC_COOKIE = 0x63825363;
+
+ /**
+ * 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 List <Inet4Address> mGateways;
+
+ /**
+ * 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: Vendor specific information
+ */
+ protected static final byte DHCP_VENDOR_INFO = 43;
+ protected String mVendorInfo;
+
+ /**
+ * Value of the vendor specific option used to indicate that the network is metered
+ */
+ public static final String VENDOR_INFO_ANDROID_METERED = "ANDROID_METERED";
+
+ /**
+ * 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_RELEASE = 7;
+ 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;
+ protected byte[] mClientId;
+
+ /**
+ * DHCP zero-length option code: pad
+ */
+ protected static final byte DHCP_OPTION_PAD = 0x00;
+
+ /**
+ * DHCP zero-length option code: end of options
+ */
+ protected static final byte DHCP_OPTION_END = (byte) 0xff;
+
+ /**
+ * The transaction identifier used in this particular DHCP negotiation
+ */
+ protected final int mTransId;
+
+ /**
+ * The seconds field in the BOOTP header. Per RFC, should be nonzero in client requests only.
+ */
+ protected final short mSecs;
+
+ /**
+ * 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;
+ protected 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);
+
+ // Set in unit tests, to ensure that the test does not break when run on different devices and
+ // on different releases.
+ static String testOverrideVendorId = null;
+ static String testOverrideHostname = null;
+
+ protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
+ Inet4Address nextIp, Inet4Address relayIp,
+ byte[] clientMac, boolean broadcast) {
+ mTransId = transId;
+ mSecs = secs;
+ 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;
+ }
+
+ // TODO: refactor DhcpClient to set clientId when constructing packets and remove
+ // hasExplicitClientId logic
+ /**
+ * Returns whether a client ID was set in the options for this packet.
+ */
+ public boolean hasExplicitClientId() {
+ return mClientId != null;
+ }
+
+ /**
+ * Convenience method to return the client ID if it was set explicitly, or null otherwise.
+ */
+ @Nullable
+ public byte[] getExplicitClientIdOrNull() {
+ return hasExplicitClientId() ? getClientId() : null;
+ }
+
+ /**
+ * Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID
+ * based on the hardware address.
+ */
+ public byte[] getClientId() {
+ final byte[] clientId;
+ if (hasExplicitClientId()) {
+ clientId = Arrays.copyOf(mClientId, mClientId.length);
+ } else {
+ clientId = new byte[mClientMac.length + 1];
+ clientId[0] = CLIENT_ID_ETHER;
+ System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length);
+ }
+ return clientId;
+ }
+
+ /**
+ * Returns whether a parameter is included in the parameter request list option of this packet.
+ *
+ * <p>If there is no parameter request list option in the packet, false is returned.
+ *
+ * @param paramId ID of the parameter, such as {@link #DHCP_MTU} or {@link #DHCP_HOST_NAME}.
+ */
+ public boolean hasRequestedParam(byte paramId) {
+ if (mRequestedParams == null) {
+ return false;
+ }
+
+ for (byte reqParam : mRequestedParams) {
+ if (reqParam == paramId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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(mSecs); // 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() +
+ (HWADDR_LEN - mClientMac.length) // pad addr to 16 bytes
+ + 64 // empty server host name (64 bytes)
+ + 128); // empty boot file name (128 bytes)
+ buf.putInt(DHCP_MAGIC_COOKIE); // 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.
+ *
+ * <p>This method is a no-op if the payload argument is null.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable 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.
+ *
+ * <p>This method is a no-op if the address argument is null.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable Inet4Address addr) {
+ if (addr != null) {
+ addTlv(buf, type, addr.getAddress());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing a list of IP addresses.
+ *
+ * <p>This method is a no-op if the addresses argument is null or empty.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable 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.
+ *
+ * <p>This method is a no-op if the value argument is null.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable Short value) {
+ if (value != null) {
+ buf.put(type);
+ buf.put((byte) 2);
+ buf.putShort(value.shortValue());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing a simple integer.
+ *
+ * <p>This method is a no-op if the value argument is null.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable Integer value) {
+ if (value != null) {
+ buf.put(type);
+ buf.put((byte) 4);
+ buf.putInt(value.intValue());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing an ASCII string.
+ *
+ * <p>This method is a no-op if the string argument is null.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, @Nullable String str) {
+ if (str != null) {
+ 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);
+ }
+
+ private String getVendorId() {
+ if (testOverrideVendorId != null) return testOverrideVendorId;
+ return "android-dhcp-" + Build.VERSION.RELEASE;
+ }
+
+ private String getHostname() {
+ if (testOverrideHostname != null) return testOverrideHostname;
+ return SystemProperties.get("net.hostname");
+ }
+
+ /**
+ * 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, getVendorId());
+ final String hn = getHostname();
+ if (!TextUtils.isEmpty(hn)) addTlv(buf, DHCP_HOST_NAME, hn);
+ }
+
+ protected void addCommonServerTlvs(ByteBuffer buf) {
+ addTlv(buf, DHCP_LEASE_TIME, mLeaseTime);
+ if (mLeaseTime != null && mLeaseTime != INFINITE_LEASE) {
+ // The client should renew at 1/2 the lease-expiry interval
+ addTlv(buf, DHCP_RENEWAL_TIME, (int) (Integer.toUnsignedLong(mLeaseTime) / 2));
+ // Default rebinding time is set as below by RFC2131
+ addTlv(buf, DHCP_REBINDING_TIME,
+ (int) (Integer.toUnsignedLong(mLeaseTime) * 875L / 1000L));
+ }
+ addTlv(buf, DHCP_SUBNET_MASK, mSubnetMask);
+ addTlv(buf, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+ addTlv(buf, DHCP_ROUTER, mGateways);
+ addTlv(buf, DHCP_DNS_SERVER, mDnsServers);
+ addTlv(buf, DHCP_DOMAIN_NAME, mDomainName);
+ addTlv(buf, DHCP_HOST_NAME, mHostName);
+ addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo);
+ if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) {
+ addTlv(buf, DHCP_MTU, mMtu);
+ }
+ }
+
+ /**
+ * 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, boolean nullOk) {
+ byte[] bytes = new byte[byteCount];
+ buf.get(bytes);
+ int length = bytes.length;
+ if (!nullOk) {
+ // Stop at the first null byte. This is because some DHCP options (e.g., the domain
+ // name) are passed to netd via FrameworkListener, which refuses arguments containing
+ // null bytes. We don't do this by default because vendorInfo is an opaque string which
+ // could in theory contain null bytes.
+ for (length = 0; length < bytes.length; length++) {
+ if (bytes[length] == 0) {
+ break;
+ }
+ }
+ }
+ return new String(bytes, 0, length, StandardCharsets.US_ASCII);
+ }
+
+ private static boolean isPacketToOrFromClient(short udpSrcPort, short udpDstPort) {
+ return (udpSrcPort == DHCP_CLIENT) || (udpDstPort == DHCP_CLIENT);
+ }
+
+ private static boolean isPacketServerToServer(short udpSrcPort, short udpDstPort) {
+ return (udpSrcPort == DHCP_SERVER) && (udpDstPort == DHCP_SERVER);
+ }
+
+ public static class ParseException extends Exception {
+ public final int errorCode;
+ public ParseException(int errorCode, String msg, Object... args) {
+ super(String.format(msg, args));
+ this.errorCode = errorCode;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @VisibleForTesting
+ static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException
+ {
+ // bootp parameters
+ int transactionId;
+ short secs;
+ Inet4Address clientIp;
+ Inet4Address yourIp;
+ Inet4Address nextIp;
+ Inet4Address relayIp;
+ byte[] clientMac;
+ byte[] clientId = null;
+ List<Inet4Address> dnsServers = new ArrayList<>();
+ List<Inet4Address> gateways = new ArrayList<>(); // aka router
+ Inet4Address serverIdentifier = null;
+ Inet4Address netMask = null;
+ String message = null;
+ String vendorId = null;
+ String vendorInfo = 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) {
+ throw new ParseException(DhcpErrorEvent.L2_TOO_SHORT,
+ "L2 packet too short, %d < %d", packet.remaining(), MIN_PACKET_LENGTH_L2);
+ }
+
+ 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) {
+ throw new ParseException(DhcpErrorEvent.L2_WRONG_ETH_TYPE,
+ "Unexpected L2 type 0x%04x, expected 0x%04x", l2type, OsConstants.ETH_P_IP);
+ }
+ }
+
+ if (pktType <= ENCAP_L3) {
+ if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
+ throw new ParseException(DhcpErrorEvent.L3_TOO_SHORT,
+ "L3 packet too short, %d < %d", packet.remaining(), MIN_PACKET_LENGTH_L3);
+ }
+
+ byte ipTypeAndLength = packet.get();
+ int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
+ if (ipVersion != 4) {
+ throw new ParseException(
+ DhcpErrorEvent.L3_NOT_IPV4, "Invalid IP version %d", ipVersion);
+ }
+
+ // 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) {
+ throw new ParseException(
+ DhcpErrorEvent.L4_NOT_UDP, "Protocol not UDP: %d", ipProto);
+ }
+
+ // 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();
+
+ // Only accept packets to or from the well-known client port (expressly permitting
+ // packets from ports other than the well-known server port; http://b/24687559), and
+ // server-to-server packets, e.g. for relays.
+ if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) &&
+ !isPacketServerToServer(udpSrcPort, udpDstPort)) {
+ // This should almost never happen because we use SO_ATTACH_FILTER on the packet
+ // socket to drop packets that don't have the right source ports. However, it's
+ // possible that a packet arrives between when the socket is bound and when the
+ // filter is set. http://b/26696823 .
+ throw new ParseException(DhcpErrorEvent.L4_WRONG_PORT,
+ "Unexpected UDP ports %d->%d", udpSrcPort, udpDstPort);
+ }
+ }
+
+ // 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) {
+ throw new ParseException(DhcpErrorEvent.BOOTP_TOO_SHORT,
+ "Invalid type or BOOTP packet too short, %d < %d",
+ packet.remaining(), MIN_PACKET_LENGTH_BOOTP);
+ }
+
+ byte type = packet.get();
+ byte hwType = packet.get();
+ int addrLen = packet.get() & 0xff;
+ byte hops = packet.get();
+ transactionId = packet.getInt();
+ secs = 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) {
+ throw new ParseException(DhcpErrorEvent.L3_INVALID_IP,
+ "Invalid IPv4 address: %s", Arrays.toString(ipv4addr));
+ }
+
+ // Some DHCP servers have been known to announce invalid client hardware address values such
+ // as 0xff. The legacy DHCP client accepted these becuause it does not check the length at
+ // all but only checks that the interface MAC address matches the first bytes of the address
+ // in the packets. We're a bit stricter: if the length is obviously invalid (i.e., bigger
+ // than the size of the field), we fudge it to 6 (Ethernet). http://b/23725795
+ // TODO: evaluate whether to make this test more liberal.
+ if (addrLen > HWADDR_LEN) {
+ addrLen = ETHER_BROADCAST.length;
+ }
+
+ 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)
+
+ // Ensure this is a DHCP packet with a magic cookie, and not BOOTP. http://b/31850211
+ if (packet.remaining() < 4) {
+ throw new ParseException(DhcpErrorEvent.DHCP_NO_COOKIE, "not a DHCP message");
+ }
+
+ int dhcpMagicCookie = packet.getInt();
+ if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
+ throw new ParseException(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE,
+ "Bad magic cookie 0x%08x, should be 0x%08x",
+ dhcpMagicCookie, DHCP_MAGIC_COOKIE);
+ }
+
+ // parse options
+ boolean notFinishedOptions = true;
+
+ while ((packet.position() < packet.limit()) && notFinishedOptions) {
+ final byte optionType = packet.get(); // cannot underflow because position < limit
+ try {
+ if (optionType == DHCP_OPTION_END) {
+ notFinishedOptions = false;
+ } else if (optionType == DHCP_OPTION_PAD) {
+ // The pad option doesn't have a length field. Nothing to do.
+ } else {
+ int optionLen = packet.get() & 0xFF;
+ int expectedLen = 0;
+
+ switch(optionType) {
+ case DHCP_SUBNET_MASK:
+ netMask = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_ROUTER:
+ for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
+ gateways.add(readIpAddress(packet));
+ }
+ 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, false);
+ break;
+ case DHCP_MTU:
+ expectedLen = 2;
+ mtu = packet.getShort();
+ break;
+ case DHCP_DOMAIN_NAME:
+ expectedLen = optionLen;
+ domainName = readAsciiString(packet, optionLen, false);
+ 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, false);
+ 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;
+ // Embedded nulls are safe as this does not get passed to netd.
+ vendorId = readAsciiString(packet, optionLen, true);
+ break;
+ case DHCP_CLIENT_IDENTIFIER: { // Client identifier
+ byte[] id = new byte[optionLen];
+ packet.get(id);
+ expectedLen = optionLen;
+ } break;
+ case DHCP_VENDOR_INFO:
+ expectedLen = optionLen;
+ // Embedded nulls are safe as this does not get passed to netd.
+ vendorInfo = readAsciiString(packet, optionLen, true);
+ break;
+ default:
+ // ignore any other parameters
+ for (int i = 0; i < optionLen; i++) {
+ expectedLen++;
+ byte throwaway = packet.get();
+ }
+ }
+
+ if (expectedLen != optionLen) {
+ final int errorCode = DhcpErrorEvent.errorCodeWithOption(
+ DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH, optionType);
+ throw new ParseException(errorCode,
+ "Invalid length %d for option %d, expected %d",
+ optionLen, optionType, expectedLen);
+ }
+ }
+ } catch (BufferUnderflowException e) {
+ final int errorCode = DhcpErrorEvent.errorCodeWithOption(
+ DhcpErrorEvent.BUFFER_UNDERFLOW, optionType);
+ throw new ParseException(errorCode, "BufferUnderflowException");
+ }
+ }
+
+ DhcpPacket newPacket;
+
+ switch(dhcpType) {
+ case (byte) 0xFF:
+ throw new ParseException(DhcpErrorEvent.DHCP_NO_MSG_TYPE,
+ "No DHCP message type option");
+ case DHCP_MESSAGE_TYPE_DISCOVER:
+ newPacket = new DhcpDiscoverPacket(transactionId, secs, relayIp, clientMac,
+ broadcast, ipSrc);
+ break;
+ case DHCP_MESSAGE_TYPE_OFFER:
+ newPacket = new DhcpOfferPacket(
+ transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_REQUEST:
+ newPacket = new DhcpRequestPacket(
+ transactionId, secs, clientIp, relayIp, clientMac, broadcast);
+ break;
+ case DHCP_MESSAGE_TYPE_DECLINE:
+ newPacket = new DhcpDeclinePacket(
+ transactionId, secs, clientIp, yourIp, nextIp, relayIp,
+ clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_ACK:
+ newPacket = new DhcpAckPacket(
+ transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_NAK:
+ newPacket = new DhcpNakPacket(
+ transactionId, secs, relayIp, clientMac, broadcast);
+ break;
+ case DHCP_MESSAGE_TYPE_RELEASE:
+ if (serverIdentifier == null) {
+ throw new ParseException(DhcpErrorEvent.MISC_ERROR,
+ "DHCPRELEASE without server identifier");
+ }
+ newPacket = new DhcpReleasePacket(
+ transactionId, serverIdentifier, clientIp, relayIp, clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_INFORM:
+ newPacket = new DhcpInformPacket(
+ transactionId, secs, clientIp, yourIp, nextIp, relayIp,
+ clientMac);
+ break;
+ default:
+ throw new ParseException(DhcpErrorEvent.DHCP_UNKNOWN_MSG_TYPE,
+ "Unimplemented DHCP type %d", dhcpType);
+ }
+
+ newPacket.mBroadcastAddress = bcAddr;
+ newPacket.mClientId = clientId;
+ newPacket.mDnsServers = dnsServers;
+ newPacket.mDomainName = domainName;
+ newPacket.mGateways = gateways;
+ 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;
+ newPacket.mVendorInfo = vendorInfo;
+ 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)
+ throws ParseException {
+ ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
+ try {
+ return decodeFullPacket(buffer, pktType);
+ } catch (ParseException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ParseException(DhcpErrorEvent.PARSING_ERROR, e.getMessage());
+ }
+ }
+
+ /**
+ * Construct a DhcpResults object from a DHCP reply packet.
+ */
+ public DhcpResults toDhcpResults() {
+ Inet4Address ipAddress = mYourIp;
+ if (ipAddress.equals(Inet4Address.ANY)) {
+ ipAddress = mClientIp;
+ if (ipAddress.equals(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;
+ }
+
+ if (mGateways.size() > 0) {
+ results.gateway = mGateways.get(0);
+ }
+
+ results.dnsServers.addAll(mDnsServers);
+ results.domains = mDomainName;
+ results.serverAddress = mServerIdentifier;
+ results.vendorInfo = mVendorInfo;
+ results.leaseDuration = (mLeaseTime != null) ? mLeaseTime : INFINITE_LEASE;
+ results.mtu = (mMtu != null && MIN_MTU <= mMtu && mMtu <= MAX_MTU) ? mMtu : 0;
+
+ return results;
+ }
+
+ /**
+ * Returns the parsed lease time, in milliseconds, or 0 for infinite.
+ */
+ public long getLeaseTimeMillis() {
+ // dhcpcd treats the lack of a lease time option as an infinite lease.
+ if (mLeaseTime == null || mLeaseTime == INFINITE_LEASE) {
+ return 0;
+ } else if (0 <= mLeaseTime && mLeaseTime < MINIMUM_LEASE) {
+ return MINIMUM_LEASE * 1000;
+ } else {
+ return (mLeaseTime & 0xffffffffL) * 1000;
+ }
+ }
+
+ /**
+ * Builds a DHCP-DISCOVER packet from the required specified
+ * parameters.
+ */
+ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId,
+ short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams) {
+ DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */,
+ clientMac, broadcast, INADDR_ANY /* srcIp */);
+ 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 relayIp,
+ Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
+ Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+ short mtu) {
+ DhcpPacket pkt = new DhcpOfferPacket(
+ transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
+ INADDR_ANY /* clientIp */, yourIp, mac);
+ pkt.mGateways = gateways;
+ pkt.mDnsServers = dnsServers;
+ pkt.mLeaseTime = timeout;
+ pkt.mDomainName = domainName;
+ pkt.mHostName = hostname;
+ pkt.mServerIdentifier = dhcpServerIdentifier;
+ pkt.mSubnetMask = netMask;
+ pkt.mBroadcastAddress = bcAddr;
+ pkt.mMtu = mtu;
+ if (metered) {
+ pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+ }
+ 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 relayIp, Inet4Address yourIp,
+ Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
+ Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+ short mtu) {
+ DhcpPacket pkt = new DhcpAckPacket(
+ transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp,
+ mac);
+ pkt.mGateways = gateways;
+ pkt.mDnsServers = dnsServers;
+ pkt.mLeaseTime = timeout;
+ pkt.mDomainName = domainName;
+ pkt.mHostName = hostname;
+ pkt.mSubnetMask = netMask;
+ pkt.mServerIdentifier = dhcpServerIdentifier;
+ pkt.mBroadcastAddress = bcAddr;
+ pkt.mMtu = mtu;
+ if (metered) {
+ pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+ }
+ 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 relayIp, byte[] mac, boolean broadcast, String message) {
+ DhcpPacket pkt = new DhcpNakPacket(
+ transactionId, (short) 0, relayIp, mac, broadcast);
+ pkt.mMessage = message;
+ pkt.mServerIdentifier = serverIpAddr;
+ 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, short secs, Inet4Address clientIp, boolean broadcast,
+ byte[] clientMac, Inet4Address requestedIpAddress,
+ Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
+ DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp,
+ INADDR_ANY /* relayIp */, 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;
+ }
+}