From 60bf35a29a1bd3c27dd0f9ca23bb445ae8a1554c Mon Sep 17 00:00:00 2001 From: Yi Tseng Date: Fri, 2 Jun 2017 17:05:48 -0700 Subject: [PATCH] [CORD-1429] Add DHCPv6 packet serializer and deserializer Change-Id: I06338590ccb6c0c3a5d56c89dd23d85646ea159b --- .../src/main/java/org/onlab/packet/DHCP6.java | 331 ++++++++++++++++++ .../java/org/onlab/packet/DHCP6Option.java | 81 +++++ .../src/main/java/org/onlab/packet/UDP.java | 12 +- .../test/java/org/onlab/packet/Dhcp6Test.java | 193 ++++++++++ 4 files changed, 612 insertions(+), 5 deletions(-) create mode 100644 utils/misc/src/main/java/org/onlab/packet/DHCP6.java create mode 100644 utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java create mode 100644 utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java new file mode 100644 index 0000000000..b6aa691bc2 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java @@ -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 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 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 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 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 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; + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java new file mode 100644 index 0000000000..f70a1c5c70 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6Option.java @@ -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; + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/UDP.java b/utils/misc/src/main/java/org/onlab/packet/UDP.java index edf99af131..6849654ffa 100644 --- a/utils/misc/src/main/java/org/onlab/packet/UDP.java +++ b/utils/misc/src/main/java/org/onlab/packet/UDP.java @@ -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> PORT_DESERIALIZER_MAP = ImmutableMap.>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; diff --git a/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java b/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java new file mode 100644 index 0000000000..927266d89c --- /dev/null +++ b/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java @@ -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 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()); + } +}