diff options
Diffstat (limited to 'framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java')
-rw-r--r-- | framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java new file mode 100644 index 00000000..de5b43fa --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/DHCP.java @@ -0,0 +1,632 @@ +/* + * 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<DHCPOption> options = new ArrayList<DHCPOption>(); + + /** + * @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<DHCPOption> getOptions() { + return this.options; + } + + /** + * @param options + * the options to set + * @return this + */ + public DHCP setOptions(final List<DHCPOption> options) { + this.options = options; + return this; + } + + /** + * @return the packetType base on option 53 + */ + public DHCPPacketType getPacketType() { + final ListIterator<DHCPOption> 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<DHCP> 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; + }; + } +} |