Created
March 3, 2015 00:05
-
-
Save ql-owo-lp/eb4862d5dd0236ac252b to your computer and use it in GitHub Desktop.
OpenDaylight DHCP and DNS packet parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | |
byte[] _count = BitBufferHelper.toByteArray(count); | |
setHeaderField(ARCOUNT, _count); | |
return this; | |
} | |
/** | |
* This is merely for private use. Not an official field of DNS | |
* | |
* @return | |
*/ | |
private byte[] getRecordData() { | |
return fieldValues.get(RECORD_DATA); | |
} | |
private DNS setRecordData(byte[] data) throws PacketException { | |
setHeaderField(RECORD_DATA, data); | |
deserializeRecordData(); | |
return this; | |
} | |
public Question[] getQuestions() { | |
return questions; | |
} | |
public ResourceRecord[] getAnswerRRs() { | |
return answerRRs; | |
} | |
public ResourceRecord[] getAuthorityRRs() { | |
return authorityRRs; | |
} | |
public ResourceRecord[] getAdditionalRRs() { | |
return additionalRRs; | |
} | |
@Override | |
public String toString() { | |
StringBuilder sb = new StringBuilder(); | |
sb.append("DNS Packet {") | |
.append("\n - TransactionId: 0x").append(Integer.toHexString(getTxId() & 0xFFFF)) | |
.append("\n - Response: ").append("Message is a ").append(getQrFlag().toString()) | |
.append("\n - Truncated: ").append(getTruncationFlag()) | |
.append("\n - Recursion desired: ").append(getRecursionDesiredFlag()) | |
.append("\n - Non-authenticated data acceptable: ").append(getAuthAnswerFlag()) | |
.append("\n - Questions: ").append(getQuestionCount()) | |
.append("\n - Answer RRs: ").append(getAnswerRRCount()) | |
.append("\n - Authority RRs: ").append(getAuthorityRRCount()) | |
.append("\n - Additional RRs:").append(getAdditionalRRCount()) | |
; | |
for (Question question : getQuestions()) { | |
sb.append("\n + ").append(question.toString()); | |
} | |
for (ResourceRecord record : getAnswerRRs()) { | |
sb.append("\n + Answer ").append(record.toString()); | |
} | |
for (ResourceRecord record : getAuthorityRRs()) { | |
sb.append("\n + Authority ").append(record.toString()); | |
} | |
for (ResourceRecord record : getAdditionalRRs()) { | |
sb.append("\n + Additional ").append(record.toString()); | |
} | |
sb.append("\n}"); | |
return sb.toString(); | |
} | |
public static enum OpCode { | |
QUERY(0), | |
IQUERY(1), // inverse query, obsolete | |
STATUS(2), | |
// notice code 3 is unassigned / reserved | |
NOTIFY(4), | |
UPDATE(5); | |
public final byte value; | |
private OpCode(int value) { | |
this.value = (byte) value; | |
} | |
public static OpCode valueOf(int value) { | |
return valueOf((byte) value); | |
} | |
public static OpCode valueOf(byte value) { | |
for (OpCode code : values()) { | |
if (code.value == value) | |
return code; | |
} | |
return null; | |
} | |
public boolean equals(byte value) { | |
return this.value == value; | |
} | |
} | |
public static enum ClassType { | |
IN(1), CH(3), HS(4), QCLASS_NONE(254), QCLASS_ANY(255); | |
public final short value; | |
private ClassType(int value) { | |
this.value = (short) value; | |
} | |
public static ClassType valueOf(int value) { | |
return valueOf((short) (value & 0xFF)); | |
} | |
public static ClassType valueOf(short value) { | |
for (ClassType type : values()) { | |
if (type.value == value) | |
return type; | |
} | |
return null; | |
} | |
public boolean equals(short value) { | |
return this.value == value; | |
} | |
} | |
/** | |
* Resource Record (RR) Types | |
*/ | |
public static enum ResourceRecordType { | |
A(1), NS(2), MD(3), MF(4), CNAME(5), SOA(6), MB(7), | |
MG(8), MR(9), NULL(10), WKS(11), PTR(12), HINFO(13), | |
MINFO(14), MX(15), TXT(16), RP(17), AFSDB(18), X25(19), | |
ISDN(20), RT(21), NSAP(22), NSAP_PTR(23), SIG(24), KEY(25), | |
PX(26), GPOS(27), AAAA(28), LOC(29), NXT(30), EID(31), | |
NIMLOC(32), SRV(33), ATMA(34), NAPTR(35), KX(36), CERT(37), | |
A6(38), DNAME(39), SINK(40), OPT(41), APL(42), DS(43), | |
SSHFP(44), IPSECKEY(45), RRSIG(46), NSEC(47), DNSKEY(48), | |
DHCID(49), NSEC3(50), NSEC3PARAM(51), TLSA(52), HIP(55), | |
NINFO(56), RKEY(57), TALINK(58), CDS(59), CDNSKEY(60), | |
OPENPGPKEY(61), SPF(99), UINFO(100), UID(101), GID(102), | |
UNSPEC(103), NID(104), L32(105), L64(106), LP(107), EUI48(108), | |
EUI64(109), TKEY(249), TSIG(250), IXFR(251), AXFR(252), | |
MAILB(253), MAILA(254), ANY(255), URI(256), CAA(257), TA(32768), | |
DLV(32769),; | |
public final short value; | |
private ResourceRecordType(int value) { | |
this.value = (short) value; | |
} | |
public static ResourceRecordType valueOf(short value) { | |
for (ResourceRecordType type : values()) { | |
if (type.value == value) | |
return type; | |
} | |
return null; | |
} | |
public boolean equals(short value) { | |
return this.value == value; | |
} | |
} | |
public static enum QRCode { | |
REQUEST(0), RESPONSE(1); | |
public final byte value; | |
private QRCode(int value) { | |
this.value = (byte) value; | |
} | |
public static QRCode valueOf(int value) { | |
return valueOf((byte) value); | |
} | |
public static QRCode valueOf(byte value) { | |
for (QRCode code : values()) { | |
if (code.value == value) | |
return code; | |
} | |
return null; | |
} | |
public boolean equals(byte value) { | |
return this.value == value; | |
} | |
} | |
public static enum ResponseCode { | |
NO_ERROR(0), | |
FORMAT_ERROR(1), | |
SERVER_FAILURE(2), | |
NAME_ERROR(3), | |
NOT_IMPLEMENTED(4), | |
REFUSED(5), | |
YX_DOMAIN(6), | |
YX_RR_SET(7), | |
NX_RR_SET(8), | |
NOT_AUTH(9), | |
NOT_ZONE(10), | |
BAD_VERS_SIG(16), // bad OPT version or TSIG Signature Failure [RFC2845] | |
BADKEY(17), | |
BADTIME(18), | |
BADMODE(19), | |
BADNAME(20), | |
BADALG(21), | |
BADTRUNC(22),; | |
public final byte value; | |
private ResponseCode(int value) { | |
this.value = (byte) value; | |
} | |
public static ResponseCode valueOf(int value) { | |
return valueOf((byte) value); | |
} | |
public static ResponseCode valueOf(byte value) { | |
for (ResponseCode code : values()) { | |
if (code.value == value) | |
return code; | |
} | |
return null; | |
} | |
public boolean equals(byte value) { | |
return this.value == value; | |
} | |
} | |
/** | |
* Embed class for DNS query | |
*/ | |
public static class Question extends Packet { | |
private static final String QNAME = "Name"; | |
private static final String QTYPE = "Type"; | |
private static final String QCLASS = "Class"; | |
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() { | |
private static final long serialVersionUID = 1L; | |
{ | |
// domain being queried | |
put(QNAME, new ImmutablePair<>(0, 0)); | |
// query type, start offset depends on QNAME's length | |
put(QTYPE, new ImmutablePair<>(0, 16)); | |
put(QCLASS, new ImmutablePair<>(16, 16)); | |
} | |
}; | |
private final Map<String, byte[]> fieldValues = new HashMap<>(); | |
private int bitOffsetInParent = 0; | |
public Question(DNS dns, int bitOffsetInParent) { | |
super(); | |
init(dns, bitOffsetInParent); | |
} | |
public Question(DNS dns, int bitOffsetInParent, boolean writeAccess) { | |
super(writeAccess); | |
init(dns, bitOffsetInParent); | |
} | |
private void init(DNS dns, int bitOffsetInParent) { | |
hdrFieldCoordMap = fieldCoordinates; | |
hdrFieldsMap = fieldValues; | |
setParent(dns); | |
setBitOffsetInParent(bitOffsetInParent); | |
} | |
@Override | |
public void setParent(Packet dns) { | |
if (dns == null) | |
throw new NullPointerException("Parent DNS packet cannot be null"); | |
super.setParent(dns); | |
} | |
@Override | |
public int getfieldnumBits(String fieldName) { | |
if (QNAME.equals(fieldName)) | |
return ((DNS) getParent()).getDomainLengthAt(getBitOffsetInParent()); | |
else | |
return hdrFieldCoordMap.get(fieldName).getRight(); | |
} | |
@Override | |
public int getfieldOffset(String fieldName) { | |
int offset = hdrFieldCoordMap.get(fieldName).getLeft(); | |
if (!QNAME.equals(fieldName)) | |
offset += ((DNS) getParent()).getDomainLengthAt(getBitOffsetInParent()); | |
return offset; | |
} | |
/** | |
* Override this function so we can get header size before deserializing this packet | |
* | |
* @return Header size of this section | |
*/ | |
@Override | |
public int getHeaderSize() { | |
int size = 0; | |
for (String field : hdrFieldCoordMap.keySet()) | |
size += getfieldnumBits(field); | |
return size; | |
} | |
private int getBitOffsetInParent() { | |
return bitOffsetInParent; | |
} | |
private Question setBitOffsetInParent(int bitOffsetInParent) { | |
if (bitOffsetInParent < DNS.HEADER_BIT_LENGTH) | |
throw new RuntimeException("New offset is within parent DNS packet. Invalid offset."); | |
this.bitOffsetInParent = bitOffsetInParent; | |
return this; | |
} | |
public String getName() { | |
return ((DNS) getParent()).getDomainAt(getBitOffsetInParent()); | |
} | |
// public Query setName(String name) { | |
// return this; | |
// } | |
public short getType() { | |
return BitBufferHelper.getShort(fieldValues.get(QTYPE)); | |
} | |
public Question setType(short type) { | |
byte[] _type = BitBufferHelper.toByteArray(type); | |
setHeaderField(QTYPE, _type); | |
return this; | |
} | |
public short getClazz() { | |
return BitBufferHelper.getShort(fieldValues.get(QCLASS)); | |
} | |
public Question setClazz(short clazz) { | |
byte[] _clazz = BitBufferHelper.toByteArray(clazz); | |
setHeaderField(QCLASS, _clazz); | |
return this; | |
} | |
@Override | |
public String toString() { | |
return new StringBuilder() | |
.append("Question: ") | |
.append(getName()) | |
.append(" type ").append(ResourceRecordType.valueOf(getType())) | |
.append(" class ").append(ClassType.valueOf(getClazz())) | |
.toString(); | |
} | |
} | |
/** | |
* Class for DNS Answer | |
*/ | |
public static class ResourceRecord extends Packet { | |
private static final String NAME = "Name"; | |
private static final String RTYPE = "Type"; | |
private static final String RCLASS = "Class"; | |
private static final String TTL = "TTL"; | |
private static final String RDLENGTH = "RDataLength"; | |
private static final String RDATA = "RData"; | |
private static final Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() { | |
private static final long serialVersionUID = 1L; | |
{ | |
// domain name | |
put(NAME, new ImmutablePair<>(0, 0)); | |
// rdata type, start offset depends on name's length | |
put(RTYPE, new ImmutablePair<>(0, 16)); | |
// rdata class | |
put(RCLASS, new ImmutablePair<>(16, 16)); | |
put(TTL, new ImmutablePair<>(32, 32)); | |
put(RDLENGTH, new ImmutablePair<>(64, 16)); | |
put(RDATA, new ImmutablePair<>(80, 0)); | |
} | |
}; | |
private final Map<String, byte[]> fieldValues = new HashMap<>(); | |
private int bitOffsetInParent = 0; | |
public ResourceRecord(DNS dns, int bitOffsetInParent) { | |
super(); | |
init(dns, bitOffsetInParent); | |
} | |
public ResourceRecord(DNS dns, int bitOffsetInParent, boolean writeAccess) { | |
super(writeAccess); | |
init(dns, bitOffsetInParent); | |
} | |
private void init(DNS dns, int bitOffsetInParent) { | |
hdrFieldCoordMap = fieldCoordinates; | |
hdrFieldsMap = fieldValues; | |
setParent(dns); | |
setBitOffsetInParent(bitOffsetInParent); | |
} | |
@Override | |
public void setParent(Packet dns) { | |
if (dns == null) | |
throw new NullPointerException("Parent DNS packet cannot be null"); | |
super.setParent(dns); | |
} | |
@Override | |
public int getfieldnumBits(String fieldName) { | |
if (NAME.equals(fieldName)) | |
return ((DNS) getParent()).getDomainLengthAt(getBitOffsetInParent()); | |
else if (RDATA.equals(fieldName)) { | |
return (getRdLengthFromParent() & 0xFFFF) * NetUtils.NumBitsInAByte; | |
} else | |
return hdrFieldCoordMap.get(fieldName).getRight(); | |
} | |
@Override | |
public int getfieldOffset(String fieldName) { | |
int offset = hdrFieldCoordMap.get(fieldName).getLeft(); | |
if (!NAME.equals(fieldName)) | |
offset += ((DNS) getParent()).getDomainLengthAt(getBitOffsetInParent()); | |
return offset; | |
} | |
/** | |
* Override this function so we can get header size before deserializing this packet | |
* | |
* @return Header size of this section | |
*/ | |
@Override | |
public int getHeaderSize() { | |
int size = 0; | |
for (String field : hdrFieldCoordMap.keySet()) | |
size += getfieldnumBits(field); | |
return size; | |
} | |
private int getBitOffsetInParent() { | |
return bitOffsetInParent; | |
} | |
private ResourceRecord setBitOffsetInParent(int bitOffsetInParent) { | |
if (bitOffsetInParent < DNS.HEADER_BIT_LENGTH) | |
throw new RuntimeException("New offset is within parent DNS packet. Invalid offset."); | |
this.bitOffsetInParent = bitOffsetInParent; | |
return this; | |
} | |
/** | |
* We can read the RDataLength value directly from parent | |
* | |
* @return | |
*/ | |
private short getRdLengthFromParent() { | |
DNS dns = (DNS) getParent(); | |
byte[] data = dns.getRecordData(); | |
int offset = getBitOffsetInParent() - HEADER_BIT_LENGTH + getfieldOffset(RDLENGTH); | |
try { | |
return BitBufferHelper.getShort(BitBufferHelper.getBits(data, offset, 16)); | |
} catch (BufferException e) { | |
return 0; | |
} | |
} | |
public String getName() { | |
return ((DNS) getParent()).getDomainAt(getBitOffsetInParent()); | |
} | |
public short getRType() { | |
return BitBufferHelper.getShort(fieldValues.get(RTYPE)); | |
} | |
public ResourceRecord setRType(short val) { | |
byte[] _val = BitBufferHelper.toByteArray(val); | |
setHeaderField(RTYPE, _val); | |
return this; | |
} | |
public short getRClass() { | |
return BitBufferHelper.getShort(fieldValues.get(RCLASS)); | |
} | |
public ResourceRecord setRClass(short val) { | |
byte[] _val = BitBufferHelper.toByteArray(val); | |
setHeaderField(RCLASS, _val); | |
return this; | |
} | |
public int getTTL() { | |
return BitBufferHelper.getInt(fieldValues.get(TTL)); | |
} | |
public ResourceRecord setTTL(int val) { | |
byte[] _val = BitBufferHelper.toByteArray(val); | |
setHeaderField(TTL, _val); | |
return this; | |
} | |
public short getRdLength() { | |
return BitBufferHelper.getShort(fieldValues.get(RDLENGTH)); | |
} | |
// public ResourceRecord setRdLength(short val) { | |
// byte[] _val = BitBufferHelper.toByteArray(val); | |
// setHeaderField(RDLENGTH, _val); | |
// return this; | |
// } | |
/** | |
* Get resource record as raw bytes | |
* | |
* @return | |
*/ | |
public byte[] getRData() { | |
return fieldValues.get(RDATA); | |
} | |
// do not support setting it yet | |
// public ResourceRecord setRData(byte[] val) { | |
// if (val.length > 65535) | |
// throw new RuntimeException("RData exceeds 65535 bytes."); | |
// setRdLength((short) (val.length & 0xFFFF)); | |
// setHeaderField(RTYPE, val); | |
// return this; | |
// } | |
/** | |
* Return the RData as a string data | |
* | |
* @return | |
*/ | |
public String getRDataAsString() { | |
ResourceRecordType rType = ResourceRecordType.valueOf(getRType()); | |
if (rType == null) | |
return HexEncode.bytesToHexStringFormat(getRData()); | |
String data; | |
try { | |
switch (rType) { | |
case A: | |
data = Inet4Address.getByAddress(getRData()).getHostAddress(); | |
break; | |
case AAAA: | |
data = Inet6Address.getByAddress(getRData()).getHostAddress(); | |
break; | |
case MX: | |
case TXT: | |
case PTR: | |
case CNAME: | |
data = ((DNS) getParent()).getDomainAt(getBitOffsetInParent() + 96); // 96 is offset of RData | |
break; | |
default: | |
data = HexEncode.bytesToHexStringFormat(getRData()); | |
} | |
} catch (UnknownHostException e) { | |
data = "[INVALID ADDRESS] " + HexEncode.bytesToHexString(getRData()); | |
} | |
return data; | |
} | |
@Override | |
public String toString() { | |
return new StringBuilder() | |
.append("Resource Record: ") | |
.append(getName()) | |
.append(" type: ").append(ResourceRecordType.valueOf(getRType())) | |
.append(" class: ").append(ClassType.valueOf(getRClass())) | |
.append(" TTL: ").append(getTTL()) | |
.append(" Data: ").append(getRDataAsString()) | |
.toString() | |
; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.kevinxw.net.packet; | |
import org.junit.Ignore; | |
import org.junit.Test; | |
import org.opendaylight.controller.sal.utils.HexEncode; | |
import org.opendaylight.controller.sal.utils.NetUtils; | |
import static org.junit.Assert.assertArrayEquals; | |
import static org.junit.Assert.assertEquals; | |
public class DNSTest { | |
/** | |
* 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; | |
} | |
@Test | |
public void testGetDomainBytes() throws Exception { | |
byte[] res = DNS.getDomainBytes("google-public-dns-a.google.com"); | |
assertArrayEquals(hexStringToByteArray("13676f6f676c652d7075626c69632d646e732d6106676f6f676c6503636f6d00"), res); | |
} | |
@Test | |
public void testSetTxId() throws Exception { | |
DNS dns = new DNS(); | |
final short val = (short) 0x8D59; // NORMALLY IT SHOULD BE ZERO, BUT FOR TESTING HERE | |
dns.setTxId(val); | |
assertEquals(val, dns.getTxId()); | |
} | |
@Test | |
public void testQrFlag() throws Exception { | |
DNS dns = new DNS(); | |
dns.setQrFlag(DNS.QRCode.RESPONSE); | |
assertEquals(DNS.QRCode.RESPONSE, dns.getQrFlag()); | |
dns.setQrFlag(DNS.QRCode.REQUEST); | |
assertEquals(DNS.QRCode.REQUEST, dns.getQrFlag()); | |
} | |
@Test | |
public void testOpCode() throws Exception { | |
DNS dns = new DNS(); | |
dns.setOpCode(DNS.OpCode.NOTIFY.value); | |
assertEquals(DNS.OpCode.NOTIFY.value, dns.getOpCode()); | |
dns.setOpCode(DNS.OpCode.IQUERY.value); | |
assertEquals(DNS.OpCode.IQUERY.value, dns.getOpCode()); | |
dns.setOpCode(DNS.OpCode.QUERY.value); | |
assertEquals(DNS.OpCode.QUERY.value, dns.getOpCode()); | |
dns.setOpCode(DNS.OpCode.STATUS.value); | |
assertEquals(DNS.OpCode.STATUS.value, dns.getOpCode()); | |
dns.setOpCode(DNS.OpCode.UPDATE.value); | |
assertEquals(DNS.OpCode.UPDATE.value, dns.getOpCode()); | |
} | |
@Test | |
public void testAaFlag() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = 1; | |
dns.setAuthAnswerFlag(val); | |
assertEquals(val, dns.getAuthAnswerFlag()); | |
} | |
@Test | |
public void testTcFlag() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = 1; | |
dns.setTruncationFlag(val); | |
assertEquals(val, dns.getTruncationFlag()); | |
} | |
@Test | |
public void testRdFlag() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = 1; | |
dns.setRecursionDesiredFlag(val); | |
assertEquals(val, dns.getRecursionDesiredFlag()); | |
} | |
@Test | |
public void testRaFlag() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = 1; | |
dns.setRecursionAvailableFlag(val); | |
assertEquals(val, dns.getRecursionAvailableFlag()); | |
} | |
@Test | |
public void testZCode() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = 0b101; // random value | |
dns.setZCode(val); | |
assertEquals(val, dns.getZCode()); | |
} | |
@Test | |
public void testResponseCode() throws Exception { | |
DNS dns = new DNS(); | |
final byte val = DNS.ResponseCode.NOT_ZONE.value; | |
dns.setResponseCode(val); | |
assertEquals(val, dns.getResponseCode()); | |
} | |
@Test | |
public void testSetQrCount() throws Exception { | |
DNS dns = new DNS(); | |
final short val = 999; | |
dns.setQuestionCount(val); | |
assertEquals(val, dns.getQuestionCount()); | |
} | |
@Test | |
public void testSetAnCount() throws Exception { | |
DNS dns = new DNS(); | |
final short val = 999; | |
dns.setAnswerRRCount(val); | |
assertEquals(val, dns.getAnswerRRCount()); | |
} | |
@Test | |
public void testSetNsCount() throws Exception { | |
DNS dns = new DNS(); | |
final short val = 999; | |
dns.setAuthorityRRCount(val); | |
assertEquals(val, dns.getAuthorityRRCount()); | |
} | |
@Test | |
public void testSetArCount() throws Exception { | |
DNS dns = new DNS(); | |
final short val = 999; | |
dns.setAdditionalRRCount(val); | |
assertEquals(val, dns.getAdditionalRRCount()); | |
} | |
@Test | |
public void testFullDNS_Query() throws Exception { | |
final byte[] pkt = hexStringToByteArray("528e01000001000000000000013801380138013807696e2d61646472046172706100000c0001"); | |
DNS dns = new DNS(); | |
dns.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte); | |
assertEquals(0x528e, dns.getTxId()); | |
assertEquals(DNS.QRCode.REQUEST, dns.getQrFlag()); | |
assertEquals(0, dns.getTruncationFlag()); | |
assertEquals(1, dns.getRecursionDesiredFlag()); | |
assertEquals(0, dns.getRecursionAvailableFlag()); | |
assertEquals(0, dns.getZCode()); | |
assertEquals(0, dns.getAuthAnswerFlag()); | |
assertEquals(1, dns.getQuestionCount()); | |
assertEquals(0, dns.getAnswerRRCount()); | |
assertEquals(0, dns.getAuthorityRRCount()); | |
assertEquals(0, dns.getAdditionalRRCount()); | |
// now testing query attributes | |
DNS.Question[] queries = dns.getQuestions(); | |
assertEquals(1, queries.length); | |
assertEquals("8.8.8.8.in-addr.arpa", queries[0].getName()); | |
assertEquals(DNS.ResourceRecordType.PTR.value, queries[0].getType()); | |
assertEquals(DNS.ClassType.IN.value, queries[0].getClazz()); | |
} | |
@Test | |
public void testFullDNS_Response() throws Exception { | |
final byte[] pkt = hexStringToByteArray("528e81800001000100000000013801380138013807696e2d61646472046172706100000c0001c00c000c00010000acb7002013676f6f676c652d7075626c69632d646e732d6106676f6f676c6503636f6d00"); | |
DNS dns = new DNS(); | |
dns.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte); | |
assertEquals(0x528e, dns.getTxId()); | |
assertEquals(DNS.QRCode.RESPONSE, dns.getQrFlag()); | |
assertEquals(0, dns.getTruncationFlag()); | |
assertEquals(1, dns.getRecursionDesiredFlag()); | |
assertEquals(1, dns.getRecursionAvailableFlag()); | |
assertEquals(0, dns.getZCode()); | |
assertEquals(0, dns.getAuthAnswerFlag()); | |
assertEquals(1, dns.getQuestionCount()); | |
assertEquals(1, dns.getAnswerRRCount()); | |
assertEquals(0, dns.getAuthorityRRCount()); | |
assertEquals(0, dns.getAdditionalRRCount()); | |
// now testing query attributes | |
DNS.Question[] queries = dns.getQuestions(); | |
assertEquals(1, queries.length); | |
assertEquals("8.8.8.8.in-addr.arpa", queries[0].getName()); | |
assertEquals(DNS.ResourceRecordType.PTR.value, queries[0].getType()); | |
assertEquals(DNS.ClassType.IN.value, queries[0].getClazz()); | |
// test answer | |
DNS.ResourceRecord[] answers = dns.getAnswerRRs(); | |
assertEquals(1, answers.length); | |
assertEquals("8.8.8.8.in-addr.arpa", answers[0].getName()); | |
assertEquals(DNS.ResourceRecordType.PTR.value, answers[0].getRType()); | |
assertEquals(DNS.ClassType.IN.value, answers[0].getRClass()); | |
assertEquals(44215, answers[0].getTTL()); | |
assertEquals(32, answers[0].getRdLength()); | |
assertArrayEquals(DNS.getDomainBytes("google-public-dns-a.google.com"), answers[0].getRData()); | |
assertEquals("google-public-dns-a.google.com", answers[0].getRDataAsString()); | |
} | |
@Test | |
public void testGetDomainAt() throws Exception { | |
final byte[] pkt = hexStringToByteArray("528e01000001000000000000013801380138013807696e2d61646472046172706100000c0001"); | |
// the domain in this query is "8.8.8.8.in-addr.arpa" | |
DNS dns = new DNS(); | |
dns.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte); | |
assertEquals(22 * NetUtils.NumBitsInAByte, dns.getDomainLengthAt(DNS.HEADER_BIT_LENGTH)); | |
assertEquals("8.8.8.8.in-addr.arpa", dns.getDomainAt(DNS.HEADER_BIT_LENGTH)); | |
final byte[] pkt2 = hexStringToByteArray("528e81800001000100000000013801380138013807696e2d61646472046172706100000c0001c00c000c00010000acb7002013676f6f676c652d7075626c69632d646e732d6106676f6f676c6503636f6d00"); | |
// the domain in this response packet is "8.8.8.8.in-addr.arpa" | |
DNS dns2 = new DNS(); | |
dns2.deserialize(pkt2, 0, pkt2.length * NetUtils.NumBitsInAByte); | |
// check the query section | |
assertEquals(22 * NetUtils.NumBitsInAByte, dns2.getDomainLengthAt(DNS.HEADER_BIT_LENGTH)); | |
assertEquals("8.8.8.8.in-addr.arpa", dns2.getDomainAt(DNS.HEADER_BIT_LENGTH)); | |
// check the response section | |
int offset = dns2.getQuestions()[0].getHeaderSize(); | |
assertEquals(2 * NetUtils.NumBitsInAByte, dns2.getDomainLengthAt(DNS.HEADER_BIT_LENGTH + offset)); | |
assertEquals("8.8.8.8.in-addr.arpa", dns2.getDomainAt(DNS.HEADER_BIT_LENGTH + offset)); | |
} | |
@Test | |
@Ignore | |
public void printFullDNS() throws Exception { | |
final byte[] pkt = HexEncode.bytesFromHexString("ba:e9:81:80:00:01:00:05:00:00:00:00:03:77:77:77:06:67:6f:6f:67:6c:65:03:63:6f:6d:00:00:01:00:01:c0:0c:00:01:00:01:00:00:00:00:00:04:4a:7d:ef:90:c0:0c:00:01:00:01:00:00:00:00:00:04:4a:7d:ef:93:c0:0c:00:01:00:01:00:00:00:00:00:04:4a:7d:ef:91:c0:0c:00:01:00:01:00:00:00:00:00:04:4a:7d:ef:92:c0:0c:00:01:00:01:00:00:00:00:00:04:4a:7d:ef:94"); | |
DNS dns = new DNS(); | |
dns.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte); | |
System.out.println(dns.toString()); | |
} | |
@Test | |
public void testCNameDNS() throws Exception { | |
final byte[] pkt = hexStringToByteArray("2e7981800001000200000000037777770462696e6703636f6d0000010001c00c000500010000013d000b03616e790465646765c010c02a000100010000013d0004cc4fc5c8"); | |
DNS dns = new DNS(); | |
dns.deserialize(pkt, 0, pkt.length * NetUtils.NumBitsInAByte); | |
assertEquals(1, dns.getQuestionCount()); | |
assertEquals(2, dns.getAnswerRRCount()); | |
dns.toString(); | |
System.out.println(dns.getAnswerRRs()[0].getName()); | |
assertEquals("www.bing.com", dns.getAnswerRRs()[0].getName()); | |
assertEquals("any.edge.bing.com", dns.getAnswerRRs()[0].getRDataAsString()); | |
assertEquals("any.edge.bing.com", dns.getAnswerRRs()[1].getName()); | |
assertEquals("204.79.197.200", dns.getAnswerRRs()[1].getRDataAsString()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment