[CORD-1429] Add DHCPv6 packet serializer and deserializer

Change-Id: I06338590ccb6c0c3a5d56c89dd23d85646ea159b
This commit is contained in:
Yi Tseng 2017-06-02 17:05:48 -07:00
parent bfc2e92c97
commit 60bf35a29a
4 changed files with 612 additions and 5 deletions

View File

@ -0,0 +1,331 @@
/*
* Copyright 2017-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.packet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Representation of an DHCPv6 Packet.
* Base on RFC-3315.
*/
public class DHCP6 extends BasePacket {
// size of different field of option
private static final int OPT_CODE_SIZE = 2;
private static final int OPT_LEN_SIZE = 2;
// default size of DHCPv6 payload (without options)
private static final int DHCP6_DEFAULT_SIZE = 4;
// default size of DHCPv6 relay message payload (without options)
private static final int DHCP6_RELAY_MSG_SIZE = 34;
private static final int IPV6_ADDR_LEN = 16;
// masks & offsets for default DHCPv6 header
private static final int MSG_TYPE_OFFSET = 24;
private static final int TRANSACTION_ID_MASK = 0x00ffffff;
// Relay message types
private static final Set<Byte> RELAY_MSG_TYPES =
ImmutableSet.of(MsgType.RELAY_FORW.value,
MsgType.RELAY_REPL.value);
/**
* DHCPv6 message type.
*/
public enum MsgType {
SOLICIT((byte) 1), ADVERTISE((byte) 2), REQUEST((byte) 3),
CONFIRM((byte) 4), RENEW((byte) 5), REBIND((byte) 6),
REPLY((byte) 7), RELEASE((byte) 8), DECLINE((byte) 9),
RECONFIGURE((byte) 10), INFORMATION_REQUEST((byte) 11),
RELAY_FORW((byte) 12), RELAY_REPL((byte) 13);
protected byte value;
MsgType(final byte value) {
this.value = value;
}
public byte value() {
return this.value;
}
}
/**
* DHCPv6 option code.
*/
public enum OptionCode {
CLIENTID((short) 1), SERVERID((short) 2), IA_NA((short) 3), IA_TA((short) 4),
IAADDR((short) 5), ORO((short) 6), PREFERENCE((short) 7), ELAPSED_TIME((short) 8),
RELAY_MSG((short) 9), AUTH((short) 11), UNICAST((short) 12),
STATUS_CODE((short) 13), RAPID_COMMIT((short) 14), USER_CLASS((short) 15),
VENDOR_CLASS((short) 16), VENDOR_OPTS((short) 17), INTERFACE_ID((short) 18),
RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20);
protected short value;
OptionCode(final short value) {
this.value = value;
}
public short value() {
return this.value;
}
}
// general field
private byte msgType; // 1 byte
private List<DHCP6Option> options;
// non-relay field
private int transactionId; // 3 bytes
// relay field
private byte hopCount; // 1 byte
private byte[] linkAddress; // 16 bytes
private byte[] peerAddress; // 16 bytes
/**
* Creates new DHCPv6 object.
*/
public DHCP6() {
options = Lists.newArrayList();
}
@Override
public byte[] serialize() {
int payloadLength = options.stream()
.mapToInt(DHCP6Option::getLength)
.sum();
// 2 bytes code and 2 bytes length
payloadLength += options.size() * (OPT_CODE_SIZE + OPT_LEN_SIZE);
if (RELAY_MSG_TYPES.contains(msgType)) {
payloadLength += DHCP6_RELAY_MSG_SIZE;
} else {
payloadLength += DHCP6_DEFAULT_SIZE;
}
ByteBuffer bb = ByteBuffer.allocate(payloadLength);
if (RELAY_MSG_TYPES.contains(msgType)) {
bb.put(msgType);
bb.put(hopCount);
bb.put(linkAddress);
bb.put(peerAddress);
} else {
int defaultHeader = ((int) msgType) << MSG_TYPE_OFFSET | (transactionId & TRANSACTION_ID_MASK);
bb.putInt(defaultHeader);
}
// serialize options
options.forEach(option -> {
bb.putShort(option.getCode());
bb.putShort(option.getLength());
bb.put(option.getData());
});
return bb.array();
}
/**
* Returns a deserializer for DHCPv6.
*
* @return the deserializer for DHCPv6
*/
public static Deserializer<DHCP6> deserializer() {
return (data, offset, length) -> {
DHCP6 dhcp6 = new DHCP6();
checkNotNull(data);
if (offset < 0 || length < 0 ||
length > data.length || offset >= data.length ||
offset + length > data.length) {
throw new DeserializationException("Illegal offset or length");
}
final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
if (bb.remaining() < DHCP6.DHCP6_DEFAULT_SIZE) {
throw new DeserializationException(
"Buffer underflow while reading DHCPv6 option");
}
// peek message type
dhcp6.msgType = bb.array()[0];
if (RELAY_MSG_TYPES.contains(dhcp6.msgType)) {
bb.get(); // drop message type
dhcp6.hopCount = bb.get();
dhcp6.linkAddress = new byte[IPV6_ADDR_LEN];
dhcp6.peerAddress = new byte[IPV6_ADDR_LEN];
bb.get(dhcp6.linkAddress);
bb.get(dhcp6.peerAddress);
} else {
// get msg type + transaction id (1 + 3 bytes)
int defaultHeader = bb.getInt();
dhcp6.transactionId = defaultHeader & TRANSACTION_ID_MASK;
}
dhcp6.options = Lists.newArrayList();
while (bb.remaining() >= OPT_CODE_SIZE) {
DHCP6Option option = new DHCP6Option();
short code = bb.getShort();
if (bb.remaining() < OPT_LEN_SIZE) {
throw new DeserializationException(
"Buffer underflow while reading DHCPv6 option");
}
short optionLen = bb.getShort();
if (bb.remaining() < optionLen) {
throw new DeserializationException(
"Buffer underflow while reading DHCPv6 option");
}
byte[] optionData = new byte[optionLen];
bb.get(optionData);
option.setCode(code);
option.setLength(optionLen);
option.setData(optionData);
dhcp6.options.add(option);
}
return dhcp6;
};
}
@Override
public IPacket deserialize(byte[] data, int offset, int length) {
try {
return deserializer().deserialize(data, offset, length);
} catch (DeserializationException e) {
return null;
}
}
/**
* Gets the message type of this DHCPv6 packet.
*
* @return the message type
*/
public byte getMsgType() {
return msgType;
}
/**
* Gets options from this DHCPv6 packet.
*
* @return DHCPv6 options
*/
public List<DHCP6Option> getOptions() {
return options;
}
/**
* Gets the transaction ID of this DHCPv6 packet.
*
* @return the transaction ID
*/
public int getTransactionId() {
return transactionId;
}
/**
* Gets the hop count of this DHCPv6 relay message.
*
* @return the hop count
*/
public byte getHopCount() {
return hopCount;
}
/**
* Gets the link address of this DHCPv6 relay message.
*
* @return the link address
*/
public byte[] getLinkAddress() {
return linkAddress;
}
/**
* Gets the peer address of this DHCPv6 relay message.
*
* @return the link address
*/
public byte[] getPeerAddress() {
return peerAddress;
}
/**
* Sets message type.
*
* @param msgType the message type
*/
public void setMsgType(byte msgType) {
this.msgType = msgType;
}
/**
* Sets options.
*
* @param options the options
*/
public void setOptions(List<DHCP6Option> options) {
this.options = options;
}
/**
* Sets transaction id.
*
* @param transactionId the transaction id
*/
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
/**
* Sets hop count.
*
* @param hopCount the hop count
*/
public void setHopCount(byte hopCount) {
this.hopCount = hopCount;
}
/**
* Sets link address.
*
* @param linkAddress the link address
*/
public void setLinkAddress(byte[] linkAddress) {
this.linkAddress = linkAddress;
}
/**
* Sets peer address.
*
* @param peerAddress the peer address
*/
public void setPeerAddress(byte[] peerAddress) {
this.peerAddress = peerAddress;
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2017-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.packet;
/**
* Representation of an DHCPv6 Option.
* Base on RFC-3315.
*/
public class DHCP6Option {
private short code;
private short length;
private byte[] data;
/**
* Sets the code of this option.
*
* @param code the code to set
*/
public void setCode(short code) {
this.code = code;
}
/**
* Sets the data and length of this option.
*
* @param data the data to set
*/
public void setData(byte[] data) {
this.data = data;
}
/**
* Sets length of this option.
*
* @param length the length to set
*/
public void setLength(short length) {
this.length = length;
}
/**
* Gets the code of this option.
*
* @return the code
*/
public short getCode() {
return code;
}
/**
* Gets the length of this option.
*
* @return the length of this option
*/
public short getLength() {
return length;
}
/**
* Gets the data of this option.
*
* @return the data of this option
*/
public byte[] getData() {
return data;
}
}

View File

@ -14,8 +14,6 @@
* limitations under the License.
*/
package org.onlab.packet;
import java.nio.ByteBuffer;
@ -32,12 +30,16 @@ import static org.onlab.packet.PacketUtils.*;
public class UDP extends BasePacket {
public static final Map<Integer, Deserializer<? extends IPacket>> PORT_DESERIALIZER_MAP =
ImmutableMap.<Integer, Deserializer<? extends IPacket>>builder()
.put(UDP.DHCP_SERVER_PORT, DHCP.deserializer())
.put(UDP.DHCP_CLIENT_PORT, DHCP.deserializer())
.build();
.put(UDP.DHCP_SERVER_PORT, DHCP.deserializer())
.put(UDP.DHCP_CLIENT_PORT, DHCP.deserializer())
.put(UDP.DHCP_V6_SERVER_PORT, DHCP6.deserializer())
.put(UDP.DHCP_V6_CLIENT_PORT, DHCP6.deserializer())
.build();
public static final int DHCP_SERVER_PORT = 67;
public static final int DHCP_CLIENT_PORT = 68;
public static final int DHCP_V6_SERVER_PORT = 547;
public static final int DHCP_V6_CLIENT_PORT = 546;
private static final short UDP_HEADER_LENGTH = 8;

View File

@ -0,0 +1,193 @@
/*
* Copyright 2017-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.packet;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import java.nio.ByteBuffer;
public class Dhcp6Test {
private static final int OPT_CLIENT_ID = 0xBEEFBEEF;
private static final byte[] OPT_CLIENT_ID_BYTE_ARR =
{(byte) 0xBE, (byte) 0xEF, (byte) 0xBE, (byte) 0xEF};
private static final short OPT_CLIENT_ID_SIZE = 4;
private static final int OPT_AUTH = 0xBA11BA11;
private static final byte[] OPT_AUTH_BYTE_AR =
{(byte) 0xBA, 0x11, (byte) 0xBA, 0x11};
private static final short OPT_AUTH_SIZE = 4;
private static final int TRANSACTION_ID = 0xC0FFEE;
private static final byte[] TRANSACTION_ID_BYTE_ARR =
{(byte) 0xC0, (byte) 0xFF, (byte) 0xEE};
private static final byte HOP_COUNT = 3;
private static final Ip6Address LINK_ADDRESS = Ip6Address.valueOf("1111:2222::8888");
private static final Ip6Address PEER_ADDRESS = Ip6Address.valueOf("3333:4444::9999");
Deserializer<DHCP6> deserializer = DHCP6.deserializer();
private byte[] byteHeader;
@Test
public void testDeserializeBadInput() throws Exception {
PacketTestUtils.testDeserializeBadInput(deserializer);
}
/**
* Truncated a simple DHCPv6 payload.
*/
@Test
public void testDeserializeTruncated() throws Exception {
ByteBuffer bb = ByteBuffer.allocate(4);
bb.put(DHCP6.MsgType.REQUEST.value());
bb.put(TRANSACTION_ID_BYTE_ARR);
byteHeader = bb.array();
PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader);
}
/**
* Basic DHCPv6 header with one msg type and one transaction id.
*/
@Test
public void testDeserializeDefaultPayload() throws Exception {
ByteBuffer bb = ByteBuffer.allocate(12);
bb.put(DHCP6.MsgType.REQUEST.value());
bb.put(TRANSACTION_ID_BYTE_ARR);
// put a simple client id (4 bytes)
bb.putShort(DHCP6.OptionCode.CLIENTID.value());
bb.putShort(OPT_CLIENT_ID_SIZE);
bb.putInt(OPT_CLIENT_ID);
byteHeader = bb.array();
DHCP6 dhcp6 = deserializer.deserialize(byteHeader, 0, byteHeader.length);
assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REQUEST.value());
assertEquals(dhcp6.getTransactionId(), TRANSACTION_ID);
assertEquals(dhcp6.getOptions().size(), 1);
DHCP6Option clientIdOption = dhcp6.getOptions().get(0);
assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
}
/**
* DHCPv6 header with relay agent information.
*/
@Test
public void testDeserializeRelayAgent() throws Exception {
ByteBuffer bb = ByteBuffer.allocate(42);
bb.put(DHCP6.MsgType.RELAY_FORW.value());
bb.put(HOP_COUNT);
bb.put(LINK_ADDRESS.toOctets());
bb.put(PEER_ADDRESS.toOctets());
// put a simple client id (4 bytes)
bb.putShort(DHCP6.OptionCode.CLIENTID.value());
bb.putShort(OPT_CLIENT_ID_SIZE);
bb.putInt(OPT_CLIENT_ID);
byteHeader = bb.array();
DHCP6 dhcp6 = deserializer.deserialize(byteHeader, 0, byteHeader.length);
assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.RELAY_FORW.value());
assertEquals(dhcp6.getHopCount(), HOP_COUNT);
assertArrayEquals(dhcp6.getLinkAddress(), LINK_ADDRESS.toOctets());
assertArrayEquals(dhcp6.getPeerAddress(), PEER_ADDRESS.toOctets());
assertEquals(dhcp6.getOptions().size(), 1);
DHCP6Option clientIdOption = dhcp6.getOptions().get(0);
assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
}
/**
* Serialize DHCPv6 header with default payload and options.
*/
@Test
public void testSerializeDefaultPayload() throws Exception {
DHCP6 dhcp6 = new DHCP6();
dhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
dhcp6.setTransactionId(TRANSACTION_ID);
DHCP6Option opt1 = new DHCP6Option();
opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
opt1.setLength(OPT_CLIENT_ID_SIZE);
opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
DHCP6Option opt2 = new DHCP6Option();
opt2.setCode(DHCP6.OptionCode.AUTH.value());
opt2.setLength(OPT_AUTH_SIZE);
opt2.setData(OPT_AUTH_BYTE_AR);
dhcp6.setOptions(ImmutableList.of(opt1, opt2));
byte[] serialized = dhcp6.serialize();
ByteBuffer expected = ByteBuffer.allocate(20)
.put(DHCP6.MsgType.REQUEST.value())
.put(TRANSACTION_ID_BYTE_ARR)
.putShort(DHCP6.OptionCode.CLIENTID.value())
.putShort(OPT_CLIENT_ID_SIZE)
.putInt(OPT_CLIENT_ID)
.putShort(DHCP6.OptionCode.AUTH.value())
.putShort(OPT_AUTH_SIZE)
.putInt(OPT_AUTH);
assertArrayEquals(serialized, expected.array());
}
/**
* Serialize DHCPv6 header with relay agent payload and options.
*/
@Test
public void testSerializeRelayAgent() throws Exception {
DHCP6 dhcp6 = new DHCP6();
dhcp6.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
dhcp6.setHopCount(HOP_COUNT);
dhcp6.setLinkAddress(LINK_ADDRESS.toOctets());
dhcp6.setPeerAddress(PEER_ADDRESS.toOctets());
DHCP6Option opt1 = new DHCP6Option();
opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
opt1.setLength(OPT_CLIENT_ID_SIZE);
opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
DHCP6Option opt2 = new DHCP6Option();
opt2.setCode(DHCP6.OptionCode.AUTH.value());
opt2.setLength(OPT_AUTH_SIZE);
opt2.setData(OPT_AUTH_BYTE_AR);
dhcp6.setOptions(ImmutableList.of(opt1, opt2));
byte[] serialized = dhcp6.serialize();
ByteBuffer expected = ByteBuffer.allocate(50)
.put(DHCP6.MsgType.RELAY_FORW.value())
.put(HOP_COUNT)
.put(LINK_ADDRESS.toOctets())
.put(PEER_ADDRESS.toOctets())
.putShort(DHCP6.OptionCode.CLIENTID.value())
.putShort(OPT_CLIENT_ID_SIZE)
.putInt(OPT_CLIENT_ID)
.putShort(DHCP6.OptionCode.AUTH.value())
.putShort(OPT_AUTH_SIZE)
.putInt(OPT_AUTH);
assertArrayEquals(serialized, expected.array());
}
}