Skip to content

Instantly share code, notes, and snippets.

@kevinxw
Created Mar 3, 2015
Embed
What would you like to do?
OpenDaylight DHCP and DNS packet parser
package com.kevinxw.net.packet;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.opendaylight.controller.sal.packet.BitBufferHelper;
import org.opendaylight.controller.sal.packet.BufferException;
import org.opendaylight.controller.sal.packet.Packet;
import org.opendaylight.controller.sal.packet.PacketException;
import org.opendaylight.controller.sal.utils.HexEncode;
import org.opendaylight.controller.sal.utils.NetUtils;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.*;
/**
* Here we defines a BOOTP packet, which includes DHCP packet
* About packet format, see http://www.networksorcery.com/enp/protocol/dhcp.htm for reference
* Field value dictionary also refers to https://github.com/FreeRADIUS/freeradius-server/blob/master/share/dictionary.dhcp
* About DHCP options see http://tools.ietf.org/html/rfc2132
* <p/>
* Created by kevin on 6/24/14.
*/
public class BOOTP extends Packet {
private static final String OPCODE = "OpCode";
private static final String HWTYPE = "HardwareType";
private static final String HWADDRLEN = "HardwareAddressLength";
private static final String HOPCOUNT = "HopCount";
private static final String TXID = "TransactionId";
private static final String NUMOFSEC = "NumberOfSecond";
private static final String FLAGS = "Flags";
private static final String CLIENTIPADDR = "ClientIpAddress";
private static final String YOURIPADDR = "YourIpAddress";
private static final String SRVIPADDR = "ServerIpAddress";
private static final String GWIPADDR = "GatewayIpAddress";
private static final String CLIENTHWADDR = "ClientHardwareAddress";
private static final String SRVHOSTNAME = "ServerHostName";
private static final String BOOTFILENAME = "BootFileName";
private static final String MAGICCOOKIE = "MagicCookie";
private static final String OPTIONS = "Options";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPCODE, new ImmutablePair<>(0, 8));
put(HWTYPE, new ImmutablePair<>(8, 8));
put(HWADDRLEN, new ImmutablePair<>(16, 8));
put(HOPCOUNT, new ImmutablePair<>(24, 8));
put(TXID, new ImmutablePair<>(32, 32));
put(NUMOFSEC, new ImmutablePair<>(64, 16));
put(FLAGS, new ImmutablePair<>(80, 16));
put(CLIENTIPADDR, new ImmutablePair<>(96, 32));
put(YOURIPADDR, new ImmutablePair<>(128, 32));
put(SRVIPADDR, new ImmutablePair<>(160, 32));
put(GWIPADDR, new ImmutablePair<>(192, 32));
put(CLIENTHWADDR, new ImmutablePair<>(224, 128)); // 16 bytes long
// we do not read server host name and boot file name here
// but just for us to have a correct header length
// we declare them as well
put(SRVHOSTNAME, new ImmutablePair<>(352, 512)); // 64 bytes
put(BOOTFILENAME, new ImmutablePair<>(864, 1024)); // 128 bytes
// magic cookie, start offset 236 bytes
put(MAGICCOOKIE, new ImmutablePair<>(1888, 32));
put(OPTIONS, new ImmutablePair<>(1920, 0));
}
};
private final Map<String, byte[]> fieldValues = new HashMap();
// DHCP option map
private final List<BootpOption> options = new ArrayList<>();
/**
* Default constructor that creates and sets the hash map values
*/
public BOOTP() {
super();
init();
}
/**
* Constructor that sets the access level for the packet
*/
public BOOTP(boolean writeAccess) {
super(writeAccess);
init();
}
/**
* Init the packet
*/
private void init() {
hdrFieldCoordMap = fieldCoordinates;
hdrFieldsMap = fieldValues;
// here we fill the Server Host Name with zero bytes
setHeaderField(SRVHOSTNAME, new byte[64]);
setHeaderField(BOOTFILENAME, new byte[128]);
}
@Override
public void setHeaderField(String headerField, byte[] readValue) {
if (headerField.equals(OPTIONS) &&
(readValue == null || readValue.length == 0)) {
hdrFieldsMap.remove(headerField);
return;
}
hdrFieldsMap.put(headerField, readValue);
}
@Override
protected void postDeserializeCustomOperation(byte[] data, int startBitOffset)
throws PacketException {
setOptions(rawPayload);
rawPayload = new byte[]{};
}
@Override
public int getfieldnumBits(String fieldName) {
if (OPTIONS.equals(fieldName)) {
byte[] opt = getOptions();
return opt == null ? 0 : opt.length * NetUtils.NumBitsInAByte;
} else
return hdrFieldCoordMap.get(fieldName).getRight();
}
/**
* Deserialize options
*/
private void deserializeOptions() throws PacketException {
options.clear(); // reset all options
byte[] optionsData = getOptions();
// the first byte is option code, second byte is option length
for (int offset = 0, len; offset < optionsData.length; offset += len + 2) {
// first, get option code
byte code = optionsData[offset];
BootpOption.BootpOptionCode optCode = BootpOption.BootpOptionCode.valueOf(code);
len = -1; // set to -1, in case we have zero length option
// some special cases
if (!BootpOption.BootpOptionCode.END.equals(optCode)
&& !BootpOption.BootpOptionCode.PAD.equals(optCode)) {
if (offset + 1 == optionsData.length) { // somethings is wrong
corrupted = true;
break;
}
len = optionsData[offset + 1] & 0xFF;
}
if (optCode == null) // unsupported option
continue;
BootpOption optPayload;
if (optCode.Clazz != null) {
try {
optPayload = optCode.Clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Error parsing option payload for BootP packet", e);
}
} else {
optPayload = new BootpOption();
}
optPayload.deserialize(optionsData, offset * NetUtils.NumBitsInAByte, (len + 2) * NetUtils.NumBitsInAByte);
optPayload.setParent(this);
if (optCode.Clazz != null) {
optPayload.setRawPayload(new byte[]{}); // fully parsed option doesn't have any payload
} else {
byte[] _payload = Arrays.copyOfRange(optionsData, offset + 2, offset + len + 2);
optPayload.setRawPayload(_payload);
}
options.add(optPayload);
}
}
private void serializeOptions() throws PacketException {
int len = 0;
for (BootpOption opt : options) {
len += opt.getHeaderSize();
}
byte[] newOpt = new byte[len / NetUtils.NumBitsInAByte];
for (int i = 0, offset = 0; i < options.size(); i++) {
byte[] dat = options.get(i).serialize();
int numBits = dat.length * NetUtils.NumBitsInAByte;
try {
BitBufferHelper.setBytes(newOpt, dat, offset, numBits);
} catch (BufferException e) {
throw new PacketException(e.getMessage());
}
offset += numBits;
}
setOptions(newOpt); // update option
}
public byte getOpCode() {
return BitBufferHelper.getByte(fieldValues.get(OPCODE));
}
/**
* Set opcode
*
* @param opCode
* @return
*/
public BOOTP setOpCode(byte opCode) {
byte[] _opCode = BitBufferHelper.toByteArray(opCode);
setHeaderField(OPCODE, _opCode);
return this;
}
public byte getHwType() {
return BitBufferHelper.getByte(fieldValues.get(HWTYPE));
}
public BOOTP setHwType(byte hwType) {
byte[] _hwType = BitBufferHelper.toByteArray(hwType);
setHeaderField(HWTYPE, _hwType);
return this;
}
public byte getHwAddrLength() {
return BitBufferHelper.getByte(fieldValues.get(HWADDRLEN));
}
public BOOTP setHwAddrLength(byte len) {
byte[] _len = BitBufferHelper.toByteArray(len);
setHeaderField(HWADDRLEN, _len);
return this;
}
public byte getHopCount() {
return BitBufferHelper.getByte(fieldValues.get(HOPCOUNT));
}
public BOOTP setHopCount(byte hop) {
byte[] _hop = BitBufferHelper.toByteArray(hop);
setHeaderField(HOPCOUNT, _hop);
return this;
}
public int getTxId() {
return BitBufferHelper.getInt(fieldValues.get(TXID));
}
public BOOTP setTxId(int txId) {
byte[] _txId = BitBufferHelper.toByteArray(txId);
setHeaderField(TXID, _txId);
return this;
}
public short getNumOfSec() {
return BitBufferHelper.getShort(fieldValues.get(NUMOFSEC));
}
public BOOTP setNumOfSec(short sec) {
byte[] _sec = BitBufferHelper.toByteArray(sec);
setHeaderField(NUMOFSEC, _sec);
return this;
}
public short getFlags() {
return BitBufferHelper.getShort(fieldValues.get(FLAGS));
}
public BOOTP setFlags(short flags) {
byte[] _opCode = BitBufferHelper.toByteArray(flags);
setHeaderField(FLAGS, _opCode);
return this;
}
public int getClientIpAddr() {
return BitBufferHelper.getInt(fieldValues.get(CLIENTIPADDR));
}
public BOOTP setClientIpAddr(int addr) {
byte[] _addr = BitBufferHelper.toByteArray(addr);
setHeaderField(CLIENTIPADDR, _addr);
return this;
}
public int getYourIpAddr() {
return BitBufferHelper.getInt(fieldValues.get(YOURIPADDR));
}
public BOOTP setYourIpAddr(int addr) {
byte[] _addr = BitBufferHelper.toByteArray(addr);
setHeaderField(YOURIPADDR, _addr);
return this;
}
public int getSrvIpAddr() {
return BitBufferHelper.getInt(fieldValues.get(SRVIPADDR));
}
public BOOTP setSrvIpAddr(int addr) {
byte[] _addr = BitBufferHelper.toByteArray(addr);
setHeaderField(SRVIPADDR, _addr);
return this;
}
public int getGwIpAddr() {
return BitBufferHelper.getInt(fieldValues.get(GWIPADDR));
}
public BOOTP setGwIpAddr(int addr) {
byte[] _addr = BitBufferHelper.toByteArray(addr);
setHeaderField(GWIPADDR, _addr);
return this;
}
public byte[] getClientHwAddr() {
byte[] _addr = fieldValues.get(CLIENTHWADDR);
return Arrays.copyOfRange(_addr, 0, getHwAddrLength());
}
public BOOTP setClientHwAddr(byte[] addr) {
setHwAddrLength((byte) addr.length);
setHeaderField(CLIENTHWADDR, addr);
return this;
}
public int getMagicCookie() {
return BitBufferHelper.getInt(fieldValues.get(MAGICCOOKIE));
}
public BOOTP setMagicCookie(int cookie) {
byte[] _cookie = BitBufferHelper.toByteArray(cookie);
setHeaderField(MAGICCOOKIE, _cookie);
return this;
}
public BootpType getBootpType() {
return BootpType.valueOf(getMagicCookie());
}
/**
* gets the Options stored
*
* @return the options
*/
public byte[] getOptions() {
return fieldValues.get(OPTIONS);
}
public BOOTP setOptions(byte[] val) {
setHeaderField(OPTIONS, val);
try {
deserializeOptions();
} catch (PacketException e) {
corrupted = true;
throw new RuntimeException("Error setting BOOTP options.", e);
}
return this;
}
/**
* Get options' order
*
* @return
*/
public BootpOption[] getOptionsAsArray() {
BootpOption[] res = new BootpOption[options.size()];
return options.toArray(res);
}
/**
* Remove an option
*
* @param code optionCode
* @return
*/
public BOOTP removeOption(byte code) {
for (Iterator<BootpOption> it = options.iterator(); it.hasNext(); ) {
if (it.next().getOptCode() == code) {
it.remove(); // remove the first one merely
break;
}
}
try {
serializeOptions();
} catch (PacketException e) {
corrupted = true;
throw new RuntimeException("Error setting BOOTP options.", e);
}
return this;
}
public <T extends BootpOption> BOOTP insertOption(T option) {
byte code = option.getOptCode();
// by default we are going to insert option by code number
int i = 0;
for (byte curCode; i < options.size(); i++) {
curCode = options.get(i).getOptCode();
if (code <= curCode || curCode == -1)
break;
}
options.add(i, option);
try {
serializeOptions();
} catch (PacketException e) {
corrupted = true;
throw new RuntimeException("Error setting BOOTP options.", e);
}
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Bootp Packet {")
.append("\n - OpCode: ").append(OpCode.valueOf(getOpCode()))
.append("\n - HardwareType: ").append(HardwareType.valueOf(getHwType()))
.append("\n - HardwareAddressLength: ").append(getHwAddrLength())
.append("\n - HopCount: ").append(getHopCount())
.append("\n - TransactionId: 0x").append(Integer.toHexString(getTxId()))
.append("\n - NumberOfSecond: ").append(getNumOfSec())
.append("\n - Flags: ").append(FlagType.valueOf(getFlags()))
.append("\n - ClientIpAddress: ").append(NetUtils.getInetAddress(getClientIpAddr()))
.append("\n - YourIpAddress: ").append(NetUtils.getInetAddress(getYourIpAddr()))
.append("\n - ServerIpAddress: ").append(NetUtils.getInetAddress(getSrvIpAddr()))
.append("\n - GatewayIpAddress: ").append(NetUtils.getInetAddress(getGwIpAddr()))
.append("\n - ClientHardwareAddress: ").append(HexEncode.bytesToHexStringFormat(getClientHwAddr()))
.append("\n - MagicCookie: ").append(BootpType.valueOf(getMagicCookie()))
.append("\n - OPTIONS [");
// append all the options
for (BootpOption opt : getOptionsAsArray()) {
// skip meaningless padding
if (BootpOption.BootpOptionCode.PAD.equals(opt.getOptCode()) || BootpOption.BootpOptionCode.END.equals(opt.getOptCode()))
continue;
sb.append("\n + ").append(opt.toString());
}
sb.append("\n ]")
.append("\n}");
return sb.toString();
}
/**
* Hardware Type field of DHCP packet
*/
public static enum HardwareType {
ETHERNET(1),
EXP_ETHERNET(2),
AX25(3),
PROTEON_TOKEN_RING(4),
CHAOS(5),
IEEE802(6),
ARCNET(7),
HYPERCHANNEL(8),
LANSTAR(9),
AUTONET_SHORT_ADDRESS(10),
LOCALTALK(11),
LOCALNET(12),
ULTRA_LINK(13),
SMDS(14),
FRAME_RELAY(15),
ATM_16(16), // NOTICE WE HAVE THREE ATM HERE 16, 19 & 21
HDLC(17),
FIBER_CHANNEL(18),
ATM_19(19),
SERIAL_LINE(20),
ATM_21(21),
MIL_STD_188_220(22),
METRICOM(23),
IEEE1394(24),
MAPOS(25),
TWINAXIAL(26),
EUI_64(27),
HIPARP(28),
IP_OVER_ISO_7816_3(29),
ARPSEC(30),
IPSEC_TUNNEL(31),
INFINIBAND(32),
CAI_TIA_102(33),
WIEGAND_INTERFACE(34),
PURE_IP(35),;
public final byte value;
private HardwareType(int val) {
value = (byte) val;
}
public static HardwareType valueOf(int val) {
return valueOf((byte) (val & 0xFF));
}
public static HardwareType valueOf(byte val) {
for (HardwareType c : values()) {
if ((c.value & val) != 0)
return c;
}
return null;
}
}
public static enum FlagType {
UNICAST(0x0000),
BROADCAST(0x8000);
public final short value;
private FlagType(int val) {
value = (short) val;
}
public static FlagType valueOf(int val) {
return valueOf((short) (val & 0xFFFF));
}
public static FlagType valueOf(short val) {
for (FlagType c : values()) {
if (c.value == val)
return c;
}
return null;
}
}
public static enum BootpType {
DHCP(1669485411);
public final int value;
private BootpType(int val) {
value = val;
}
public static BootpType valueOf(int value) {
for (BootpType c : values()) {
if (c.value == value)
return c;
}
return null; // null refers unknown
}
}
public static enum DhcpState {
AVAILABLE(1),
ACTIVE(2),
EXPIRED(3),
RELEASED(4),
ABANDONED(5),
RESET(6),
REMOTE(7),
TRANSITIONING(8),;
public final byte value;
private DhcpState(int val) {
value = (byte) val;
}
public static DhcpState valueOf(int value) {
return valueOf((byte) (value & 0xFF));
}
public static DhcpState valueOf(byte value) {
for (DhcpState c : values()) {
if (c.value == value)
return c;
}
return null;
}
}
/**
* DHCP OpCode
*/
public static enum OpCode {
BOOT_REQUEST(1),
BOOT_REPLY(2),;
public final byte value;
private OpCode(int val) {
value = (byte) val;
}
public static OpCode valueOf(int value) {
return valueOf((byte) (value & 0xFF));
}
public static OpCode valueOf(byte value) {
for (OpCode c : values()) {
if (c.value == value)
return c;
}
return null;
}
public boolean equals(byte value) {
return this.value == value;
}
}
/**
* Code = 1
*/
public static class SubnetMaskOption extends BootpOption {
private static final String OPTION_SUBNETMASK = "SubnetMask";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_SUBNETMASK, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getSubnetMask() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_SUBNETMASK));
}
public void setSubnetMask(int mask) {
byte[] _mask = BitBufferHelper.toByteArray(mask);
setHeaderField(OPTION_SUBNETMASK, _mask);
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(NetUtils.getInetAddress(getSubnetMask()).getHostAddress())
.toString();
}
}
/**
* Code = 3
*/
public static class RouterAddressOption extends BootpOption {
private static final String OPTION_ROUTER_ADDRRESS = "RouterAddress";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_ROUTER_ADDRRESS, new ImmutablePair<>(16, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
@Override
public int getfieldnumBits(String fieldName) {
if (fieldName.equals(OPTION_ROUTER_ADDRRESS))
return getHeaderLen() * NetUtils.NumBitsInAByte;
else
return hdrFieldCoordMap.get(fieldName).getRight();
}
public byte[] getAddress() {
return fieldValues.get(OPTION_ROUTER_ADDRRESS);
}
public RouterAddressOption setAddress(byte[] val) {
if (val.length > 255 || val.length % 4 != 0)
throw new RuntimeException("Error setting address. Address's length should be a multiple of 4 and less than 255.");
setHeaderLen((byte) val.length);
setHeaderField(OPTION_ROUTER_ADDRRESS, val);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(super.toString())
.append(" ");
byte[] addr = getAddress();
for (int i = 0; i < addr.length; i += 4) {
try {
sb
.append(Inet4Address.getByAddress(new byte[]{addr[i], addr[i + 1], addr[i + 2], addr[i + 3]}).getHostAddress())
.append(",");
} catch (UnknownHostException e) {
}
}
if (sb.charAt(sb.length() - 1) == ',')
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
/**
* Code = 6
*/
public static class DomainNameServerOption extends BootpOption {
private static final String OPTION_DNS = "DomainNameServer";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_DNS, new ImmutablePair<>(16, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int[] getAddresses() {
byte[] _val = fieldValues.get(OPTION_DNS);
int[] addrs = new int[_val.length / 4];
for (int i = 0; i < addrs.length; i++) {
addrs[i] = BitBufferHelper.getInt(Arrays.copyOfRange(_val, i * 4, (i + 1) * 4));
}
return addrs;
}
public DomainNameServerOption setAddresses(int[] val) {
byte[] _val = new byte[val.length * 4];
for (int i = 0; i < val.length; i++) {
byte[] v = BitBufferHelper.toByteArray(val[i]);
for (int j = 0; j < 4; j++)
_val[i * 4 + j] = v[j];
}
setHeaderField(OPTION_DNS, _val);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(super.toString())
.append(" ");
for (int addr : getAddresses())
sb.append(NetUtils.getInetAddress(addr).getHostAddress()).append(",");
if (sb.charAt(sb.length() - 1) == ',')
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
/**
* Code = 12
*/
public static class HostNameOption extends BootpOption {
private static final String OPTION_HOST_NAME = "HostName";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_HOST_NAME, new ImmutablePair<>(16, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public String getName() {
return new String(fieldValues.get(OPTION_HOST_NAME));
}
public HostNameOption setName(String val) {
byte[] _val = val.getBytes();
if (_val.length > 255)
throw new RuntimeException("Host name is longer than 255 characters.");
setHeaderLen((byte) _val.length);
setHeaderField(OPTION_HOST_NAME, _val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(getName())
.toString();
}
}
/**
* Code = 15
*/
public static class DomainNameOption extends BootpOption {
private static final String OPTION_DOMAIN_NAME = "DomainName";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_DOMAIN_NAME, new ImmutablePair<>(16, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public String getName() {
return new String(fieldValues.get(OPTION_DOMAIN_NAME));
}
public DomainNameOption setName(String val) {
byte[] _val = val.getBytes();
if (_val.length > 255)
throw new RuntimeException("Domain name is longer than 255 characters.");
setHeaderLen((byte) _val.length);
setHeaderField(OPTION_DOMAIN_NAME, _val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(getName())
.toString();
}
}
/**
* Code = 50
*/
public static class RequestedIpAddressOption extends BootpOption {
private static final String OPTION_REQUESTED_IP_ADDRRESS = "RequestedIpAddress";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_REQUESTED_IP_ADDRRESS, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getAddress() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_REQUESTED_IP_ADDRRESS));
}
public RequestedIpAddressOption setAddress(int val) {
byte[] _val = BitBufferHelper.toByteArray(val);
setHeaderField(OPTION_REQUESTED_IP_ADDRRESS, _val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(NetUtils.getInetAddress(getAddress()).getHostAddress())
.toString();
}
}
/**
* Code = 51
*/
public static class IpAddressLeaseTimeOption extends BootpOption {
private static final String OPTION_LEASETIME = "LeaseTime";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_LEASETIME, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getLeaseTime() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_LEASETIME));
}
public IpAddressLeaseTimeOption setLeaseTime(int time) {
byte[] _time = BitBufferHelper.toByteArray(time);
setHeaderField(OPTION_LEASETIME, _time);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(getLeaseTime())
.append(" sec")
.toString();
}
}
/**
* Code = 53
*/
public static class DhcpMessageTypeOption extends BootpOption {
private static final String OPTION_DHCP_MSG_TYPE = "DhcpMessageType";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_DHCP_MSG_TYPE, new ImmutablePair<>(16, 8));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public byte getType() {
return BitBufferHelper.getByte(fieldValues.get(OPTION_DHCP_MSG_TYPE));
}
public DhcpMessageTypeOption setType(byte val) {
byte[] _val = BitBufferHelper.toByteArray(val);
setHeaderField(OPTION_DHCP_MSG_TYPE, _val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(MessageType.valueOf(getType()))
.toString();
}
public static enum MessageType {
DO_NOT_RESPOND(0),
DISCOVER(1),
OFFER(2),
REQUEST(3),
DECLINE(4),
ACK(5),
NAK(6),
RELEASE(7),
INFORM(8),
FORCE_RENEW(9),
LEASE_QUERY(10),
LEASE_UNASSIGNED(11),
LEASE_UNKNOWN(12),
LEASE_ACTIVE(13),
BULK_LEASE_QUERY(14),
LEASE_QUERY_DONE(15),;
public final byte value;
private MessageType(int val) {
value = (byte) val;
}
public static MessageType valueOf(int value) {
return valueOf((byte) (value & 0xFF));
}
public static MessageType valueOf(byte value) {
for (MessageType c : values()) {
if (c.value == value)
return c;
}
return null;
}
public boolean equals(byte value) {
return this.value == value;
}
}
}
/**
* Code = 54
*/
public static class ServerIdentifierOption extends BootpOption {
private static final String OPTION_SRV_IDENT = "ServerIdentifier";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_SRV_IDENT, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getServerIdentifier() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_SRV_IDENT));
}
public ServerIdentifierOption setSserverIdentifier(int val) {
byte[] _val = BitBufferHelper.toByteArray(val);
setHeaderField(OPTION_SRV_IDENT, _val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(NetUtils.getInetAddress(getServerIdentifier()).getHostAddress())
.toString();
}
}
/**
* Code = 55
*/
public static class ParameterListOption extends BootpOption {
private static final String OPTION_PARAM_LIST = "ParameterList";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_PARAM_LIST, new ImmutablePair<>(16, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public byte[] getParameterList() {
return fieldValues.get(OPTION_PARAM_LIST);
}
public ParameterListOption setParameterList(byte[] params) {
if (params.length > 255)
throw new RuntimeException("Parameter list has more than 255 fields.");
setHeaderLen((byte) params.length);
setHeaderField(OPTION_PARAM_LIST, params);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(super.toString())
.append(" ");
for (byte para : getParameterList()) {
BootpOptionCode opt = BootpOptionCode.valueOf(para);
sb.append(opt == null ? (int) (para & 0xFF) : opt).append(",");
}
if (sb.charAt(sb.length() - 1) == ',')
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
/**
* Code = 58
*/
public static class RenewalTimeOption extends BootpOption {
private static final String OPTION_RENEWAL_TIME = "RenewalTime";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_RENEWAL_TIME, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getRenewalTime() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_RENEWAL_TIME));
}
public RenewalTimeOption setRenewalTime(int time) {
byte[] _time = BitBufferHelper.toByteArray(time);
setHeaderField(OPTION_RENEWAL_TIME, _time);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(getRenewalTime())
.append(" sec")
.toString();
}
}
/**
* Code = 59
*/
public static class RebindingTimeOption extends BootpOption {
private static final String OPTION_REBINDING_TIME = "RebindingTime";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_REBINDING_TIME, new ImmutablePair<>(16, 32));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
public int getRebindingTime() {
return BitBufferHelper.getInt(fieldValues.get(OPTION_REBINDING_TIME));
}
public RebindingTimeOption setRebindingTime(int time) {
byte[] _time = BitBufferHelper.toByteArray(time);
setHeaderField(OPTION_REBINDING_TIME, _time);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" ")
.append(getRebindingTime())
.append(" sec")
.toString();
}
}
/**
* Code = 61
*/
public static class ClientIdOption extends BootpOption {
private static final String OPTION_HW_TYPE = "HardwareType";
private static final String OPTION_CLIENT_ID = "ClientIdentifier";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
put(OPTION_HW_TYPE, new ImmutablePair<>(16, 8));
put(OPTION_CLIENT_ID, new ImmutablePair<>(24, 0));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
@Override
public int getfieldnumBits(String fieldName) {
if (OPTION_CLIENT_ID.equals(fieldName))
return (getHeaderLen() - 1) * NetUtils.NumBitsInAByte;
else
return fieldCoordinates.get(fieldName).getRight();
}
public byte getHwType() {
return BitBufferHelper.getByte(fieldValues.get(OPTION_HW_TYPE));
}
public ClientIdOption setHwType(byte type) {
byte[] _type = BitBufferHelper.toByteArray(type);
setHeaderField(OPTION_HW_TYPE, _type);
return this;
}
public byte[] getClientIdentifier() {
return fieldValues.get(OPTION_CLIENT_ID);
}
public ClientIdOption setClientIdentifier(byte[] val) {
if (val.length > 254)
throw new RuntimeException("Client Identifier is longer than 254.");
setHeaderLen((byte) (val.length + 1));
setHeaderField(OPTION_CLIENT_ID, val);
return this;
}
@Override
public String toString() {
return new StringBuilder()
.append(super.toString())
.append(" hardwareType=")
.append(HardwareType.valueOf(getHwType()))
.append(" id=")
.append(HexEncode.bytesToHexStringFormat(getClientIdentifier()))
.toString();
}
}
/**
* Code = 0
*/
public static class PadOption extends BootpOption {
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
}
/**
* Code = 255
*/
public static class EndOption extends BootpOption {
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
// NOTE for PAD and END option, there is no length field
put(OPTION_CODE, new ImmutablePair<>(0, 8));
}
};
@Override
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
}
/**
* Base class for all Bootp options
*/
public static class BootpOption extends Packet {
protected static final String OPTION_CODE = "OptionCode";
protected static final String OPTION_HEADERLENGTH = "HeaderLength";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
put(OPTION_CODE, new ImmutablePair<>(0, 8));
put(OPTION_HEADERLENGTH, new ImmutablePair<>(8, 8));
}
};
protected final Map<String, byte[]> fieldValues;
protected final BootpOptionCode optionCode;
protected final boolean isCodeOnlyOption;
/**
* Default constructor that creates and sets the hash map values
*/
public BootpOption() {
super();
fieldValues = new HashMap<>();
optionCode = BootpOptionCode.valueOf(this.getClass());
isCodeOnlyOption = BootpOptionCode.PAD.equals(optionCode) || BootpOptionCode.END.equals(optionCode);
init();
}
/**
* Constructor that sets the access level for the packet
*/
public BootpOption(boolean writeAccess) {
super(writeAccess);
fieldValues = new HashMap<>();
optionCode = BootpOptionCode.valueOf(this.getClass());
isCodeOnlyOption = BootpOptionCode.PAD.equals(optionCode) || BootpOptionCode.END.equals(optionCode);
init();
}
/**
* Init the packet
*/
protected void init() {
hdrFieldCoordMap = getFieldCoordinates();
hdrFieldsMap = fieldValues;
if (optionCode != null) {
setOptCode(optionCode.value);
if (!isCodeOnlyOption)
setHeaderLen(optionCode.defaultLenInByte);
}
}
/**
* Shoule be overridden
*
* @return
*/
protected Map<String, Pair<Integer, Integer>> getFieldCoordinates() {
return fieldCoordinates;
}
@Override
public int getHeaderSize() {
// additional two bytes, one byte for opcode, one byte for length
return isCodeOnlyOption ? NetUtils.NumBitsInAByte
: (getHeaderLen() + 2) * NetUtils.NumBitsInAByte;
}
@Override
public int getfieldnumBits(String fieldName) {
int len = hdrFieldCoordMap.get(fieldName).getRight();
return len != 0 ? len : getHeaderLen() * NetUtils.NumBitsInAByte;
}
@Override
protected void postDeserializeCustomOperation(byte[] data, int startBitOffset)
throws PacketException {
// option code mismatch
if (optionCode != null && data[startBitOffset / NetUtils.NumBitsInAByte] != optionCode.value)
corrupted = true;
}
public byte getHeaderLen() {
return isCodeOnlyOption ? 0 : BitBufferHelper.getByte(fieldValues.get(OPTION_HEADERLENGTH));
}
/**
* Notice change the header length here is very dangerous
* As for some options, their length is quite fixed
*
* @param len
* @param <T>
* @return
*/
protected <T extends BootpOption> T setHeaderLen(byte len) {
if (!isCodeOnlyOption) {
byte[] _len = BitBufferHelper.toByteArray(len);
setHeaderField(OPTION_HEADERLENGTH, _len);
}
return (T) this;
}
public byte getOptCode() {
return BitBufferHelper.getByte(fieldValues.get(OPTION_CODE));
}
/**
* It's unreasonable to change option code, as it will change the class
* of option instance. We leave it for internal use
*
* @param code
* @param <T>
* @return
*/
protected <T extends BootpOption> T setOptCode(byte code) {
byte[] _code = BitBufferHelper.toByteArray(code);
setHeaderField(OPTION_CODE, _code);
return (T) this;
}
@Override
public String toString() {
return new StringBuilder()
.append(BootpOptionCode.valueOf(getOptCode()))
.toString();
}
public static enum BootpOptionCode {
PAD(0, PadOption.class, 0),
SUBNET_MASK(1, SubnetMaskOption.class, 4), // subnet mask option must be inserted before router option
TIME_OFFSET(2),
ROUTER_ADDRESS(3, RouterAddressOption.class, 4),
TIME_SERVER(4),
IEN_116_NAME_SERVER(5),
DOMAIN_NAME_SERVER(6, DomainNameServerOption.class, 0),
LOG_SERVER(7),
QUOTES_SERVER(8),
LPR_SERVER(9),
IMPRESS_SERVER(10),
RLP_SERVER(11),
HOSTNAME(12, HostNameOption.class, 1), // min length is 1
BOOT_FILE_SIZE(13),
MERIT_DUMP_FILE(14),
DOMAIN_NAME(15, DomainNameOption.class, 1), // min length is 1
SWAP_SERVER(16),
ROOT_PATH(17),
BOOTP_EXTENSIONS_PATH(18),
IP_FORWARD_ENABLE(19),
SOURCE_ROUTE_ENABLE(20),
POLICY_FILTER(21),
MAX_DATAGRAM_REASSEMBLY_SZ(22),
DEFAULT_IP_TTL(23),
PATH_MTU_AGING_TIMEOUT(24),
PATH_MTU_PLATEAU_TABLE(25),
INTERFACE_MTU_SIZE(26),
ALL_SUBNETS_ARE_LOCAL(27),
BROADCAST_ADDRESS(28),
PERFORM_MASK_DISCOVERY(29),
PROVIDE_MASK_TO_OTHERS(30),
PERFORM_ROUTER_DISCOVERY(31),
ROUTER_SOLICITATION_ADDRESS(32),
STATIC_ROUTES(33),
TRAILER_ENCAPSULATION(34),
ARP_CACHE_TIMEOUT(35),
ETHERNET_ENCAPSULATION(36),
DEFAULT_TCP_TTL(37),
KEEP_ALIVE_INTERVAL(38),
KEEP_ALIVE_GARBAGE(39),
NIS_DOMAIN_NAME(40),
NIS_SERVERS(41),
NTP_SERVERS(42),
VENDOR(43),
NETBIOS_NAME_SERVERS(44),
NETBIOS_DGM_DIST_SERVERS(45),
NETBIOS_NODE_TYPE(46),
NETBIOS(47),
X_WINDOW_FONT_SERVER(48),
X_WINDOW_DISPLAY_MGR(49),
REQUESTED_IP_ADDRESS(50, RequestedIpAddressOption.class, 4),
IP_ADDRESS_LEASE_TIME(51, IpAddressLeaseTimeOption.class, 4),
OVERLOAD(52),
DHCP_MESSAGE_TYPE(53, DhcpMessageTypeOption.class, 1), // min length = 1
SERVER_IDENTIFIER(54, ServerIdentifierOption.class, 4),
PARAMETER_REQUEST_LIST(55, ParameterListOption.class, 1), // min length = 1
ERROR_MESSAGE(56),
MAXIMUM_DHCP_MSG_SIZE(57),
RENEWAL_TIME(58, RenewalTimeOption.class, 4),
REBINDING_TIME(59, RebindingTimeOption.class, 4),
CLASS_IDENTIFIER(60),
CLIENT_IDENTIFIER(61, ClientIdOption.class, 2), // min length = 2
NETWARE_DOMAIN_NAME(62),
NETWARE_SUB_OPTIONS(63),
NIS_CLIENT_DOMAIN_NAME(64),
NIS_SERVER_ADDRESS(65),
TFTP_SERVER_NAME(66),
BOOT_FILE_NAME(67),
HOME_AGENT_ADDRESS(68),
SMTP_SERVER_ADDRESS(69),
POP3_SERVER_ADDRESS(70),
NNTP_SERVER_ADDRESS(71),
WWW_SERVER_ADDRESS(72),
FINGER_SERVER_ADDRESS(73),
IRC_SERVER_ADDRESS(74),
STREETTALK_SERVER_ADDRESS(75),
STDA_SERVER_ADDRESS(76),
USER_CLASS(77),
DIRECTORY_AGENT(78),
SERVICE_SCOPE(79),
RAPID_COMMIT(80),
CLIENT_FQDN(81),
RELAY_AGENT_INFORMATION(82),
ISNS(83),
NDS_SERVERS(85),
NDS_TREE_NAME(86),
NDS_CONTEXT(87),
AUTHENTICATION(90),
CLIENT_LAST_TXN_TIME(91),
ASSOCIATED_IP(92),
CLIENT_SYSTEM(93),
CLIENT_NDI(94),
LDAP(95),
UUID_GUID(97),
USER_AUTH(98),
NETINFO_ADDRESS(112),
NETINFO_TAG(113),
URL(114),
AUTO_CONFIG(116),
NAME_SERVICE_SEARCH(117),
SUBNET_SELECTION_OPTION(118),
DOMAIN_SEARCH(119),
SIP_SERVERS_DHCP_OPTION(120),
CLASSLESS_STATIC_ROUTE(121),
CCC(122),
GEOCONF_OPTION(123),
V_I_VENDOR_CLASS(124),
V_I_VENDOR_SPECIFIC(125),
ETHERBOOT(128),
TFTP_SERVER_IP_ADDRESS(128),
CALL_SERVER_IP_ADDRESS(129),
ETHERNET_INTERFACE(130),
VENDOR_DISCRIMINATION_STR(130),
REMOTE_STATS_SVR_IP_ADDRESS(131),
IEEE_802_1P_VLAN_ID(132),
IEEE_802_1Q_L2_PRIORITY(133),
DIFFSERV_CODE_POINT(134),
HTTP_PROXY(135),
MICROSOFT_CLASSLESS_STATIC_ROUTE(249),
WEB_PROXY_AUTO_DISCOVERY(252),
END(255, EndOption.class, 0),;
public final byte value;
public final Class<? extends BootpOption> Clazz;
public final byte defaultLenInByte;
private BootpOptionCode(int val) {
value = (byte) val;
Clazz = null;
defaultLenInByte = 0;
}
private BootpOptionCode(int val, Class<? extends BootpOption> clazz, int len) {
value = (byte) val;
Clazz = clazz;
defaultLenInByte = (byte) (len & 0xFF);
}
public static BootpOptionCode valueOf(int value) {
return valueOf((byte) (value & 0xFF));
}
public static BootpOptionCode valueOf(byte value) {
for (BootpOptionCode c : values()) {
if (c.value == value)
return c;
}
return null;
}
public static BootpOptionCode valueOf(Class<? extends BootpOption> clazz) {
for (BootpOptionCode c : values()) {
if (c.Clazz == clazz)
return c;
}
return null;
}
public boolean equals(byte value) {
return this.value == value;
}
}
}
}
package com.kevinxw.net.packet;
import org.junit.Ignore;
import org.junit.Test;
import org.opendaylight.controller.sal.packet.Ethernet;
import org.opendaylight.controller.sal.packet.IPv4;
import org.opendaylight.controller.sal.packet.Packet;
import org.opendaylight.controller.sal.packet.UDP;
import org.opendaylight.controller.sal.utils.HexEncode;
import org.opendaylight.controller.sal.utils.NetUtils;
import java.util.ArrayList;
import static org.junit.Assert.*;
public class BOOTPTest {
/**
* Convert hex string to byte array
*
* @param s
* @return
*/
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* Create a DHCP payload. Notice this is payload merely, ethernet layer
* and ip layer are not included
*
* @param msgType Merely DHCP Discover, Request, Offer, ACK are supported
* @param txId
* @param clientHwAddr
* @return
*/
public static BOOTP createDhcpPayload(BOOTP.DhcpMessageTypeOption.MessageType msgType,
int txId, byte[] clientHwAddr, String hostName) {
BOOTP bootp = new BOOTP();
bootp.setOpCode(BOOTP.OpCode.BOOT_REQUEST.value);
bootp.setHwType(BOOTP.HardwareType.ETHERNET.value);
bootp.setHwAddrLength((byte) 6); // MAC address length
bootp.setTxId(txId);
bootp.setHopCount((byte) 0);
bootp.setNumOfSec((byte) 0);
bootp.setFlags(BOOTP.FlagType.UNICAST.value);
bootp.setClientIpAddr(0);
bootp.setSrvIpAddr(0);
bootp.setGwIpAddr(0);
// we do the client hardware address padding here
// this field requires 16 bytes, while normally ethernet addr has
// only 6 bytes
if (clientHwAddr.length < 16) {
byte[] tmpHwAddr = clientHwAddr;
clientHwAddr = new byte[16];
int i = 0;
for (; i < tmpHwAddr.length; i++)
clientHwAddr[i] = tmpHwAddr[i];
for (; i < 16; i++)
clientHwAddr[i] = 0;
}
bootp.setClientHwAddr(clientHwAddr);
bootp.setMagicCookie(BOOTP.BootpType.DHCP.value);
// DHCP message option
BOOTP.DhcpMessageTypeOption msgTypeOption = new BOOTP.DhcpMessageTypeOption();
msgTypeOption.setType(msgType.value);
bootp.insertOption(msgTypeOption);
// DHCP client identifier option
BOOTP.ClientIdOption clientIdOption = new BOOTP.ClientIdOption();
clientIdOption.setHwType(BOOTP.HardwareType.ETHERNET.value);
clientIdOption.setClientIdentifier(clientHwAddr);
bootp.insertOption(clientIdOption);
// DHCP requested IP address
BOOTP.RequestedIpAddressOption requestedIpAddressOption = new BOOTP.RequestedIpAddressOption();
requestedIpAddressOption.setAddress(0);
bootp.insertOption(requestedIpAddressOption);
// Parameter list option
BOOTP.ParameterListOption parameterListOption = new BOOTP.ParameterListOption();
parameterListOption.setParameterList(new byte[]{
BOOTP.BootpOption.BootpOptionCode.SUBNET_MASK.value,
BOOTP.BootpOption.BootpOptionCode.ROUTER_ADDRESS.value,
BOOTP.BootpOption.BootpOptionCode.DOMAIN_NAME_SERVER.value,
BOOTP.BootpOption.BootpOptionCode.NETBIOS_NAME_SERVERS.value,
BOOTP.BootpOption.BootpOptionCode.NETBIOS_NODE_TYPE.value,
BOOTP.BootpOption.BootpOptionCode.PERFORM_ROUTER_DISCOVERY.value,
BOOTP.BootpOption.BootpOptionCode.STATIC_ROUTES.value,
BOOTP.BootpOption.BootpOptionCode.CLASSLESS_STATIC_ROUTE.value,
});
bootp.insertOption(parameterListOption);
bootp.insertOption(new BOOTP.EndOption());
return bootp;
}
@Test
@Ignore
public void testToString() throws Exception {
final byte[] pkt = hexStringToByteArray("0101060000003d1e00000000000000000000000000" +
"00000000000000000b8201fc420000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"0000000000638253633501033d0701000b8201fc42" +
"3204c0a8000a3604c0a8000137040103062aff00"
);
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
System.out.println(bootp.toString());
}
@Test
public void testSetOpCode() throws Exception {
BOOTP bootp = new BOOTP();
final byte val = BOOTP.OpCode.BOOT_REQUEST.value; // DHCPREQUEST
bootp.setOpCode(val);
assertEquals(val, bootp.getOpCode());
}
@Test
public void testSetHwType() throws Exception {
BOOTP bootp = new BOOTP();
final byte val = BOOTP.HardwareType.ETHERNET.value; // ETHERNET
bootp.setHwType(val);
assertEquals(val, bootp.getHwType());
}
@Test
public void testSetHwAddrLength() throws Exception {
BOOTP bootp = new BOOTP();
final byte val = NetUtils.MACAddrLengthInBytes; // ETH ADDR
bootp.setHwAddrLength(val);
assertEquals(val, bootp.getHwAddrLength());
}
@Test
public void testSetHopCount() throws Exception {
BOOTP bootp = new BOOTP();
final byte val = 3; // HOP COUNT
bootp.setHopCount(val);
assertEquals(val, bootp.getHopCount());
}
@Test
public void testSetTxId() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0x3903F326; // TRANSACTION ID
bootp.setTxId(val);
assertEquals(val, bootp.getTxId());
}
@Test
public void testSetNumOfSec() throws Exception {
BOOTP bootp = new BOOTP();
final short val = 4; // NORMALLY IT SHOULD BE ZERO, BUT FOR TESTING HERE
bootp.setNumOfSec(val);
assertEquals(val, bootp.getNumOfSec());
}
@Test
public void testSetFlags() throws Exception {
BOOTP bootp = new BOOTP();
final short val = BOOTP.FlagType.BROADCAST.value; // NORMALLY IT SHOULD BE ZERO, BUT FOR TESTING HERE
bootp.setFlags(val);
assertEquals(val, bootp.getFlags());
final short val2 = BOOTP.FlagType.UNICAST.value;
bootp.setFlags(val2);
assertEquals(val2, bootp.getFlags());
}
@Test
public void testSetClientIpAddr() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0xC0A80164; // CLIENT IP
bootp.setClientIpAddr(val);
assertEquals(val, bootp.getClientIpAddr());
}
@Test
public void testSetYourIpAddr() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0xC0A80102; // YOUR IP
bootp.setYourIpAddr(val);
assertEquals(val, bootp.getYourIpAddr());
}
@Test
public void testSetSrvIpAddr() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0x8D590000; // SERVER IP
bootp.setSrvIpAddr(val);
assertEquals(val, bootp.getSrvIpAddr());
}
@Test
public void testSetGwIpAddr() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0xC0A80101; // GATEWAY IP
bootp.setGwIpAddr(val);
assertEquals(val, bootp.getGwIpAddr());
}
@Test
public void testSetClientHwAddr() throws Exception {
BOOTP bootp = new BOOTP();
final byte[] val = new byte[]{(byte) 0x98, (byte) 0xfc,
(byte) 0x11, (byte) 0x93, (byte) 0x5c, (byte) 0xb8}; // MAGIC COOKIE
bootp.setClientHwAddr(val);
assertArrayEquals(val, bootp.getClientHwAddr());
}
@Test
public void testSetMagicCookie() throws Exception {
BOOTP bootp = new BOOTP();
final int val = 0x63825363, val2 = 0x0; // MAGIC COOKIE
assertEquals(val, BOOTP.BootpType.DHCP.value); // we are using DHCP's magic cookie
bootp.setMagicCookie(val);
assertEquals(val, bootp.getMagicCookie());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
// test special case for DHCP
bootp.setMagicCookie(val2);
assertEquals(val2, bootp.getMagicCookie());
assertNotEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
}
@Test
public void testSubnetMaskOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("0104ffffff00");
BOOTP.SubnetMaskOption option = new BOOTP.SubnetMaskOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertArrayEquals(new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 0}, NetUtils.intToByteArray4(option.getSubnetMask()));
assertEquals(4, option.getHeaderLen());
}
@Test
public void testRouterAddressOption() throws Exception {
}
@Test
public void testDomainNameServerOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3204c0a8000a");
BOOTP.RequestedIpAddressOption option = new BOOTP.RequestedIpAddressOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 10}, NetUtils.intToByteArray4(option.getAddress()));
assertEquals(4, option.getHeaderLen());
}
@Test
public void testHostNameOption() throws Exception {
}
@Test
public void testDomainNameOption() throws Exception {
}
@Test
public void testRequestedIpAddressRequestOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3204c0a8000a");
BOOTP.RequestedIpAddressOption option = new BOOTP.RequestedIpAddressOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 10}, NetUtils.intToByteArray4(option.getAddress()));
assertEquals(4, option.getHeaderLen());
}
@Test
public void testIpAddressLeaseTimeOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("330400000e10");
BOOTP.IpAddressLeaseTimeOption option = new BOOTP.IpAddressLeaseTimeOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertEquals(3600, option.getLeaseTime());
assertEquals(4, option.getHeaderLen());
}
@Test
public void testDhcpMessageOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("350103");
BOOTP.DhcpMessageTypeOption option = new BOOTP.DhcpMessageTypeOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertEquals(BOOTP.DhcpMessageTypeOption.MessageType.REQUEST.value, option.getType());
assertEquals(1, option.getHeaderLen());
}
@Test
public void testServerIdentifierOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3604c0a80001");
BOOTP.ServerIdentifierOption option = new BOOTP.ServerIdentifierOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 1}, NetUtils.intToByteArray4(option.getServerIdentifier()));
assertEquals(4, option.getHeaderLen());
}
@Test
public void testParameterListOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("37040103062a");
BOOTP.ParameterListOption option = new BOOTP.ParameterListOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
byte[] list = new byte[]{
BOOTP.BootpOption.BootpOptionCode.SUBNET_MASK.value,
BOOTP.BootpOption.BootpOptionCode.ROUTER_ADDRESS.value,
BOOTP.BootpOption.BootpOptionCode.DOMAIN_NAME_SERVER.value,
BOOTP.BootpOption.BootpOptionCode.NTP_SERVERS.value
};
assertArrayEquals(
list, option.getParameterList()
);
assertEquals(4, option.getHeaderLen());
}
@Test
public void testRenewalTimeOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3a0400000708");
BOOTP.RenewalTimeOption option = new BOOTP.RenewalTimeOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertEquals(1800, option.getRenewalTime());
assertEquals(4, option.getHeaderLen());
}
@Test
public void testRebindingTimeOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3b0400000c4e");
BOOTP.RebindingTimeOption option = new BOOTP.RebindingTimeOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertEquals(3150, option.getRebindingTime());
assertEquals(4, option.getHeaderLen());
}
@Test
public void testClientIdOption() throws Exception {
final byte[] rawOpt = hexStringToByteArray("3d0701000b8201fc42");
BOOTP.ClientIdOption option = new BOOTP.ClientIdOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, option.isCorrupted());
assertEquals(BOOTP.HardwareType.ETHERNET.value, option.getHwType());
assertEquals(7, option.getHeaderLen());
assertArrayEquals(HexEncode.bytesFromHexString("00:0b:82:01:fc:42"), option.getClientIdentifier());
}
@Test
public void testPadOptionAndEndOption() throws Exception {
// test PAD
final byte[] rawPadOpt = hexStringToByteArray("00");
BOOTP.PadOption padOption = new BOOTP.PadOption();
padOption.deserialize(rawPadOpt, 0, rawPadOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, padOption.isCorrupted());
assertEquals(0, padOption.getHeaderLen());
assertArrayEquals(new byte[]{0}, padOption.serialize());
// test END
final byte[] rawEndOpt = hexStringToByteArray("ff");
BOOTP.EndOption endOption = new BOOTP.EndOption();
endOption.deserialize(rawEndOpt, 0, rawEndOpt.length * NetUtils.NumBitsInAByte);
assertEquals(false, endOption.isCorrupted());
assertEquals(0, endOption.getHeaderLen());
}
/**
* Here we test a corrupted option
*
* @throws Exception
*/
@Test
public void testCorruptedOption() throws Exception {
// we use option code 54 instead of 53 here
final byte[] rawOpt = hexStringToByteArray("360103");
BOOTP.DhcpMessageTypeOption option = new BOOTP.DhcpMessageTypeOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
assertEquals(true, option.isCorrupted());
}
@Test
public void testFullDHCP() throws Exception {
// DHCP Request packet
final byte[] pkt = hexStringToByteArray("ffffffffffff000b8201fc4208004" +
"500012ca8370000fa11178a000000" +
"00ffffffff0044004301189fbd010" +
"1060000003d1e0000000000000000" +
"000000000000000000000000000b8" +
"201fc420000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000000000000000000000000000" +
"00000638253633501033d0701000b" +
"8201fc423204c0a8000a3604c0a80" +
"00137040103062aff00"
);
// verify l2 payload
Ethernet ethPkt = new Ethernet();
ethPkt.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// verify l3 payload
Packet ipPkt = ethPkt.getPayload();
assertTrue(ipPkt instanceof IPv4);
// verify l4 payload
Packet udpPkt = ipPkt.getPayload();
assertTrue(udpPkt instanceof UDP);
// everything looks good by far, now read DHCP payload
byte[] rawPkt = udpPkt.getRawPayload();
BOOTP bootp = new BOOTP();
bootp.deserialize(rawPkt, 0, rawPkt.length * NetUtils.NumBitsInAByte);
// verify DHCP fields
assertEquals(BOOTP.OpCode.BOOT_REQUEST.value, bootp.getOpCode());
assertEquals(BOOTP.HardwareType.ETHERNET.value, bootp.getHwType());
assertEquals(NetUtils.MACAddrLengthInBytes, bootp.getHwAddrLength());
assertEquals(0, bootp.getHopCount());
assertEquals(0x3d1e, bootp.getTxId());
assertEquals(0, bootp.getNumOfSec());
assertEquals(BOOTP.FlagType.UNICAST.value, bootp.getFlags());
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getClientIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getYourIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getSrvIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getGwIpAddr()));
assertArrayEquals(new byte[]{0, (byte) 0xb, (byte) 0x82, (byte) 0x1, (byte) 0xfc, (byte) 0x42}, bootp.getClientHwAddr());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
// now verify DHCP options
assertEquals("35:01:03:3d:07:01:00:0b:82:01:fc:42:32:04:c0:a8:00:0a:36:04:c0:a8:00:01:37:04:01:03:06:2a:ff:00", HexEncode.bytesToHexStringFormat(bootp.getOptions()));
BOOTP.BootpOption[] opts = bootp.getOptionsAsArray();
ArrayList<Byte> optCodes = new ArrayList<>();
for (int i = 0; i < opts.length; i++)
optCodes.add(opts[i].getOptCode());
assertArrayEquals(new Byte[]{53, 61, 50, 54, 55, -1, 0}, optCodes.toArray());
}
@Test
public void testFullDHCP_Request() throws Exception {
// DHCP Request packet, DHCP payload only
final byte[] pkt = hexStringToByteArray("0101060000003d1e00000000000000000000000000" +
"00000000000000000b8201fc420000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000" +
"0000000000638253633501033d0701000b8201fc42" +
"3204c0a8000a3604c0a8000137040103062aff00"
);
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// now verify basic DHCP fields
assertEquals(BOOTP.OpCode.BOOT_REQUEST.value, bootp.getOpCode());
assertEquals(BOOTP.HardwareType.ETHERNET.value, bootp.getHwType());
assertEquals(NetUtils.MACAddrLengthInBytes, bootp.getHwAddrLength());
assertEquals(0, bootp.getHopCount());
assertEquals(0x3d1e, bootp.getTxId());
assertEquals(0, bootp.getNumOfSec());
assertEquals(BOOTP.FlagType.UNICAST.value, bootp.getFlags());
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getClientIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getYourIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getSrvIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getGwIpAddr()));
assertArrayEquals(new byte[]{0, (byte) 0xb, (byte) 0x82, (byte) 0x1, (byte) 0xfc, (byte) 0x42}, bootp.getClientHwAddr());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
// verify DHCP options
assertEquals("35:01:03:3d:07:01:00:0b:82:01:fc:42:32:04:c0:a8:00:0a:36:04:c0:a8:00:01:37:04:01:03:06:2a:ff:00", HexEncode.bytesToHexStringFormat(bootp.getOptions()));
BOOTP.BootpOption[] opts = bootp.getOptionsAsArray();
ArrayList<Byte> optCodes = new ArrayList<>();
for (int i = 0; i < opts.length; i++)
optCodes.add(opts[i].getOptCode());
assertArrayEquals(new Byte[]{53, 61, 50, 54, 55, -1, 0}, optCodes.toArray());
}
@Test
public void testFullDHCP_Discover() throws Exception {
// DHCP Discover packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0101060000003d1d0000000000000000000000000000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013d0701000b8201fc4232040000000037040103062aff00000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// now verify basic DHCP fields
assertEquals(BOOTP.OpCode.BOOT_REQUEST.value, bootp.getOpCode());
assertEquals(BOOTP.HardwareType.ETHERNET.value, bootp.getHwType());
assertEquals(NetUtils.MACAddrLengthInBytes, bootp.getHwAddrLength());
assertEquals(0, bootp.getHopCount());
assertEquals(0x3d1d, bootp.getTxId());
assertEquals(0, bootp.getNumOfSec());
assertEquals(BOOTP.FlagType.UNICAST.value, bootp.getFlags());
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getClientIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getYourIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getSrvIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getGwIpAddr()));
assertArrayEquals(new byte[]{0, (byte) 0xb, (byte) 0x82, (byte) 0x1, (byte) 0xfc, (byte) 0x42}, bootp.getClientHwAddr());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
}
@Test
public void testFullDHCP_Offer() throws Exception {
// DHCP Offer packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0201060000003d1d0000000000000000c0a8000ac0a8000100000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501020104ffffff003a04000007083b0400000c4e330400000e103604c0a80001ff0000000000000000000000000000000000000000000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// now verify basic DHCP fields
assertEquals(BOOTP.OpCode.BOOT_REPLY.value, bootp.getOpCode());
assertEquals(BOOTP.HardwareType.ETHERNET.value, bootp.getHwType());
assertEquals(NetUtils.MACAddrLengthInBytes, bootp.getHwAddrLength());
assertEquals(0, bootp.getHopCount());
assertEquals(0x3d1d, bootp.getTxId());
assertEquals(0, bootp.getNumOfSec());
assertEquals(BOOTP.FlagType.UNICAST.value, bootp.getFlags());
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getClientIpAddr()));
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 10}, NetUtils.intToByteArray4(bootp.getYourIpAddr()));
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 1}, NetUtils.intToByteArray4(bootp.getSrvIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getGwIpAddr()));
assertArrayEquals(new byte[]{0, (byte) 0xb, (byte) 0x82, (byte) 0x1, (byte) 0xfc, (byte) 0x42}, bootp.getClientHwAddr());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
}
@Test
public void testFullDHCP_Ack() throws Exception {
// DHCP Ack packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// now verify basic DHCP fields
assertEquals(BOOTP.OpCode.BOOT_REPLY.value, bootp.getOpCode());
assertEquals(BOOTP.HardwareType.ETHERNET.value, bootp.getHwType());
assertEquals(NetUtils.MACAddrLengthInBytes, bootp.getHwAddrLength());
assertEquals(0, bootp.getHopCount());
assertEquals(0x3d1e, bootp.getTxId());
assertEquals(0, bootp.getNumOfSec());
assertEquals(BOOTP.FlagType.UNICAST.value, bootp.getFlags());
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getClientIpAddr()));
assertArrayEquals(new byte[]{(byte) 192, (byte) 168, 0, (byte) 10}, NetUtils.intToByteArray4(bootp.getYourIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getSrvIpAddr()));
assertArrayEquals(new byte[]{0, 0, 0, 0}, NetUtils.intToByteArray4(bootp.getGwIpAddr()));
assertArrayEquals(new byte[]{0, (byte) 0xb, (byte) 0x82, (byte) 0x1, (byte) 0xfc, (byte) 0x42}, bootp.getClientHwAddr());
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
}
@Test
public void testFullDHCP2() throws Exception {
final byte[] pkt = HexEncode.bytesFromHexString("02:01:06:00:76:0a:bf:d5:00:00:00:00:00:00:00:00:0a:0a:01:22:00:00:00:00:00:00:00:00:3c:97:0e:dc:db:23:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:63:82:53:63:35:01:05:3a:04:00:05:46:00:3b:04:00:09:3a:80:33:04:00:0a:8c:00:36:04:0a:0a:00:0a:01:04:ff:ff:00:00:51:03:00:ff:ff:0f:0d:7a:61:6e:74:74:7a:2e:6c:6f:63:61:6c:00:03:04:0a:0a:00:01:06:08:0a:0a:00:0b:0a:0a:00:01:2c:04:0a:0a:00:0b:2e:01:08:ff");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
assertEquals(BOOTP.BootpType.DHCP, bootp.getBootpType());
BOOTP.BootpOption[] options = bootp.getOptionsAsArray();
for (BOOTP.BootpOption option : options) {
if (BOOTP.BootpOption.BootpOptionCode.DHCP_MESSAGE_TYPE.equals(option.getOptCode())) {
assertTrue(BOOTP.DhcpMessageTypeOption.MessageType.ACK.equals(((BOOTP.DhcpMessageTypeOption) option).getType()));
}
}
}
@Test
public void testSerialize() throws Exception {
// DHCP Ack packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
assertArrayEquals(pkt, bootp.serialize());
}
@Test
public void testRemoveOption() throws Exception {
// DHCP Ack packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000");
final byte[] newPkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e3604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
bootp.removeOption((byte) 51); // we remove the IP address lease time option here
assertArrayEquals(newPkt, bootp.serialize());
}
@Test
public void testInsertOption() throws Exception {
// DHCP Ack packet, DHCP payload merely
final byte[] pkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff00ff0000000000000000000000000000000000000000000000000000");
final byte[] newPkt = hexStringToByteArray("0201060000003d1e0000000000000000c0a8000a0000000000000000000b8201fc4200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501053a04000007083b0400000c4e330400000e103604c0a800010104ffffff003d0701000b8201fc42ff0000000000000000000000000000000000000000000000000000");
BOOTP bootp = new BOOTP();
bootp.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte);
// a new client identifier option
final byte[] rawOpt = hexStringToByteArray("3d0701000b8201fc42");
BOOTP.ClientIdOption option = new BOOTP.ClientIdOption();
option.deserialize(rawOpt, 0, rawOpt.length * NetUtils.NumBitsInAByte);
// add to bootp
bootp.insertOption(option);
assertArrayEquals(newPkt, bootp.serialize());
}
}
package com.kevinxw.net.packet;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.opendaylight.controller.sal.packet.BitBufferHelper;
import org.opendaylight.controller.sal.packet.BufferException;
import org.opendaylight.controller.sal.packet.Packet;
import org.opendaylight.controller.sal.packet.PacketException;
import org.opendaylight.controller.sal.utils.HexEncode;
import org.opendaylight.controller.sal.utils.NetUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Here we defines a DNS packet
* see http://www.tcpipguide.com/free/t_DNSMessageHeaderandQuestionSectionFormat.htm for reference
* also see http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
* http://tools.ietf.org/html/rfc2929
* <p/>
* Created by kevin on 6/24/14.
*/
public class DNS extends Packet {
public static final int HEADER_BIT_LENGTH = 96; // The header length (exclude record section) in bit
private static final String TXID = "TransactionId";
private static final String QRFLAG = "QRFlag";
private static final String OPCODE = "OpCode";
private static final String AUTHORITATIVE_ANSWER = "AuthoritativeAnswer";
private static final String TRUNCATION = "Truncation";
private static final String RECURSION_DESIRED = "RecursionDesired";
private static final String RECURSION_AVAILABLE = "RecursionAvailable";
private static final String Z = "Reserved";
private static final String RESPONSE_CODE = "ResponseCode";
private static final String QRCOUNT = "QueryCount";
private static final String ANSCOUNT = "AnswerRecordCount";
private static final String NSCOUNT = "AuthorityRecordCount";
private static final String ARCOUNT = "AdditionalRecordCount";
private static final String RECORD_DATA = "RecordData";
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
private static final long serialVersionUID = 1L;
{
// transaction ID
put(TXID, new ImmutablePair<>(0, 16));
// flags
put(QRFLAG, new ImmutablePair<>(16, 1));
put(OPCODE, new ImmutablePair<>(17, 4));
put(AUTHORITATIVE_ANSWER, new ImmutablePair<>(21, 1));
put(TRUNCATION, new ImmutablePair<>(22, 1));
put(RECURSION_DESIRED, new ImmutablePair<>(23, 1));
put(RECURSION_AVAILABLE, new ImmutablePair<>(24, 1));
put(Z, new ImmutablePair<>(25, 3));
put(RESPONSE_CODE, new ImmutablePair<>(28, 4));
// following several counts
put(QRCOUNT, new ImmutablePair<>(32, 16));
put(ANSCOUNT, new ImmutablePair<>(48, 16));
put(NSCOUNT, new ImmutablePair<>(64, 16));
put(ARCOUNT, new ImmutablePair<>(80, 16));
put(RECORD_DATA, new ImmutablePair<>(96, 0));
}
};
private final Map<String, byte[]> fieldValues = new HashMap<>();
// a domain name map indexed by offset, the value is domain name string section (content between dot and dot)
private final Map<Integer, String> stringDic = new HashMap<>();
private Question[] questions = null;
private ResourceRecord[] answerRRs = null;
private ResourceRecord[] authorityRRs = null;
private ResourceRecord[] additionalRRs = null;
/**
* Default constructor that creates and sets the hash map values
*/
public DNS() {
super();
init();
}
/**
* Constructor that sets the access level for the packet
*/
public DNS(boolean writeAccess) {
super(writeAccess);
init();
}
public static byte[] getDomainBytes(String str) {
String[] sections = str.split("\\.");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sections.length; i++) {
if (sections[i].length() > 255)
throw new RuntimeException("Invalid domain string");
sb.append(Character.toChars(sections[i].length())).append(sections[i]);
}
sb.append("\0");
return sb.toString().getBytes();
}
private void init() {
hdrFieldCoordMap = fieldCoordinates;
hdrFieldsMap = fieldValues;
}
@Override
protected void postDeserializeCustomOperation(byte[] data, int startBitOffset)
throws PacketException {
// here we try to deserialize the questions and answers, which are stored in rawPayload
setRecordData(rawPayload);
rawPayload = new byte[]{};
}
@Override
public int getfieldnumBits(String fieldName) {
if (RECORD_DATA.equals(fieldName)) {
byte[] dat = getRecordData();
return dat == null ? 0 : dat.length * NetUtils.NumBitsInAByte;
} else
return hdrFieldCoordMap.get(fieldName).getRight();
}
@Override
public void setHeaderField(String headerField, byte[] readValue) {
if (headerField.equals(RECORD_DATA) &&
(readValue == null || readValue.length == 0)) {
hdrFieldsMap.remove(headerField);
return;
}
hdrFieldsMap.put(headerField, readValue);
}
private void deserializeRecordData() throws PacketException {
stringDic.clear();
questions = new Question[getQuestionCount()];
additionalRRs = new ResourceRecord[getAdditionalRRCount()];
answerRRs = new ResourceRecord[getAnswerRRCount()];
authorityRRs = new ResourceRecord[getAuthorityRRCount()];
if (questions.length + additionalRRs.length + answerRRs.length + authorityRRs.length < 1) {
return;
}
int offset = HEADER_BIT_LENGTH; // init offset for the first section (most likely question)
byte[] data = getRecordData();
// now try to peer them with buffer
// the order must not be changed
for (int i = 0; i < questions.length; i++) {
final Question question = new Question(this, offset);
int len = question.getHeaderSize();
question.deserialize(data, offset - HEADER_BIT_LENGTH, len);
questions[i] = question;
offset += len;
}
for (int i = 0; i < answerRRs.length; i++) {
final ResourceRecord answerRR = new ResourceRecord(this, offset);
int len = answerRR.getHeaderSize();
answerRR.deserialize(data, offset - HEADER_BIT_LENGTH, len);
answerRRs[i] = answerRR;
offset += len;
}
for (int i = 0; i < authorityRRs.length; i++) {
final ResourceRecord authorityRR = new ResourceRecord(this, offset);
int len = authorityRR.getHeaderSize();
authorityRR.deserialize(data, offset - HEADER_BIT_LENGTH, len);
authorityRRs[i] = authorityRR;
offset += len;
}
for (int i = 0; i < additionalRRs.length; i++) {
final ResourceRecord additionalRR = new ResourceRecord(this, offset);
int len = additionalRR.getHeaderSize();
additionalRR.deserialize(data, offset - HEADER_BIT_LENGTH, len);
additionalRRs[i] = additionalRR;
offset += len;
}
}
/**
* Serialize records to byte data
*/
private void serializeRecordData() throws PacketException {
int offset = 0;
int len = 0;
for (Question question : questions)
len += question.getHeaderSize();
for (ResourceRecord resourceRecord : additionalRRs)
len += resourceRecord.getHeaderSize();
for (ResourceRecord resourceRecord : answerRRs)
len += resourceRecord.getHeaderSize();
for (ResourceRecord resourceRecord : authorityRRs)
len += resourceRecord.getHeaderSize();
byte[] recordData = new byte[len / NetUtils.NumBitsInAByte];
for (int i = 0; i < questions.length; i++) {
byte[] data = questions[i].serialize();
}
for (int i = 0; i < answerRRs.length; i++) {
byte[] data = answerRRs[i].serialize();
}
for (int i = 0; i < authorityRRs.length; i++) {
byte[] data = authorityRRs[i].serialize();
}
for (int i = 0; i < additionalRRs.length; i++) {
byte[] data = additionalRRs[i].serialize();
}
}
/**
* Read domain from buffer
*
* @param bitOffset absolute offset, unit - bit
* @return
*/
private void readDomainAt(final int bitOffset) {
// if domain starting at this offset has already been read
if (stringDic.containsKey(bitOffset)) return;
int len, i = bitOffset - HEADER_BIT_LENGTH; // the unit of i is bit
byte[] data = getRecordData();
try {
while ((len = BitBufferHelper.getByte(BitBufferHelper.getBits(data, i, NetUtils.NumBitsInAByte)) & 0xFF) != 0 // it's not the end of a string
&& ((len & 0xC0) != 0xC0) // it's not a pointer
) {
byte[] chars = BitBufferHelper.getBits(data, i + NetUtils.NumBitsInAByte, len * NetUtils.NumBitsInAByte);
stringDic.put(i + HEADER_BIT_LENGTH, new String(chars));
i += (len + 1) * NetUtils.NumBitsInAByte; // skip the chars and length
}
if (len == 0) {
// put an end mark(null) here
stringDic.put(i + HEADER_BIT_LENGTH, null);
} else if ((len & 0xC0) == 0xC0) { // it's a pointer
// append pointed domain
int pointer = BitBufferHelper.getShort(BitBufferHelper.getBits(data, i, 2 * NetUtils.NumBitsInAByte)) & 0x3FFF;
if (pointer == bitOffset) return; // avoid infinite loop here
stringDic.put(i + HEADER_BIT_LENGTH, "#" + pointer * NetUtils.NumBitsInAByte); // mark as pointer, put an anchor here
// recursively read the pointer, notice the pointer is pointed to an absolute position
readDomainAt(pointer);
}
} catch (BufferException e) {
return;
}
}
/**
* Concatenate all the string section
*
* @param bitOffset absolute offset, unit - bit
* @return
*/
public String getDomainAt(int bitOffset) {
readDomainAt(bitOffset);
StringBuilder sb = new StringBuilder();
String str;
while (stringDic.containsKey(bitOffset) && (str = stringDic.get(bitOffset)) != null) {
if (str.startsWith("#")) { // if this is a pointer
bitOffset = Integer.parseInt(str.substring(1)); // jump to pointer
continue;
}
sb.append(str).append('.');
bitOffset += (str.length() + 1) * NetUtils.NumBitsInAByte;
}
if (sb.length() > 1)
sb.deleteCharAt(sb.length() - 1); // remove the additional dot
return sb.toString();
}
/**
* The input unit is bit. return -1 when there is an exception
*
* @param bitOffset absolute offset, unit - bit
* @return length of domain starting at specific offset
*/
public int getDomainLengthAt(int bitOffset) {
int len, i = 0;
bitOffset -= HEADER_BIT_LENGTH; // fix the offset to relative to RecordData
byte[] data = getRecordData();
try {
while ((len = BitBufferHelper.getByte(BitBufferHelper.getBits(data, i * NetUtils.NumBitsInAByte + bitOffset, NetUtils.NumBitsInAByte)) & 0xFF) != 0 // it's not the end of a string
&& ((len & 0xC0) != 0xC0) // it's not a pointer
) {
i += len + 1; // char bytes plus len byte
}
} catch (BufferException e) {
return -1;
}
if ((len & 0xC0) == 0xC0) { // it's a pointer
i++; // the pointer takes two bytes
}
i++; // as the i is index, plus one here
return i * NetUtils.NumBitsInAByte;
}
/**
* Returns the transaction ID of the current DNS packet
*
* @return The transaction ID of the current DNS packet
*/
public short getTxId() {
return BitBufferHelper.getShort(fieldValues.get(TXID));
}
/**
* Set the transaction ID for this DNS packet
*
* @param txId
* @return
*/
public DNS setTxId(short txId) {
byte[] _txId = BitBufferHelper.toByteArray(txId);
setHeaderField(TXID, _txId);
return this;
}
/**
* Returns the question flag
*
* @return
*/
public QRCode getQrFlag() {
return QRCode.valueOf(BitBufferHelper.getByte(fieldValues.get(QRFLAG))); // get the most significant bit
}
/**
* Set question flag
*
* @param flag
* @return
*/
public DNS setQrFlag(QRCode flag) {
byte[] _val = BitBufferHelper.toByteArray(flag.value);
setHeaderField(QRFLAG, _val);
return this;
}
/**
* @return
*/
public byte getOpCode() {
return BitBufferHelper.getByte(fieldValues.get(OPCODE));
}
/**
* @param code
* @return
*/
public DNS setOpCode(byte code) {
byte[] _val = BitBufferHelper.toByteArray((byte) (code & 0b1111));
setHeaderField(OPCODE, _val);
return this;
}
/**
* @return
*/
public byte getAuthAnswerFlag() {
return BitBufferHelper.getByte(fieldValues.get(AUTHORITATIVE_ANSWER));
}
/**
* @param val
* @return
*/
public DNS setAuthAnswerFlag(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 1));
setHeaderField(AUTHORITATIVE_ANSWER, _val);
return this;
}
/**
* @return
*/
public byte getTruncationFlag() {
return BitBufferHelper.getByte(fieldValues.get(TRUNCATION));
}
/**
* @param val
* @return
*/
public DNS setTruncationFlag(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 1));
setHeaderField(TRUNCATION, _val);
return this;
}
/**
* @return
*/
public byte getRecursionDesiredFlag() {
return BitBufferHelper.getByte(fieldValues.get(RECURSION_DESIRED));
}
/**
* @param val
* @return
*/
public DNS setRecursionDesiredFlag(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 1));
setHeaderField(RECURSION_DESIRED, _val);
return this;
}
/**
* @return
*/
public byte getRecursionAvailableFlag() {
return BitBufferHelper.getByte(fieldValues.get(RECURSION_AVAILABLE));
}
/**
* @param val
* @return
*/
public DNS setRecursionAvailableFlag(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 1));
setHeaderField(RECURSION_AVAILABLE, _val);
return this;
}
/**
* @return
*/
public byte getZCode() {
return BitBufferHelper.getByte(fieldValues.get(Z));
}
/**
* We provide this possibility, but remember, it should always be zero
* for correct usage
*
* @param val
* @return
*/
public DNS setZCode(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 0b111));
setHeaderField(Z, _val);
return this;
}
/**
* @return
*/
public byte getResponseCode() {
return BitBufferHelper.getByte(fieldValues.get(RESPONSE_CODE));
}
/**
* @param val
* @return
*/
public DNS setResponseCode(byte val) {
byte[] _val = BitBufferHelper.toByteArray((byte) (val & 0b1111));
setHeaderField(RESPONSE_CODE, _val);
return this;
}
/**
* Get query count
*
* @return
*/
public short getQuestionCount() {
return BitBufferHelper.getShort(fieldValues.get(QRCOUNT));
}
public DNS setQuestionCount(short count) {
byte[] _count = BitBufferHelper.toByteArray(count);
setHeaderField(QRCOUNT, _count);
return this;
}
/**
* Return query answer count
*
* @return
*/
public short getAnswerRRCount() {
return BitBufferHelper.getShort(fieldValues.get(ANSCOUNT));
}
public DNS setAnswerRRCount(short count) {
byte[] _count = BitBufferHelper.toByteArray(count);
setHeaderField(ANSCOUNT, _count);
return this;
}
/**
* @return
*/
public short getAuthorityRRCount() {
return BitBufferHelper.getShort(fieldValues.get(NSCOUNT));
}
public DNS setAuthorityRRCount(short count) {
byte[] _count = BitBufferHelper.toByteArray(count);
setHeaderField(NSCOUNT, _count);
return this;
}
/**
* @return
*/
public short getAdditionalRRCount() {
return BitBufferHelper.getShort(fieldValues.get(ARCOUNT));
}
public DNS setAdditionalRRCount(short count) {