DHCP util refactor
Move options to packet.dhcp package
Deprecated DHCPPacketType, add MsgType to DHCP class
Change-Id: I85ce7fa5e6f3fdc916fbbeba9a4e10e75064a054
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
index 012443e..ed1741e 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -18,12 +18,16 @@
package org.onlab.packet;
-import java.io.UnsupportedEncodingException;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.ListIterator;
+import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static org.onlab.packet.PacketUtils.checkInput;
@@ -53,20 +57,33 @@
public static final int MIN_HEADER_LENGTH = 240;
public static final byte OPCODE_REQUEST = 0x1;
public static final byte OPCODE_REPLY = 0x2;
-
public static final byte HWTYPE_ETHERNET = 0x1;
+ private static final Map<Byte, Deserializer<? extends DhcpOption>> OPTION_DESERIALIZERS =
+ ImmutableMap.of(DHCPOptionCode.OptionCode_CircuitID.value, DhcpRelayAgentOption.deserializer());
+ private static final int UNSIGNED_BYTE_MASK = 0xff;
+ private static final int BASE_OPTION_LEN = 60;
+ private static final int MIN_DHCP_LEN = 240;
+ private static final int BASE_HW_ADDR_LEN = 16;
+ private static final byte PAD_BYTE = 0;
+ private static final int BASE_SERVER_NAME_LEN = 64;
+ private static final int BASE_BOOT_FILE_NAME_LEN = 128;
+ private static final int MAGIC_COOKIE = 0x63825363;
+
public enum DHCPOptionCode {
- OptionCode_SubnetMask((byte) 1), OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
- OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15), OptionCode_BroadcastAddress((byte) 28),
- OptionCode_RequestedIP((byte) 50), OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
+ OptionCode_Pad((byte) 0), OptionCode_SubnetMask((byte) 1),
+ OptionCode_RouterAddress((byte) 3), OptionCode_DomainServer((byte) 6),
+ OptionCode_HostName((byte) 12), OptionCode_DomainName((byte) 15),
+ OptionCode_BroadcastAddress((byte) 28), OptionCode_RequestedIP((byte) 50),
+ OptionCode_LeaseTime((byte) 51), OptionCode_MessageType((byte) 53),
OptionCode_DHCPServerIp((byte) 54), OptionCode_RequestedParameters((byte) 55),
- OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59), OptionCode_ClientID((byte) 61),
- OptionCode_CircuitID((byte) 82), OptionCode_END((byte) 255);
+ OptionCode_RenewalTime((byte) 58), OPtionCode_RebindingTime((byte) 59),
+ OptionCode_ClientID((byte) 61), OptionCode_CircuitID((byte) 82),
+ OptionCode_END((byte) 255);
protected byte value;
- private DHCPOptionCode(final byte value) {
+ DHCPOptionCode(final byte value) {
this.value = value;
}
@@ -75,6 +92,65 @@
}
}
+ public enum MsgType {
+ // From RFC 1533
+ DHCPDISCOVER(1), DHCPOFFER(2), DHCPREQUEST(3), DHCPDECLINE(4), DHCPACK(5),
+ DHCPNAK(6), DHCPRELEASE(7),
+
+ // From RFC2132
+ DHCPINFORM(8),
+
+ // From RFC3203
+ DHCPFORCERENEW(9),
+
+ // From RFC4388
+ DHCPLEASEQUERY(10), DHCPLEASEUNASSIGNED(11), DHCPLEASEUNKNOWN(12),
+ DHCPLEASEACTIVE(13);
+
+ protected int value;
+
+ MsgType(final int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return this.value;
+ }
+
+ public static MsgType getType(final int value) {
+ switch (value) {
+ case 1:
+ return DHCPDISCOVER;
+ case 2:
+ return DHCPOFFER;
+ case 3:
+ return DHCPREQUEST;
+ case 4:
+ return DHCPDECLINE;
+ case 5:
+ return DHCPACK;
+ case 6:
+ return DHCPNAK;
+ case 7:
+ return DHCPRELEASE;
+ case 8:
+ return DHCPINFORM;
+ case 9:
+ return DHCPFORCERENEW;
+ case 10:
+ return DHCPLEASEQUERY;
+ case 11:
+ return DHCPLEASEUNASSIGNED;
+ case 12:
+ return DHCPLEASEUNKNOWN;
+ case 13:
+ return DHCPLEASEACTIVE;
+ default:
+ return null;
+ }
+ }
+ }
+
protected byte opCode;
protected byte hardwareType;
protected byte hardwareAddressLength;
@@ -89,7 +165,7 @@
protected byte[] clientHardwareAddress;
protected String serverName;
protected String bootFileName;
- protected List<DHCPOption> options = new ArrayList<DHCPOption>();
+ protected List<DhcpOption> options = new ArrayList<DhcpOption>();
/**
* @return the opCode
@@ -302,9 +378,9 @@
* The option code to get
* @return The value of the option if it exists, null otherwise
*/
- public DHCPOption getOption(final DHCPOptionCode optionCode) {
- for (final DHCPOption opt : this.options) {
- if (opt.code == optionCode.value) {
+ public DhcpOption getOption(final DHCPOptionCode optionCode) {
+ for (final DhcpOption opt : this.options) {
+ if (opt.getCode() == optionCode.getValue()) {
return opt;
}
}
@@ -314,7 +390,7 @@
/**
* @return the options
*/
- public List<DHCPOption> getOptions() {
+ public List<DhcpOption> getOptions() {
return this.options;
}
@@ -323,7 +399,7 @@
* the options to set
* @return this
*/
- public DHCP setOptions(final List<DHCPOption> options) {
+ public DHCP setOptions(final List<DhcpOption> options) {
this.options = options;
return this;
}
@@ -331,16 +407,15 @@
/**
* @return the packetType base on option 53
*/
- public DHCPPacketType getPacketType() {
- final ListIterator<DHCPOption> lit = this.options.listIterator();
- while (lit.hasNext()) {
- final DHCPOption option = lit.next();
- // only care option 53
- if (option.getCode() == 53) {
- return DHCPPacketType.getType(option.getData()[0]);
- }
- }
- return null;
+ public MsgType getPacketType() {
+ return this.options.parallelStream()
+ .filter(op -> op.getCode() == DHCPOptionCode.OptionCode_MessageType.getValue())
+ .map(DhcpOption::getData)
+ .filter(data -> data.length != 0)
+ .map(data -> data[0])
+ .map(MsgType::getType)
+ .findFirst()
+ .orElse(null);
}
/**
@@ -385,19 +460,20 @@
// minimum size 240 including magic cookie, options generally padded to
// 300
int optionsLength = 0;
- for (final DHCPOption option : this.options) {
- if (option.getCode() == 0 || option.getCode() == ((byte) 255)) {
+ for (final DhcpOption option : this.options) {
+ if (option.getCode() == DHCPOptionCode.OptionCode_Pad.getValue() ||
+ option.getCode() == DHCPOptionCode.OptionCode_END.getValue()) {
optionsLength += 1;
} else {
- optionsLength += 2 + (0xff & option.getLength());
+ optionsLength += 2 + (UNSIGNED_BYTE_MASK & option.getLength());
}
}
int optionsPadLength = 0;
- if (optionsLength < 60) {
- optionsPadLength = 60 - optionsLength;
+ if (optionsLength < BASE_OPTION_LEN) {
+ optionsPadLength = BASE_OPTION_LEN - optionsLength;
}
- final byte[] data = new byte[240 + optionsLength + optionsPadLength];
+ final byte[] data = new byte[MIN_DHCP_LEN + optionsLength + optionsPadLength];
final ByteBuffer bb = ByteBuffer.wrap(data);
bb.put(this.opCode);
bb.put(this.hardwareType);
@@ -410,127 +486,44 @@
bb.putInt(this.yourIPAddress);
bb.putInt(this.serverIPAddress);
bb.putInt(this.gatewayIPAddress);
- checkArgument(this.clientHardwareAddress.length <= 16,
+ checkArgument(this.clientHardwareAddress.length <= BASE_HW_ADDR_LEN,
"Hardware address is too long (%s bytes)", this.clientHardwareAddress.length);
bb.put(this.clientHardwareAddress);
- if (this.clientHardwareAddress.length < 16) {
- for (int i = 0; i < 16 - this.clientHardwareAddress.length; ++i) {
- bb.put((byte) 0x0);
+ if (this.clientHardwareAddress.length < BASE_HW_ADDR_LEN) {
+ for (int i = 0; i < BASE_HW_ADDR_LEN - this.clientHardwareAddress.length; ++i) {
+ bb.put(PAD_BYTE);
}
}
- this.writeString(this.serverName, bb, 64);
- this.writeString(this.bootFileName, bb, 128);
+ this.writeString(this.serverName, bb, BASE_SERVER_NAME_LEN);
+ this.writeString(this.bootFileName, bb, BASE_BOOT_FILE_NAME_LEN);
// magic cookie
- bb.put((byte) 0x63);
- bb.put((byte) 0x82);
- bb.put((byte) 0x53);
- bb.put((byte) 0x63);
- for (final DHCPOption option : this.options) {
- dhcpOptionToByteArray(option, bb);
+ bb.putInt(MAGIC_COOKIE);
+ for (final DhcpOption option : this.options) {
+ bb.put(option.serialize());
}
// assume the rest is padded out with zeroes
return data;
}
- public static ByteBuffer dhcpOptionToByteArray(DHCPOption option, ByteBuffer bb) {
- final int code = option.getCode() & 0xff;
- bb.put((byte) code);
- if (code != 0 && code != 255) {
- bb.put(option.getLength());
- bb.put(option.getData());
- }
- return bb;
- }
-
@Override
public IPacket deserialize(final byte[] data, final int offset,
final int length) {
- final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
- if (bb.remaining() < DHCP.MIN_HEADER_LENGTH) {
- return this;
+ try {
+ return deserializer().deserialize(data, offset, length);
+ } catch (DeserializationException e) {
+ return null;
}
-
- this.opCode = bb.get();
- this.hardwareType = bb.get();
- this.hardwareAddressLength = bb.get();
- this.hops = bb.get();
- this.transactionId = bb.getInt();
- this.seconds = bb.getShort();
- this.flags = bb.getShort();
- this.clientIPAddress = bb.getInt();
- this.yourIPAddress = bb.getInt();
- this.serverIPAddress = bb.getInt();
- this.gatewayIPAddress = bb.getInt();
- final int hardwareAddressLength = 0xff & this.hardwareAddressLength;
- this.clientHardwareAddress = new byte[hardwareAddressLength];
-
- bb.get(this.clientHardwareAddress);
- for (int i = hardwareAddressLength; i < 16; ++i) {
- bb.get();
- }
- this.serverName = this.readString(bb, 64);
- this.bootFileName = this.readString(bb, 128);
- // read the magic cookie
- // magic cookie
- bb.get();
- bb.get();
- bb.get();
- bb.get();
- // read options
- while (bb.hasRemaining()) {
- final DHCPOption option = new DHCPOption();
- int code = 0xff & bb.get(); // convert signed byte to int in range
- // [0,255]
- option.setCode((byte) code);
- if (code == 0) {
- // skip these
- continue;
- } else if (code != 255) {
- if (bb.hasRemaining()) {
- final int l = 0xff & bb.get(); // convert signed byte to
- // int in range [0,255]
- option.setLength((byte) l);
- if (bb.remaining() >= l) {
- final byte[] optionData = new byte[l];
- bb.get(optionData);
- option.setData(optionData);
- } else {
- // Skip the invalid option and set the END option
- code = 0xff;
- option.setCode((byte) code);
- option.setLength((byte) 0);
- }
- } else {
- // Skip the invalid option and set the END option
- code = 0xff;
- option.setCode((byte) code);
- option.setLength((byte) 0);
- }
- }
- this.options.add(option);
- if (code == 255) {
- // remaining bytes are supposed to be 0, but ignore them just in
- // case
- break;
- }
- }
-
- return this;
}
protected void writeString(final String string, final ByteBuffer bb,
final int maxLength) {
if (string == null) {
for (int i = 0; i < maxLength; ++i) {
- bb.put((byte) 0x0);
+ bb.put(PAD_BYTE);
}
} else {
- byte[] bytes = null;
- try {
- bytes = string.getBytes("ascii");
- } catch (final UnsupportedEncodingException e) {
- throw new RuntimeException("Failure encoding server name", e);
- }
+ byte[] bytes;
+ bytes = string.getBytes(StandardCharsets.US_ASCII);
int writeLength = bytes.length;
if (writeLength > maxLength) {
writeLength = maxLength;
@@ -545,12 +538,8 @@
private static String readString(final ByteBuffer bb, final int maxLength) {
final byte[] bytes = new byte[maxLength];
bb.get(bytes);
- String result = null;
- try {
- result = new String(bytes, "ascii").trim();
- } catch (final UnsupportedEncodingException e) {
- throw new RuntimeException("Failure decoding string", e);
- }
+ String result;
+ result = new String(bytes, StandardCharsets.US_ASCII).trim();
return result;
}
@@ -577,56 +566,64 @@
dhcp.yourIPAddress = bb.getInt();
dhcp.serverIPAddress = bb.getInt();
dhcp.gatewayIPAddress = bb.getInt();
- final int hardwareAddressLength = 0xff & dhcp.hardwareAddressLength;
+ final int hardwareAddressLength = UNSIGNED_BYTE_MASK & dhcp.hardwareAddressLength;
dhcp.clientHardwareAddress = new byte[hardwareAddressLength];
bb.get(dhcp.clientHardwareAddress);
- for (int i = hardwareAddressLength; i < 16; ++i) {
+ for (int i = hardwareAddressLength; i < BASE_HW_ADDR_LEN; ++i) {
bb.get();
}
- dhcp.serverName = readString(bb, 64);
- dhcp.bootFileName = readString(bb, 128);
+ dhcp.serverName = readString(bb, BASE_SERVER_NAME_LEN);
+ dhcp.bootFileName = readString(bb, BASE_BOOT_FILE_NAME_LEN);
// read the magic cookie
// magic cookie
- bb.get();
- bb.get();
- bb.get();
- bb.get();
+ bb.getInt();
// read options
boolean foundEndOptionsMarker = false;
while (bb.hasRemaining()) {
- final DHCPOption option = new DHCPOption();
- int code = 0xff & bb.get(); // convert signed byte to int in range
- // [0,255]
- option.setCode((byte) code);
- if (code == 0) {
- // skip these
+ DhcpOption option;
+
+ int pos = bb.position();
+ int optCode = UNSIGNED_BYTE_MASK & bb.array()[pos]; // to unsigned integer
+ int optLen;
+ byte[] optData;
+
+ if (optCode == DHCPOptionCode.OptionCode_Pad.value) {
+ // pad, skip
+ // read option code
+ bb.get();
continue;
- } else if (code != 255) {
- if (bb.hasRemaining()) {
- final int l = 0xff & bb.get(); // convert signed byte to
- // int in range [0,255]
- option.setLength((byte) l);
- if (bb.remaining() >= l) {
- final byte[] optionData = new byte[l];
- bb.get(optionData);
- option.setData(optionData);
- dhcp.options.add(option);
- } else {
- throw new DeserializationException(
- "Buffer underflow while reading DHCP option");
- }
- }
- } else if (code == 255) {
- DHCPOption end = new DHCPOption();
- end.setCode((byte) 255);
- dhcp.options.add(end);
- // remaining bytes are supposed to be 0, but ignore them just in
- // case
+ }
+ if (optCode == (UNSIGNED_BYTE_MASK & DHCPOptionCode.OptionCode_END.value)) {
+ // end of dhcp options or invalid option and set the END option
+ option = new DhcpOption();
+ option.setCode((byte) optCode);
+ dhcp.options.add(option);
foundEndOptionsMarker = true;
break;
}
+
+ if (bb.remaining() < 2) {
+ // No option length
+ throw new DeserializationException("Buffer underflow while reading DHCP option");
+ }
+
+ optLen = UNSIGNED_BYTE_MASK & bb.array()[pos + 1];
+ if (bb.remaining() < DhcpOption.DEFAULT_LEN + optLen) {
+ // Invalid option length
+ throw new DeserializationException("Buffer underflow while reading DHCP option");
+ }
+ optData = new byte[DhcpOption.DEFAULT_LEN + optLen];
+ bb.get(optData);
+ if (OPTION_DESERIALIZERS.containsKey((byte) optCode)) {
+ option = OPTION_DESERIALIZERS.get((byte) optCode).deserialize(optData, 0, optData.length);
+ dhcp.options.add(option);
+ } else {
+ // default option
+ option = DhcpOption.deserializer().deserialize(optData, 0, optData.length);
+ dhcp.options.add(option);
+ }
}
if (!foundEndOptionsMarker) {