/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* 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 net.floodlightcontroller.packet;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
*
* @author David Erickson (daviderickson@cs.stanford.edu)
*/
public class DHCP extends BasePacket {
/**
* ------------------------------------------
* |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 int MIN_HEADER_LENGTH = 240;
public static byte OPCODE_REQUEST = 0x1;
public static byte OPCODE_REPLY = 0x2;
public static byte HWTYPE_ETHERNET = 0x1;
public enum DHCPOptionCode {
OptionCode_SubnetMask ((byte)1),
OptionCode_Hostname ((byte)12),
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(byte value) {
this.value = value;
}
public byte getValue() {
return 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 opCode;
}
/**
* @param opCode the opCode to set
*/
public DHCP setOpCode(byte opCode) {
this.opCode = opCode;
return this;
}
/**
* @return the hardwareType
*/
public byte getHardwareType() {
return hardwareType;
}
/**
* @param hardwareType the hardwareType to set
*/
public DHCP setHardwareType(byte hardwareType) {
this.hardwareType = hardwareType;
return this;
}
/**
* @return the hardwareAddressLength
*/
public byte getHardwareAddressLength() {
return hardwareAddressLength;
}
/**
* @param hardwareAddressLength the hardwareAddressLength to set
*/
public DHCP setHardwareAddressLength(byte hardwareAddressLength) {
this.hardwareAddressLength = hardwareAddressLength;
return this;
}
/**
* @return the hops
*/
public byte getHops() {
return hops;
}
/**
* @param hops the hops to set
*/
public DHCP setHops(byte hops) {
this.hops = hops;
return this;
}
/**
* @return the transactionId
*/
public int getTransactionId() {
return transactionId;
}
/**
* @param transactionId the transactionId to set
*/
public DHCP setTransactionId(int transactionId) {
this.transactionId = transactionId;
return this;
}
/**
* @return the seconds
*/
public short getSeconds() {
return seconds;
}
/**
* @param seconds the seconds to set
*/
public DHCP setSeconds(short seconds) {
this.seconds = seconds;
return this;
}
/**
* @return the flags
*/
public short getFlags() {
return flags;
}
/**
* @param flags the flags to set
*/
public DHCP setFlags(short flags) {
this.flags = flags;
return this;
}
/**
* @return the clientIPAddress
*/
public int getClientIPAddress() {
return clientIPAddress;
}
/**
* @param clientIPAddress the clientIPAddress to set
*/
public DHCP setClientIPAddress(int clientIPAddress) {
this.clientIPAddress = clientIPAddress;
return this;
}
/**
* @return the yourIPAddress
*/
public int getYourIPAddress() {
return yourIPAddress;
}
/**
* @param yourIPAddress the yourIPAddress to set
*/
public DHCP setYourIPAddress(int yourIPAddress) {
this.yourIPAddress = yourIPAddress;
return this;
}
/**
* @return the serverIPAddress
*/
public int getServerIPAddress() {
return serverIPAddress;
}
/**
* @param serverIPAddress the serverIPAddress to set
*/
public DHCP setServerIPAddress(int serverIPAddress) {
this.serverIPAddress = serverIPAddress;
return this;
}
/**
* @return the gatewayIPAddress
*/
public int getGatewayIPAddress() {
return gatewayIPAddress;
}
/**
* @param gatewayIPAddress the gatewayIPAddress to set
*/
public DHCP setGatewayIPAddress(int gatewayIPAddress) {
this.gatewayIPAddress = gatewayIPAddress;
return this;
}
/**
* @return the clientHardwareAddress
*/
public byte[] getClientHardwareAddress() {
return clientHardwareAddress;
}
/**
* @param clientHardwareAddress the clientHardwareAddress to set
*/
public DHCP setClientHardwareAddress(byte[] clientHardwareAddress) {
this.clientHardwareAddress = clientHardwareAddress;
return this;
}
/**
* Gets a specific DHCP option parameter
* @param opetionCode The option code to get
* @return The value of the option if it exists, null otherwise
*/
public DHCPOption getOption(DHCPOptionCode optionCode) {
for (DHCPOption opt : options) {
if (opt.code == optionCode.value)
return opt;
}
return null;
}
/**
* @return the options
*/
public List<DHCPOption> getOptions() {
return options;
}
/**
* @param options the options to set
*/
public DHCP setOptions(List<DHCPOption> options) {
this.options = options;
return this;
}
/**
* @return the packetType base on option 53
*/
public DHCPPacketType getPacketType() {
ListIterator<DHCPOption> lit = options.listIterator();
while (lit.hasNext()) {
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 serverName;
}
/**
* @param serverName the serverName to set
*/
public DHCP setServerName(String serverName) {
this.serverName = serverName;
return this;
}
/**
* @return the bootFileName
*/
public String getBootFileName() {
return bootFileName;
}
/**
* @param bootFileName the bootFileName to set
*/
public DHCP setBootFileName(String bootFileName) {
this.bootFileName = bootFileName;
return this;
}
@Override
public byte[] serialize() {
// not guaranteed to retain length/exact format
resetChecksum();
// minimum size 240 including magic cookie, options generally padded to 300
int optionsLength = 0;
for (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;
byte[] data = new byte[240+optionsLength+optionsPadLength];
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);
}
}
writeString(this.serverName, bb, 64);
writeString(this.bootFileName, bb, 128);
// magic cookie
bb.put((byte) 0x63);
bb.put((byte) 0x82);
bb.put((byte) 0x53);
bb.put((byte) 0x63);
for (DHCPOption option : this.options) {
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(String string, ByteBuffer bb, 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 (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(byte[] data, int offset, int length) {
ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
if (bb.remaining() < 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();
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 = readString(bb, 64);
this.bootFileName = readString(bb, 128);
// read the magic cookie
// magic cookie
bb.get();
bb.get();
bb.get();
bb.get();
// read options
while (bb.hasRemaining()) {
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()) {
int l = 0xff & bb.get(); // convert signed byte to int in range [0,255]
option.setLength((byte) l);
if (bb.remaining() >= l) {
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(ByteBuffer bb, int maxLength) {
byte[] bytes = new byte[maxLength];
bb.get(bytes);
String result = null;
try {
result = new String(bytes, "ascii").trim();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Failure decoding string", e);
}
return result;
}
}