| /* |
| * 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; |
| |
| /** |
| * |
| */ |
| 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_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); |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| @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 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; |
| } |
| } |