package android.net.dhcp; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY; import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; import android.net.metrics.DhcpErrorEvent; import android.net.shared.Inet4AddressUtils; 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 = IPV4_ADDR_ANY; public static final Inet4Address INADDR_BROADCAST = IPV4_ADDR_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 mGateways; /** * DHCP Optional Type: DHCP DNS Server */ protected static final byte DHCP_DNS_SERVER = 6; protected List 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: Option overload option */ protected static final byte DHCP_OPTION_OVERLOAD = 52; /** * Possible values of the option overload option. */ private static final byte OPTION_OVERLOAD_FILE = 1; private static final byte OPTION_OVERLOAD_SNAME = 2; private static final byte OPTION_OVERLOAD_BOTH = 3; /** * 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; /** * The server host name from server. */ protected String mServerHostName; /** * 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. * *

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. * *

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. * *

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. * *

This method is a no-op if the addresses argument is null or empty. */ protected static void addTlv(ByteBuffer buf, byte type, @Nullable List 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. * *

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. * *

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. * *

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). *
* 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 dnsServers = new ArrayList<>(); List 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; String serverHostName; byte optionOverload = 0; // 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)); serverHostName = readAsciiString(packet, 64, false); packet.position(packet.position() + 128); // 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; case DHCP_OPTION_OVERLOAD: expectedLen = 1; optionOverload = packet.get(); optionOverload &= OPTION_OVERLOAD_BOTH; 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; if ((optionOverload & OPTION_OVERLOAD_SNAME) == 0) { newPacket.mServerHostName = serverHostName; } else { newPacket.mServerHostName = ""; } 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(IPV4_ADDR_ANY)) { ipAddress = mClientIp; if (ipAddress.equals(IPV4_ADDR_ANY)) { return null; } } int prefixLength; if (mSubnetMask != null) { try { prefixLength = Inet4AddressUtils.netmaskToPrefixLength(mSubnetMask); } catch (IllegalArgumentException e) { // Non-contiguous netmask. return null; } } else { prefixLength = Inet4AddressUtils.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; results.serverHostName = mServerHostName; 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 gateways, List 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 gateways, List 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; } }