/* * Copyright 2014 Open Networking Laboratory * * 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 org.onlab.packet; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import static com.google.common.base.Preconditions.checkArgument; import static org.onlab.packet.PacketUtils.checkInput; /** * */ public class DHCP extends BasePacket { /** * Dynamic Host Configuration Protocol packet. * ------------------------------------------ |op (1) | htype(1) | hlen(1) | * hops(1) | ------------------------------------------ | xid (4) | * ------------------------------------------ | secs (2) | flags (2) | * ------------------------------------------ | ciaddr (4) | * ------------------------------------------ | yiaddr (4) | * ------------------------------------------ | siaddr (4) | * ------------------------------------------ | giaddr (4) | * ------------------------------------------ | chaddr (16) | * ------------------------------------------ | sname (64) | * ------------------------------------------ | file (128) | * ------------------------------------------ | options (312) | * ------------------------------------------ * */ // Header + magic without options public static final int MIN_HEADER_LENGTH = 240; public static final byte OPCODE_REQUEST = 0x1; public static final byte OPCODE_REPLY = 0x2; public static final byte HWTYPE_ETHERNET = 0x1; public enum DHCPOptionCode { OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6), OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28), OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53), OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55), OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61), OptionCode_END((byte) 255); protected byte value; private DHCPOptionCode(final byte value) { this.value = value; } public byte getValue() { return this.value; } } protected byte opCode; protected byte hardwareType; protected byte hardwareAddressLength; protected byte hops; protected int transactionId; protected short seconds; protected short flags; protected int clientIPAddress; protected int yourIPAddress; protected int serverIPAddress; protected int gatewayIPAddress; protected byte[] clientHardwareAddress; protected String serverName; protected String bootFileName; protected List options = new ArrayList(); /** * @return the opCode */ public byte getOpCode() { return this.opCode; } /** * @param opCode * the opCode to set * @return this */ public DHCP setOpCode(final byte opCode) { this.opCode = opCode; return this; } /** * @return the hardwareType */ public byte getHardwareType() { return this.hardwareType; } /** * @param hardwareType * the hardwareType to set * @return this */ public DHCP setHardwareType(final byte hardwareType) { this.hardwareType = hardwareType; return this; } /** * @return the hardwareAddressLength */ public byte getHardwareAddressLength() { return this.hardwareAddressLength; } /** * @param hardwareAddressLength * the hardwareAddressLength to set * @return this */ public DHCP setHardwareAddressLength(final byte hardwareAddressLength) { this.hardwareAddressLength = hardwareAddressLength; return this; } /** * @return the hops */ public byte getHops() { return this.hops; } /** * @param hops * the hops to set * @return this */ public DHCP setHops(final byte hops) { this.hops = hops; return this; } /** * @return the transactionId */ public int getTransactionId() { return this.transactionId; } /** * @param transactionId * the transactionId to set * @return this */ public DHCP setTransactionId(final int transactionId) { this.transactionId = transactionId; return this; } /** * @return the seconds */ public short getSeconds() { return this.seconds; } /** * @param seconds * the seconds to set * @return this */ public DHCP setSeconds(final short seconds) { this.seconds = seconds; return this; } /** * @return the flags */ public short getFlags() { return this.flags; } /** * @param flags * the flags to set * @return this */ public DHCP setFlags(final short flags) { this.flags = flags; return this; } /** * @return the clientIPAddress */ public int getClientIPAddress() { return this.clientIPAddress; } /** * @param clientIPAddress * the clientIPAddress to set * @return this */ public DHCP setClientIPAddress(final int clientIPAddress) { this.clientIPAddress = clientIPAddress; return this; } /** * @return the yourIPAddress */ public int getYourIPAddress() { return this.yourIPAddress; } /** * @param yourIPAddress * the yourIPAddress to set * @return this */ public DHCP setYourIPAddress(final int yourIPAddress) { this.yourIPAddress = yourIPAddress; return this; } /** * @return the serverIPAddress */ public int getServerIPAddress() { return this.serverIPAddress; } /** * @param serverIPAddress * the serverIPAddress to set * @return this */ public DHCP setServerIPAddress(final int serverIPAddress) { this.serverIPAddress = serverIPAddress; return this; } /** * @return the gatewayIPAddress */ public int getGatewayIPAddress() { return this.gatewayIPAddress; } /** * @param gatewayIPAddress * the gatewayIPAddress to set * @return this */ public DHCP setGatewayIPAddress(final int gatewayIPAddress) { this.gatewayIPAddress = gatewayIPAddress; return this; } /** * @return the clientHardwareAddress */ public byte[] getClientHardwareAddress() { return this.clientHardwareAddress; } /** * @param clientHardwareAddress * the clientHardwareAddress to set * @return this */ public DHCP setClientHardwareAddress(final byte[] clientHardwareAddress) { this.clientHardwareAddress = clientHardwareAddress; return this; } /** * Gets a specific DHCP option parameter. * * @param optionCode * The option code to get * @return The value of the option if it exists, null otherwise */ public DHCPOption getOption(final DHCPOptionCode optionCode) { for (final DHCPOption opt : this.options) { if (opt.code == optionCode.value) { return opt; } } return null; } /** * @return the options */ public List getOptions() { return this.options; } /** * @param options * the options to set * @return this */ public DHCP setOptions(final List options) { this.options = options; return this; } /** * @return the packetType base on option 53 */ public DHCPPacketType getPacketType() { final ListIterator lit = this.options.listIterator(); while (lit.hasNext()) { final DHCPOption option = lit.next(); // only care option 53 if (option.getCode() == 53) { return DHCPPacketType.getType(option.getData()[0]); } } return null; } /** * @return the serverName */ public String getServerName() { return this.serverName; } /** * @param server * the serverName to set * @return this */ public DHCP setServerName(final String server) { this.serverName = server; return this; } /** * @return the bootFileName */ public String getBootFileName() { return this.bootFileName; } /** * @param bootFile * the bootFileName to set * @return this */ public DHCP setBootFileName(final String bootFile) { this.bootFileName = bootFile; return this; } @Override public byte[] serialize() { // not guaranteed to retain length/exact format this.resetChecksum(); // minimum size 240 including magic cookie, options generally padded to // 300 int optionsLength = 0; for (final DHCPOption option : this.options) { if (option.getCode() == 0 || option.getCode() == ((byte) 255)) { optionsLength += 1; } else { optionsLength += 2 + (0xff & option.getLength()); } } int optionsPadLength = 0; if (optionsLength < 60) { optionsPadLength = 60 - optionsLength; } final byte[] data = new byte[240 + optionsLength + optionsPadLength]; final ByteBuffer bb = ByteBuffer.wrap(data); bb.put(this.opCode); bb.put(this.hardwareType); bb.put(this.hardwareAddressLength); bb.put(this.hops); bb.putInt(this.transactionId); bb.putShort(this.seconds); bb.putShort(this.flags); bb.putInt(this.clientIPAddress); bb.putInt(this.yourIPAddress); bb.putInt(this.serverIPAddress); bb.putInt(this.gatewayIPAddress); checkArgument(this.clientHardwareAddress.length <= 16, "Hardware address is too long (%s bytes)", this.clientHardwareAddress.length); bb.put(this.clientHardwareAddress); if (this.clientHardwareAddress.length < 16) { for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) { bb.put((byte) 0x0); } } this.writeString(this.serverName, bb, 64); this.writeString(this.bootFileName, bb, 128); // magic cookie bb.put((byte) 0x63); bb.put((byte) 0x82); bb.put((byte) 0x53); bb.put((byte) 0x63); for (final DHCPOption option : this.options) { final int code = option.getCode() & 0xff; bb.put((byte) code); if (code != 0 && code != 255) { bb.put(option.getLength()); bb.put(option.getData()); } } // assume the rest is padded out with zeroes return data; } @Override public IPacket deserialize(final byte[] data, final int offset, final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) { return this; } this.opCode = bb.get(); this.hardwareType = bb.get(); this.hardwareAddressLength = bb.get(); this.hops = bb.get(); this.transactionId = bb.getInt(); this.seconds = bb.getShort(); this.flags = bb.getShort(); this.clientIPAddress = bb.getInt(); this.yourIPAddress = bb.getInt(); this.serverIPAddress = bb.getInt(); this.gatewayIPAddress = bb.getInt(); final int hardwareAddressLength = 0xff & this.hardwareAddressLength; this.clientHardwareAddress = new byte[hardwareAddressLength]; bb.get(this.clientHardwareAddress); for (int i = hardwareAddressLength; i < 16; ++i) { bb.get(); } this.serverName = this.readString(bb, 64); this.bootFileName = this.readString(bb, 128); // read the magic cookie // magic cookie bb.get(); bb.get(); bb.get(); bb.get(); // read options while (bb.hasRemaining()) { final DHCPOption option = new DHCPOption(); int code = 0xff & bb.get(); // convert signed byte to int in range // [0,255] option.setCode((byte) code); if (code == 0) { // skip these continue; } else if (code != 255) { if (bb.hasRemaining()) { final int l = 0xff & bb.get(); // convert signed byte to // int in range [0,255] option.setLength((byte) l); if (bb.remaining() >= l) { final byte[] optionData = new byte[l]; bb.get(optionData); option.setData(optionData); } else { // Skip the invalid option and set the END option code = 0xff; option.setCode((byte) code); option.setLength((byte) 0); } } else { // Skip the invalid option and set the END option code = 0xff; option.setCode((byte) code); option.setLength((byte) 0); } } this.options.add(option); if (code == 255) { // remaining bytes are supposed to be 0, but ignore them just in // case break; } } return this; } protected void writeString(final String string, final ByteBuffer bb, final int maxLength) { if (string == null) { for (int i = 0; i < maxLength; ++i) { bb.put((byte) 0x0); } } else { byte[] bytes = null; try { bytes = string.getBytes("ascii"); } catch (final UnsupportedEncodingException e) { throw new RuntimeException("Failure encoding server name", e); } int writeLength = bytes.length; if (writeLength > maxLength) { writeLength = maxLength; } bb.put(bytes, 0, writeLength); for (int i = writeLength; i < maxLength; ++i) { bb.put((byte) 0x0); } } } private static String readString(final ByteBuffer bb, final int maxLength) { final byte[] bytes = new byte[maxLength]; bb.get(bytes); String result = null; try { result = new String(bytes, "ascii").trim(); } catch (final UnsupportedEncodingException e) { throw new RuntimeException("Failure decoding string", e); } return result; } /** * Deserializer function for DHCP packets. * * @return deserializer function */ public static Deserializer deserializer() { return (data, offset, length) -> { checkInput(data, offset, length, MIN_HEADER_LENGTH); ByteBuffer bb = ByteBuffer.wrap(data, offset, length); DHCP dhcp = new DHCP(); dhcp.opCode = bb.get(); dhcp.hardwareType = bb.get(); dhcp.hardwareAddressLength = bb.get(); dhcp.hops = bb.get(); dhcp.transactionId = bb.getInt(); dhcp.seconds = bb.getShort(); dhcp.flags = bb.getShort(); dhcp.clientIPAddress = bb.getInt(); dhcp.yourIPAddress = bb.getInt(); dhcp.serverIPAddress = bb.getInt(); dhcp.gatewayIPAddress = bb.getInt(); final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength; dhcp.clientHardwareAddress = new byte[hardwareAddressLength]; bb.get(dhcp.clientHardwareAddress); for (int i = hardwareAddressLength; i < 16; ++i) { bb.get(); } dhcp.serverName = readString(bb, 64); dhcp.bootFileName = readString(bb, 128); // read the magic cookie // magic cookie bb.get(); bb.get(); bb.get(); bb.get(); // read options boolean foundEndOptionsMarker = false; while (bb.hasRemaining()) { final DHCPOption option = new DHCPOption(); int code = 0xff & bb.get(); // convert signed byte to int in range // [0,255] option.setCode((byte) code); if (code == 0) { // skip these continue; } else if (code != 255) { if (bb.hasRemaining()) { final int l = 0xff & bb.get(); // convert signed byte to // int in range [0,255] option.setLength((byte) l); if (bb.remaining() >= l) { final byte[] optionData = new byte[l]; bb.get(optionData); option.setData(optionData); dhcp.options.add(option); } else { throw new DeserializationException( "Buffer underflow while reading DHCP option"); } } } else if (code == 255) { DHCPOption end = new DHCPOption(); end.setCode((byte) 255); dhcp.options.add(end); // remaining bytes are supposed to be 0, but ignore them just in // case foundEndOptionsMarker = true; break; } } if (!foundEndOptionsMarker) { throw new DeserializationException("DHCP End options marker was missing"); } return dhcp; }; } }