[CORD-1664] Fix errors when parsing DHCP packets

Change-Id: Ifa9cd3ba04b31f2b7de60fd63dc655978042dbce
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
index fe6debd..924d181 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -39,6 +39,7 @@
  * Base on RFC-3315.
  */
 public class DHCP6 extends BasePacket {
+    private static final int UNSIGNED_SHORT_MASK = 0xffff;
     // size of different field of option
     private static final int OPT_CODE_SIZE = 2;
     private static final int OPT_LEN_SIZE = 2;
@@ -87,7 +88,7 @@
         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);
+        RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20), SUBSCRIBER_ID((short) 38);
 
         protected short value;
         OptionCode(final short value) {
@@ -183,7 +184,7 @@
             }
 
             // peek message type
-            dhcp6.msgType = (byte) (0xff & bb.array()[offset]);
+            dhcp6.msgType = bb.array()[offset];
             if (RELAY_MSG_TYPES.contains(dhcp6.msgType)) {
                 bb.get(); // drop message type
                 dhcp6.hopCount = bb.get();
@@ -202,9 +203,9 @@
             while (bb.remaining() >= Dhcp6Option.DEFAULT_LEN) {
                 // create temporary byte buffer for reading code and length
                 ByteBuffer optByteBuffer =
-                        ByteBuffer.wrap(data, bb.position(), length - bb.position());
+                        ByteBuffer.wrap(data, bb.position(), bb.limit() - bb.position());
                 short code = optByteBuffer.getShort();
-                short optionLen = (short) (0xffff & optByteBuffer.getShort());
+                int optionLen = UNSIGNED_SHORT_MASK & optByteBuffer.getShort();
                 if (optByteBuffer.remaining() < optionLen) {
                     throw new DeserializationException(
                             "Buffer underflow while reading DHCPv6 option");
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientIdOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientIdOption.java
index 0063bf6..5623e71 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientIdOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientIdOption.java
@@ -17,6 +17,7 @@
 
 package org.onlab.packet.dhcp;
 
+import com.google.common.base.MoreObjects;
 import org.onlab.packet.DHCP6;
 import org.onlab.packet.DeserializationException;
 import org.onlab.packet.Deserializer;
@@ -87,4 +88,13 @@
         bb.put(payload.serialize());
         return bb.array();
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("code", getCode())
+                .add("length", getLength())
+                .add("duid", getDuid().toString())
+                .toString();
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Duid.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Duid.java
index e8926ce..df5e4d4 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Duid.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Duid.java
@@ -17,11 +17,14 @@
 
 package org.onlab.packet.dhcp;
 
+import com.google.common.base.MoreObjects;
 import org.onlab.packet.BasePacket;
+import org.onlab.packet.DeserializationException;
 import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 public class Dhcp6Duid extends BasePacket {
     private static final int DEFAULT_LLT_LEN = 8;
@@ -145,7 +148,11 @@
 
     @Override
     public IPacket deserialize(byte[] data, int offset, int length) {
-        return null;
+        try {
+            return deserializer().deserialize(data, offset, length);
+        } catch (DeserializationException e) {
+            throw new RuntimeException("Can't deserialize duid due to {}", e);
+        }
     }
 
     public static Deserializer<Dhcp6Duid> deserializer() {
@@ -178,4 +185,31 @@
             return duid;
         };
     }
+
+    @Override
+    public String toString() {
+        MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(getClass());
+
+        switch (duidType) {
+            case DUID_LLT:
+                helper.add("type", "DUID_LLT");
+                helper.add("hardwareType", hardwareType);
+                helper.add("duidTime", duidTime);
+                helper.add("linkLayerAddress", Arrays.toString(linkLayerAddress));
+                break;
+            case DUID_EN:
+                helper.add("type", "DUID_EN");
+                helper.add("enterpriseNumber", enterpriseNumber);
+                helper.add("id", Arrays.toString(identifier));
+                break;
+            case DUID_LL:
+                helper.add("type", "DUID_LL");
+                helper.add("hardwareType", hardwareType);
+                helper.add("linkLayerAddress", Arrays.toString(linkLayerAddress));
+                break;
+            default:
+                helper.add("type", "Unknown");
+        }
+        return helper.toString();
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaNaOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaNaOption.java
index 578a2f9..f71de3a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaNaOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaNaOption.java
@@ -174,7 +174,8 @@
                                                            optionData.length - bb.position());
                 short code = optByteBuffer.getShort();
                 short len = optByteBuffer.getShort();
-                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + len];
+                int optLen = UNSIGNED_SHORT_MASK & len;
+                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + optLen];
                 bb.get(subOptData);
 
                 // TODO: put more sub-options?
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaTaOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaTaOption.java
index e5eb211..e8fdba6 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaTaOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaTaOption.java
@@ -119,7 +119,8 @@
                                                            optionData.length - bb.position());
                 short code = optByteBuffer.getShort();
                 short len = optByteBuffer.getShort();
-                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + len];
+                int optLen = UNSIGNED_SHORT_MASK & len;
+                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + optLen];
                 bb.get(subOptData);
 
                 // TODO: put more sub-options?
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
index aaf3f66..597732d 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Option.java
@@ -34,6 +34,7 @@
  */
 public class Dhcp6Option extends BasePacket {
     public static final int DEFAULT_LEN = 4;
+    protected static final int UNSIGNED_SHORT_MASK = 0xffff;
     private short code;
     private short length;
     // XXX: use "payload" from BasePacket for option data.
@@ -50,8 +51,8 @@
      * @param dhcp6Option other DHCPv6 option
      */
     public Dhcp6Option(Dhcp6Option dhcp6Option) {
-        this.code = (short) (0xffff & dhcp6Option.code);
-        this.length = (short) (0xffff & dhcp6Option.length);
+        this.code = dhcp6Option.code;
+        this.length = dhcp6Option.length;
         this.payload = dhcp6Option.payload;
         this.payload.setParent(this);
     }
@@ -127,9 +128,10 @@
                                                            "should be at least 4 bytes");
             }
             ByteBuffer bb = ByteBuffer.wrap(data, offset, len);
-            dhcp6Option.code = (short) (0xff & bb.getShort());
-            dhcp6Option.length = (short) (0xff & bb.getShort());
-            byte[] optData = new byte[dhcp6Option.length];
+            dhcp6Option.code = bb.getShort();
+            dhcp6Option.length = bb.getShort();
+            int optionLen = UNSIGNED_SHORT_MASK & dhcp6Option.length;
+            byte[] optData = new byte[optionLen];
             bb.get(optData);
             dhcp6Option.setData(optData);
             return dhcp6Option;
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6RelayOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6RelayOption.java
index 9707f98..4133049 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6RelayOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6RelayOption.java
@@ -17,6 +17,7 @@
 
 package org.onlab.packet.dhcp;
 
+import com.google.common.base.MoreObjects;
 import org.onlab.packet.DHCP6;
 import org.onlab.packet.Deserializer;
 import org.onlab.packet.IPacket;
@@ -36,6 +37,11 @@
         return (short) payload.serialize().length;
     }
 
+    @Override
+    public byte[] getData() {
+        return this.payload.serialize();
+    }
+
     /**
      * Default constructor.
      */
@@ -67,4 +73,13 @@
             return relayOption;
         };
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("code", getCode())
+                .add("length", getLength())
+                .add("data", payload.toString())
+                .toString();
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java
index e54d6b1..76f768f 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpOption.java
@@ -34,6 +34,7 @@
 public class DhcpOption extends BasePacket {
     public static final int OPT_CODE_LEN = 1;
     public static final int DEFAULT_LEN = 2;
+    protected static final int UNSIGNED_BYTE_MASK = 0xff;
     private final Logger log = getLogger(getClass());
     protected byte code;
     protected byte length;
@@ -76,7 +77,8 @@
             dhcpOption.code = byteBuffer.get();
             if (byteBuffer.hasRemaining()) {
                 dhcpOption.length = byteBuffer.get();
-                dhcpOption.data = new byte[dhcpOption.length];
+                int optionLen = UNSIGNED_BYTE_MASK & dhcpOption.length;
+                dhcpOption.data = new byte[optionLen];
                 byteBuffer.get(dhcpOption.data);
             } else {
                 dhcpOption.length = 0;
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java
index e60df53..adda088 100644
--- a/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/DhcpRelayAgentOption.java
@@ -102,7 +102,8 @@
             while (byteBuffer.remaining() >= DEFAULT_LEN) {
                 byte subOptCode = byteBuffer.get();
                 byte subOptLen = byteBuffer.get();
-                byte[] subOptData = new byte[subOptLen];
+                int subOptLenInt = UNSIGNED_BYTE_MASK & subOptLen;
+                byte[] subOptData = new byte[subOptLenInt];
                 byteBuffer.get(subOptData);
 
                 DhcpOption subOption = new DhcpOption();
diff --git a/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java b/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
deleted file mode 100644
index 85b1cfa..0000000
--- a/utils/misc/src/test/java/org/onlab/packet/DhcpTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Foundation
- *
- * 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.base.Charsets;
-import org.apache.commons.lang3.StringUtils;
-import org.junit.Before;
-import org.junit.Test;
-import org.onlab.packet.dhcp.DhcpOption;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Unit tests for DHCP class.
- */
-public class DhcpTest {
-
-    // For serialize test
-    private static final int TRANSACTION_ID = 1000;
-    private static final MacAddress CLIENT1_HOST_MAC = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
-    private static final Ip4Address REQ_IP = Ip4Address.valueOf("10.2.0.2");
-    private static final byte[] EXPECTED_SERIALIZED = ByteBuffer.allocate(300)
-            .put((byte) 0x01) // op code
-            .put((byte) 0x01) // hardware type
-            .put((byte) 0x06) // hardware address len
-            .put((byte) 0x00) // hops
-            .putInt(0x3e8) // transaction id
-            .putShort((short) 0x0) // seconds
-            .putShort((short) 0x0) // flags
-            .putInt(0) // client ip
-            .putInt(0) // your ip
-            .putInt(0) // server ip
-            .putInt(0) // gateway ip
-            .put(CLIENT1_HOST_MAC.toBytes()) // client hardware address
-            .put(new byte[10]) // pad
-            .put(new byte[64]) // server name
-            .put(new byte[128]) // boot file name
-            .putInt(0x63825363) // magic cookie
-            .put(new byte[]{0x35, 0x1, 0x3}) // msg type
-            .put(new byte[]{0x32, 0x4, 0xa, 0x2, 0x0, 0x2}) // requested ip
-            .put((byte) 0xff) // end of options
-            .put(new byte[50]) // pad
-            .array();
-
-    private Deserializer<DHCP> deserializer = DHCP.deserializer();
-
-    private byte opCode = 1;
-    private byte hardwareType = 1;
-    private byte hardwareAddressLength = Ethernet.DATALAYER_ADDRESS_LENGTH;
-    private byte hops = 0;
-    private int transactionId = 0x2ed4eb50;
-    private short seconds = 0;
-    private short flags = 0;
-    private int clientIpAddress = 1;
-    private int yourIpAddress = 2;
-    private int serverIpAddress = 3;
-    private int gatewayIpAddress = 4;
-    private byte[] clientHardwareAddress = MacAddress.valueOf(500).toBytes();
-    private String serverName = "test-server";
-    private String bootFileName = "test-file";
-
-    private String hostName = "test-host";
-    private DhcpOption hostNameOption = new DhcpOption();
-
-    private byte[] byteHeader;
-
-    @Before
-    public void setUp() {
-        hostNameOption.setCode((byte) 55);
-        hostNameOption.setLength((byte) hostName.length());
-        hostNameOption.setData(hostName.getBytes(Charsets.US_ASCII));
-
-        // Packet length is the fixed DHCP header plus option length plus an
-        // extra byte to indicate 'end of options'.
-        ByteBuffer bb = ByteBuffer.allocate(DHCP.MIN_HEADER_LENGTH +
-                                                    2 + hostNameOption.getLength()  + 1);
-
-        bb.put(opCode);
-        bb.put(hardwareType);
-        bb.put(hardwareAddressLength);
-        bb.put(hops);
-        bb.putInt(transactionId);
-        bb.putShort(seconds);
-        bb.putShort(flags);
-        bb.putInt(clientIpAddress);
-        bb.putInt(yourIpAddress);
-        bb.putInt(serverIpAddress);
-        bb.putInt(gatewayIpAddress);
-        bb.put(clientHardwareAddress);
-
-        // need 16 bytes of zeros to pad out the client hardware address field
-        bb.put(new byte[16 - hardwareAddressLength]);
-
-        // Put server name and pad out to 64 bytes
-        bb.put(serverName.getBytes(Charsets.US_ASCII));
-        bb.put(new byte[64 - serverName.length()]);
-
-        // Put boot file name and pad out to 128 bytes
-        bb.put(bootFileName.getBytes(Charsets.US_ASCII));
-        bb.put(new byte[128 - bootFileName.length()]);
-
-        // Magic cookie
-        bb.put("DHCP".getBytes(Charsets.US_ASCII));
-
-        bb.put(hostNameOption.getCode());
-        bb.put(hostNameOption.getLength());
-        bb.put(hostNameOption.getData());
-
-        // End of options marker
-        bb.put((DHCP.DHCPOptionCode.OptionCode_END.getValue()));
-
-        byteHeader = bb.array();
-    }
-
-    @Test
-    public void testDeserializeBadInput() throws Exception {
-        PacketTestUtils.testDeserializeBadInput(deserializer);
-    }
-
-    @Test
-    public void testDeserializeTruncated() throws Exception {
-        PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader);
-    }
-
-    /**
-     * Tests deserialize and getters.
-     */
-    @Test
-    public void testDeserialize() throws Exception {
-        DHCP dhcp = deserializer.deserialize(byteHeader, 0, byteHeader.length);
-
-        assertEquals(opCode, dhcp.opCode);
-        assertEquals(hardwareType, dhcp.hardwareType);
-        assertEquals(hardwareAddressLength, dhcp.hardwareAddressLength);
-        assertEquals(hops, dhcp.hops);
-        assertEquals(transactionId, dhcp.transactionId);
-        assertEquals(seconds, dhcp.seconds);
-        assertEquals(flags, dhcp.flags);
-        assertEquals(clientIpAddress, dhcp.clientIPAddress);
-        assertEquals(yourIpAddress, dhcp.yourIPAddress);
-        assertEquals(serverIpAddress, dhcp.serverIPAddress);
-        assertEquals(gatewayIpAddress, dhcp.gatewayIPAddress);
-        assertTrue(Arrays.equals(clientHardwareAddress, dhcp.clientHardwareAddress));
-
-        assertEquals(serverName, dhcp.serverName);
-        assertEquals(bootFileName, dhcp.bootFileName);
-        assertEquals(2, dhcp.options.size());
-        assertEquals(hostNameOption, dhcp.options.get(0));
-    }
-
-    /**
-     * Tests toString.
-     */
-    @Test
-    public void testToStringDhcp() throws Exception {
-        DHCP dhcp = deserializer.deserialize(byteHeader, 0, byteHeader.length);
-        String str = dhcp.toString();
-
-        assertTrue(StringUtils.contains(str, "opCode=" + opCode));
-        assertTrue(StringUtils.contains(str, "hardwareType=" + hardwareType));
-        assertTrue(StringUtils.contains(str, "hardwareAddressLength=" + hardwareAddressLength));
-        assertTrue(StringUtils.contains(str, "hops=" + hops));
-        assertTrue(StringUtils.contains(str, "transactionId=" + transactionId));
-        assertTrue(StringUtils.contains(str, "seconds=" + seconds));
-        assertTrue(StringUtils.contains(str, "flags=" + flags));
-        assertTrue(StringUtils.contains(str, "clientIPAddress=" + clientIpAddress));
-        assertTrue(StringUtils.contains(str, "yourIPAddress=" + yourIpAddress));
-        assertTrue(StringUtils.contains(str, "serverIPAddress=" + serverIpAddress));
-        assertTrue(StringUtils.contains(str, "gatewayIPAddress=" + gatewayIpAddress));
-        assertTrue(StringUtils.contains(str, "clientHardwareAddress=" + Arrays.toString(clientHardwareAddress)));
-        assertTrue(StringUtils.contains(str, "serverName=" + serverName));
-        assertTrue(StringUtils.contains(str, "bootFileName=" + bootFileName));
-        // TODO: add option unit test
-    }
-
-
-
-    @Test
-    public void testSerialize() throws Exception {
-        DHCP dhcpReply = new DHCP();
-        dhcpReply.setOpCode(DHCP.OPCODE_REQUEST);
-
-        dhcpReply.setYourIPAddress(0);
-        dhcpReply.setServerIPAddress(0);
-
-        dhcpReply.setTransactionId(TRANSACTION_ID);
-        dhcpReply.setClientHardwareAddress(CLIENT1_HOST_MAC.toBytes());
-        dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
-        dhcpReply.setHardwareAddressLength((byte) 6);
-
-        // DHCP Options.
-        DhcpOption option = new DhcpOption();
-        List<DhcpOption> optionList = new ArrayList<>();
-
-        // DHCP Message Type.
-        option.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
-        option.setLength((byte) 1);
-        byte[] optionData = {(byte) DHCP.MsgType.DHCPREQUEST.getValue()};
-        option.setData(optionData);
-        optionList.add(option);
-
-        // DHCP Requested IP.
-        option = new DhcpOption();
-        option.setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue());
-        option.setLength((byte) 4);
-        optionData = REQ_IP.toOctets();
-        option.setData(optionData);
-        optionList.add(option);
-
-        // End Option.
-        option = new DhcpOption();
-        option.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
-        option.setLength((byte) 1);
-        optionList.add(option);
-
-        dhcpReply.setOptions(optionList);
-
-        assertArrayEquals(EXPECTED_SERIALIZED, dhcpReply.serialize());
-    }
-}
diff --git a/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6RelayTest.java b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6RelayTest.java
index 245a100..79b9a46 100644
--- a/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6RelayTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6RelayTest.java
@@ -21,12 +21,15 @@
 import com.google.common.collect.Lists;
 import com.google.common.io.Resources;
 
+import org.apache.commons.io.Charsets;
 import org.junit.Test;
 import org.onlab.packet.DHCP6;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
 
-import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -42,9 +45,9 @@
 
     private static final int HOP_COUNT = 0;
     private static final Ip6Address LINK_ADDRESS = Ip6Address.valueOf("2000::2ff");
-    private static final Ip6Address PEER_ADDRESS = Ip6Address.valueOf("fe80::1");
-    private static final int XID_1 = 13346301;
-    private static final int XID_2 = 9807588;
+    private static final Ip6Address PEER_ADDRESS = Ip6Address.valueOf("fe80::2bb:ff:fe00:1");
+    private static final int XID_1 = 8135067;
+    private static final int XID_2 = 14742082;
     private static final int IA_ID = 1;
     private static final int T1_CLIENT = 3600;
     private static final int T2_CLIENT = 5400;
@@ -54,9 +57,18 @@
     private static final int PREFFERRED_LT_SERVER = 375;
     private static final int VALID_LT_SERVER = 600;
     private static final int PREFFERRED_LT_REQ = 7200;
-    private static final int VALID_LT_REQ = 7500;
+    private static final int VALID_LT_REQ = 10800;
+    private static final int VALID_LT_REQ_2 = 7500;
+    private static final MacAddress DOWNSTREAM_MAC = MacAddress.valueOf("4a:c0:c2:78:92:34");
     private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:bb:00:00:00:01");
-    private static final int CLIENT_DUID_TIME = 0x210016b4;
+    private static final int CLIENT_DUID_TIME = 555636143;
+    private static final MacAddress IPV6_MCAST = MacAddress.valueOf("33:33:00:01:00:03");
+    private static final Ip6Address DOWNSTREAM_LL = Ip6Address.valueOf("fe80::48c0:c2ff:fe78:9234");
+    private static final Ip6Address DHCP6_BRC = Ip6Address.valueOf("ff05::1:3");
+    private static final Ip6Address SERVER_IP = Ip6Address.valueOf("2000::9903");
+    private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:99:66:00:00:01");
+    private static final Ip6Address SERVER_LL = Ip6Address.valueOf("fe80::299:66ff:fe00:1");
+
 
     /**
      * Test deserialize relay message with solicit message.
@@ -66,16 +78,22 @@
     @Test
     public void deserializeSolicit() throws Exception {
         byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT));
-        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 relayMsg = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_FORW.value());
         assertEquals(relayMsg.getHopCount(), HOP_COUNT);
         assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
         assertEquals(relayMsg.getIp6PeerAddress(), PEER_ADDRESS);
 
-        assertEquals(relayMsg.getOptions().size(), 1);
+        assertEquals(relayMsg.getOptions().size(), 2);
         Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.SUBSCRIBER_ID.value());
+        assertEquals(option.getLength(), 10);
+        assertArrayEquals(option.getData(), SERVER_IP.toString().getBytes(Charsets.US_ASCII));
+
+        option = relayMsg.getOptions().get(1);
         assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
-        assertEquals(option.getLength(), 56);
+        assertEquals(option.getLength(), 84);
         assertTrue(option.getPayload() instanceof DHCP6);
 
         DHCP6 relaiedDhcp6 = (DHCP6) option.getPayload();
@@ -113,13 +131,17 @@
         assertTrue(option instanceof Dhcp6IaNaOption);
         Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
         assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
-        assertEquals(iaNaOption.getLength(), 12);
+        assertEquals(iaNaOption.getLength(), 40);
         assertEquals(iaNaOption.getIaId(), IA_ID);
         assertEquals(iaNaOption.getT1(), T1_CLIENT);
         assertEquals(iaNaOption.getT2(), T2_CLIENT);
-        assertEquals(iaNaOption.getOptions().size(), 0);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+        Dhcp6IaAddressOption subOption = (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(subOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(subOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
+        assertEquals(subOption.getValidLifetime(), VALID_LT_REQ);
 
-        assertArrayEquals(data, relayMsg.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -169,17 +191,47 @@
         iaNaOption.setIaId(IA_ID);
         iaNaOption.setT1(T1_CLIENT);
         iaNaOption.setT2(T2_CLIENT);
-        iaNaOption.setOptions(Collections.emptyList());
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
         options.add(iaNaOption);
         relaiedDhcp6.setOptions(options);
 
-
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(relaiedDhcp6);
 
-        relayMsg.setOptions(ImmutableList.of(relayOption));
+        Dhcp6Option subscriberId = new Dhcp6Option();
+        subscriberId.setCode(DHCP6.OptionCode.SUBSCRIBER_ID.value());
+        subscriberId.setLength((short) 10);
+        subscriberId.setData(SERVER_IP.toString().getBytes(Charsets.US_ASCII));
+
+        relayMsg.setOptions(ImmutableList.of(subscriberId, relayOption));
+
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(relayMsg);
+        udp.setChecksum((short) 0x9a99);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 32);
+        ipv6.setSourceAddress(DOWNSTREAM_LL.toOctets());
+        ipv6.setDestinationAddress(DHCP6_BRC.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000cbf64);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(IPV6_MCAST);
+        eth.setSourceMACAddress(DOWNSTREAM_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT)),
-                          relayMsg.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -190,8 +242,8 @@
     @Test
     public void deserializeAdvertise() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(ADVERTISE));
-        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
-
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 relayMsg = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_REPL.value());
         assertEquals(relayMsg.getHopCount(), HOP_COUNT);
         assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
@@ -243,10 +295,13 @@
         option = relaiedDhcp6.getOptions().get(2);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
-
-        assertArrayEquals(data, relayMsg.serialize());
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -295,7 +350,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         relaiedDhcp6.setOptions(options);
@@ -305,8 +365,28 @@
 
         relayMsg.setOptions(ImmutableList.of(relayOption));
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(relayMsg);
+        udp.setChecksum((short) 0x0000019d);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 64);
+        ipv6.setSourceAddress(SERVER_LL.toOctets());
+        ipv6.setDestinationAddress(DOWNSTREAM_LL.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000c72ef);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(DOWNSTREAM_MAC);
+        eth.setSourceMACAddress(SERVER_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(ADVERTISE)),
-                          relayMsg.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -317,15 +397,20 @@
     @Test
     public void deserializeRequest() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(REQUEST));
-        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
-
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 relayMsg = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_FORW.value());
         assertEquals(relayMsg.getHopCount(), HOP_COUNT);
         assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
         assertEquals(relayMsg.getIp6PeerAddress(), PEER_ADDRESS);
 
-        assertEquals(relayMsg.getOptions().size(), 1);
+        assertEquals(relayMsg.getOptions().size(), 2);
         Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.SUBSCRIBER_ID.value());
+        assertEquals(option.getLength(), 10);
+        assertArrayEquals(option.getData(), SERVER_IP.toString().getBytes(Charsets.US_ASCII));
+
+        option = relayMsg.getOptions().get(1);
         assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
         assertEquals(option.getLength(), 102);
         assertTrue(option.getPayload() instanceof DHCP6);
@@ -350,8 +435,12 @@
         option = relaiedDhcp6.getOptions().get(1);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
 
         // Option Request
         option = relaiedDhcp6.getOptions().get(2);
@@ -363,8 +452,7 @@
         option = relaiedDhcp6.getOptions().get(3);
         assertEquals(option.getCode(), DHCP6.OptionCode.ELAPSED_TIME.value());
         assertEquals(option.getLength(), 2);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 0});
+        assertArrayEquals(option.getData(), new byte[]{0, 0});
 
         // IA NA
         option = relaiedDhcp6.getOptions().get(4);
@@ -383,10 +471,10 @@
                 (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
         assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
         assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
-        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ_2);
         assertNull(iaAddressOption.getOptions());
 
-        assertArrayEquals(data, relayMsg.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -421,7 +509,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         // Option request
@@ -442,7 +535,7 @@
         Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
         iaAddressOption.setIp6Address(IA_ADDRESS);
         iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
-        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ_2);
 
         // IA NA
         Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
@@ -454,13 +547,39 @@
 
         relaiedDhcp6.setOptions(options);
 
+        Dhcp6Option subscriberId = new Dhcp6Option();
+        subscriberId.setCode(DHCP6.OptionCode.SUBSCRIBER_ID.value());
+        subscriberId.setLength((short) 10);
+        subscriberId.setData(SERVER_IP.toString().getBytes(Charsets.US_ASCII));
+
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(relaiedDhcp6);
 
-        relayMsg.setOptions(ImmutableList.of(relayOption));
+        relayMsg.setOptions(ImmutableList.of(subscriberId, relayOption));
+
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(relayMsg);
+        udp.setChecksum((short) 0x9aab);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 32);
+        ipv6.setSourceAddress(DOWNSTREAM_LL.toOctets());
+        ipv6.setDestinationAddress(DHCP6_BRC.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000cbf64);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(IPV6_MCAST);
+        eth.setSourceMACAddress(DOWNSTREAM_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
 
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(REQUEST)),
-                          relayMsg.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -471,8 +590,8 @@
     @Test
     public void deserializeReply() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(REPLY));
-        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
-
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 relayMsg = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_REPL.value());
         assertEquals(relayMsg.getHopCount(), HOP_COUNT);
         assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
@@ -524,10 +643,14 @@
         option = relaiedDhcp6.getOptions().get(2);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
 
-        assertArrayEquals(data, relayMsg.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     @Test
@@ -571,7 +694,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         relaiedDhcp6.setOptions(options);
@@ -581,7 +709,28 @@
 
         relayMsg.setOptions(ImmutableList.of(relayOption));
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(relayMsg);
+        udp.setChecksum((short) 0x019d);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 64);
+        ipv6.setSourceAddress(SERVER_LL.toOctets());
+        ipv6.setDestinationAddress(DOWNSTREAM_LL.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000c72ef);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(DOWNSTREAM_MAC);
+        eth.setSourceMACAddress(SERVER_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(REPLY)),
-                          relayMsg.serialize());
+                          eth.serialize());
     }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6Test.java b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6Test.java
index ecfbd4c..795c8aa 100644
--- a/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6Test.java
@@ -23,12 +23,14 @@
 import org.junit.Test;
 import org.onlab.packet.DHCP6;
 import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.PacketTestUtils;
+import org.onlab.packet.UDP;
 
 import java.nio.ByteBuffer;
-import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -40,8 +42,8 @@
     private static final String REQUEST = "dhcp6_request.bin";
     private static final String REPLY = "dhcp6_reply.bin";
 
-    private static final int XID_1 = 13346301;
-    private static final int XID_2 = 9807588;
+    private static final int XID_1 = 13938541;
+    private static final int XID_2 = 11359587;
     private static final int IA_ID = 1;
     private static final int T1_CLIENT = 3600;
     private static final int T2_CLIENT = 5400;
@@ -51,9 +53,16 @@
     private static final int PREFFERRED_LT_SERVER = 375;
     private static final int VALID_LT_SERVER = 600;
     private static final int PREFFERRED_LT_REQ = 7200;
-    private static final int VALID_LT_REQ = 7500;
+    private static final int VALID_LT_REQ = 10800;
+    private static final int VALID_LT_REQ_2 = 7500;
     private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:bb:00:00:00:01");
-    private static final int CLIENT_DUID_TIME = 0x210016b4;
+    private static final int CLIENT_DUID_TIME = 555636143;
+    private static final MacAddress IPV6_MCAST = MacAddress.valueOf("33:33:00:01:00:02");
+    private static final Ip6Address CLIENT_LL = Ip6Address.valueOf("fe80::2bb:ff:fe00:1");
+    private static final Ip6Address DHCP6_BRC = Ip6Address.valueOf("ff02::1:2");
+    private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:99:66:00:00:01");
+    private static final MacAddress UPSTREAM_MAC = MacAddress.valueOf("de:15:19:e4:d7:35");
+    private static final Ip6Address UPSTREAM_LL = Ip6Address.valueOf("fe80::dc15:19ff:fee4:d735");
 
 
     private Deserializer<DHCP6> deserializer = DHCP6.deserializer();
@@ -83,7 +92,8 @@
     @Test
     public void testDeserializeSolicit() throws Exception {
         byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT));
-        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 dhcp6 = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.SOLICIT.value());
         assertEquals(dhcp6.getTransactionId(), XID_1);
         assertEquals(dhcp6.getOptions().size(), 4);
@@ -118,13 +128,17 @@
         assertTrue(option instanceof Dhcp6IaNaOption);
         Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
         assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
-        assertEquals(iaNaOption.getLength(), 12);
+        assertEquals(iaNaOption.getLength(), 40);
         assertEquals(iaNaOption.getIaId(), IA_ID);
         assertEquals(iaNaOption.getT1(), T1_CLIENT);
         assertEquals(iaNaOption.getT2(), T2_CLIENT);
-        assertEquals(iaNaOption.getOptions().size(), 0);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+        Dhcp6IaAddressOption subOption = (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(subOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(subOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
+        assertEquals(subOption.getValidLifetime(), VALID_LT_REQ);
 
-        assertArrayEquals(data, dhcp6.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -168,16 +182,40 @@
         iaNaOption.setIaId(IA_ID);
         iaNaOption.setT1(T1_CLIENT);
         iaNaOption.setT2(T2_CLIENT);
-        iaNaOption.setOptions(Collections.emptyList());
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
         options.add(iaNaOption);
         dhcp6.setOptions(options);
 
-
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(dhcp6);
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(dhcp6);
+        udp.setChecksum((short) 0xffaf);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 1);
+        ipv6.setSourceAddress(CLIENT_LL.toOctets());
+        ipv6.setDestinationAddress(DHCP6_BRC.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000322ad);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(IPV6_MCAST);
+        eth.setSourceMACAddress(CLIENT_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT)),
-                          dhcp6.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -188,9 +226,8 @@
     @Test
     public void deserializeAdvertise() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(ADVERTISE));
-
-
-        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 dhcp6 = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.ADVERTISE.value());
         assertEquals(dhcp6.getTransactionId(), XID_1);
         assertEquals(dhcp6.getOptions().size(), 3);
@@ -230,10 +267,13 @@
         option = dhcp6.getOptions().get(2);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
-
-        assertArrayEquals(data, dhcp6.serialize());
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -276,7 +316,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         dhcp6.setOptions(options);
@@ -284,8 +329,29 @@
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(dhcp6);
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_CLIENT_PORT);
+        udp.setPayload(dhcp6);
+        udp.setChecksum((short) 0xcb5a);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 64);
+        ipv6.setSourceAddress(UPSTREAM_LL.toOctets());
+        ipv6.setDestinationAddress(CLIENT_LL.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000d935f);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(CLIENT_MAC);
+        eth.setSourceMACAddress(UPSTREAM_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(ADVERTISE)),
-                          dhcp6.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -296,7 +362,8 @@
     @Test
     public void deserializeRequest() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(REQUEST));
-        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 dhcp6 = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REQUEST.value());
         assertEquals(dhcp6.getTransactionId(), XID_2);
         assertEquals(dhcp6.getOptions().size(), 5);
@@ -316,8 +383,12 @@
         option = dhcp6.getOptions().get(1);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
 
         // Option Request
         option = dhcp6.getOptions().get(2);
@@ -349,10 +420,10 @@
                 (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
         assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
         assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
-        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ_2);
         assertNull(iaAddressOption.getOptions());
 
-        assertArrayEquals(data, dhcp6.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     /**
@@ -381,7 +452,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         // Option request
@@ -402,7 +478,7 @@
         Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
         iaAddressOption.setIp6Address(IA_ADDRESS);
         iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
-        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ_2);
 
         // IA NA
         Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
@@ -417,8 +493,29 @@
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(dhcp6);
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setPayload(dhcp6);
+        udp.setChecksum((short) 0xffc1);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 1);
+        ipv6.setSourceAddress(CLIENT_LL.toOctets());
+        ipv6.setDestinationAddress(DHCP6_BRC.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000322ad);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(IPV6_MCAST);
+        eth.setSourceMACAddress(CLIENT_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(REQUEST)),
-                          dhcp6.serialize());
+                          eth.serialize());
     }
 
     /**
@@ -429,8 +526,8 @@
     @Test
     public void deserializeReply() throws Exception {
         byte[] data = Resources.toByteArray(getClass().getResource(REPLY));
-
-        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP6 dhcp6 = (DHCP6) eth.getPayload().getPayload().getPayload();
         assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REPLY.value());
         assertEquals(dhcp6.getTransactionId(), XID_2);
         assertEquals(dhcp6.getOptions().size(), 3);
@@ -470,10 +567,14 @@
         option = dhcp6.getOptions().get(2);
         assertEquals(option.getCode(), DHCP6.OptionCode.SERVERID.value());
         assertEquals(option.getLength(), 14);
-        assertArrayEquals(option.getData(),
-                          new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid =
+                Dhcp6Duid.deserializer().deserialize(option.getData(), 0, option.getData().length);
+        assertEquals(serverDuid.getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(serverDuid.getDuidTime(), 0x211e5340);
+        assertEquals(serverDuid.getHardwareType(), 1);
+        assertArrayEquals(serverDuid.getLinkLayerAddress(), SERVER_MAC.toBytes());
 
-        assertArrayEquals(data, dhcp6.serialize());
+        assertArrayEquals(data, eth.serialize());
     }
 
     @Test
@@ -511,7 +612,12 @@
         Dhcp6Option option = new Dhcp6Option();
         option.setCode(DHCP6.OptionCode.SERVERID.value());
         option.setLength((short) 14);
-        option.setData(new byte[]{0, 1, 0, 1, 32, -1, -8, -17, 0, -103, 102, 0, 0, 1});
+        Dhcp6Duid serverDuid = new Dhcp6Duid();
+        serverDuid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        serverDuid.setLinkLayerAddress(SERVER_MAC.toBytes());
+        serverDuid.setHardwareType((short) 1);
+        serverDuid.setDuidTime(0x211e5340);
+        option.setData(serverDuid.serialize());
         options.add(option);
 
         dhcp6.setOptions(options);
@@ -519,7 +625,28 @@
         Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
         relayOption.setPayload(dhcp6);
 
+        UDP udp = new UDP();
+        udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        udp.setDestinationPort(UDP.DHCP_V6_CLIENT_PORT);
+        udp.setPayload(dhcp6);
+        udp.setChecksum((short) 0xcb5a);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setHopLimit((byte) 64);
+        ipv6.setSourceAddress(UPSTREAM_LL.toOctets());
+        ipv6.setDestinationAddress(CLIENT_LL.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+        ipv6.setTrafficClass((byte) 0);
+        ipv6.setFlowLabel(0x000d935f);
+        ipv6.setPayload(udp);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(CLIENT_MAC);
+        eth.setSourceMACAddress(UPSTREAM_MAC);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setPayload(ipv6);
+
         assertArrayEquals(Resources.toByteArray(Dhcp6RelayTest.class.getResource(REPLY)),
-                          dhcp6.serialize());
+                          eth.serialize());
     }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/dhcp/DhcpTest.java b/utils/misc/src/test/java/org/onlab/packet/dhcp/DhcpTest.java
new file mode 100644
index 0000000..c2bbeb6
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/dhcp/DhcpTest.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.dhcp;
+
+import com.google.common.io.Resources;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.lang.ArrayUtils;
+import org.junit.Test;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.PacketTestUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for DHCP class.
+ */
+public class DhcpTest {
+    private static final String DISCOVER = "dhcp_discover.bin";
+    private static final String OFFER = "dhcp_offer.bin";
+    private static final String REQUEST = "dhcp_request.bin";
+    private static final String ACK = "dhcp_ack.bin";
+    private static final String LONG_OPT = "dhcp_long_opt.bin";
+    private static final String EMPTY = "";
+    private static final byte HW_TYPE = 1;
+    private static final byte HW_ADDR_LEN = 6;
+    private static final byte HOPS = 1;
+    private static final int XID = 0x8f5a186c;
+    private static final short SECS = 0;
+    private static final short FLAGS = 0;
+    private static final int NO_IP = 0;
+    private static final Ip4Address GW_IP = Ip4Address.valueOf("10.0.4.254");
+    private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.99.3");
+    private static final MacAddress CLIENT_HW_ADDR = MacAddress.valueOf("00:aa:00:00:00:01");
+    private static final Ip4Address CLIENT_IP = Ip4Address.valueOf("10.0.4.1");
+    private static final Ip4Address DNS_1 = Ip4Address.valueOf("8.8.8.8");
+    private static final Ip4Address DNS_2 = Ip4Address.valueOf("8.8.4.4");
+    private static final Ip4Address SUBNET_MASK = Ip4Address.valueOf("255.255.255.0");
+    private static final String HOSTNAME = "charlie-n";
+    private static final String CIRCUIT_ID = "relay-eth0";
+    private static final String DOMAIN_NAME = "trellis.local";
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(DHCP.deserializer());
+    }
+
+    @Test
+    public void testDeserializeTruncated() throws Exception {
+        byte[] byteHeader = ByteBuffer.allocate(241)
+                .put((byte) 0x01) // op code
+                .put((byte) 0x01) // hardware type
+                .put((byte) 0x06) // hardware address len
+                .put((byte) 0x00) // hops
+                .putInt(0x3e8) // transaction id
+                .putShort((short) 0x0) // seconds
+                .putShort((short) 0x0) // flags
+                .putInt(0) // client ip
+                .putInt(0) // your ip
+                .putInt(0) // server ip
+                .putInt(0) // gateway ip
+                .put(MacAddress.valueOf("1a:1a:1a:1a:1a:1a").toBytes()) // client hardware address
+                .put(new byte[10]) // pad
+                .put(new byte[64]) // server name
+                .put(new byte[128]) // boot file name
+                .putInt(0x63825363) // magic cookie
+                .put((byte) 0xff) // end of options
+                .array();
+        PacketTestUtils.testDeserializeTruncated(DHCP.deserializer(), byteHeader);
+    }
+
+    /**
+     * Tests deserialize discover packet.
+     */
+    @Test
+    public void testDeserializeDiscover() throws Exception {
+        byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(DISCOVER));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP dhcp = (DHCP) eth.getPayload().getPayload().getPayload();
+
+        assertEquals(DHCP.OPCODE_REQUEST, dhcp.getOpCode());
+        assertEquals(HW_TYPE, dhcp.getHardwareType());
+        assertEquals(HW_ADDR_LEN, dhcp.getHardwareAddressLength());
+        assertEquals(HOPS, dhcp.getHops());
+        assertEquals(XID, dhcp.getTransactionId());
+        assertEquals(SECS, dhcp.getSeconds());
+        assertEquals(FLAGS, dhcp.getFlags());
+        assertEquals(NO_IP, dhcp.getClientIPAddress());
+        assertEquals(NO_IP, dhcp.getYourIPAddress());
+        assertEquals(NO_IP, dhcp.getServerIPAddress());
+        assertEquals(GW_IP.toInt(), dhcp.getGatewayIPAddress());
+        assertTrue(Arrays.equals(CLIENT_HW_ADDR.toBytes(), dhcp.getClientHardwareAddress()));
+        assertEquals(EMPTY, dhcp.getServerName());
+        assertEquals(EMPTY, dhcp.getBootFileName());
+        assertEquals(6, dhcp.getOptions().size());
+
+        DhcpOption option = dhcp.getOptions().get(0);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue(), option.code);
+        assertEquals(1, option.length);
+        assertEquals(DHCP.MsgType.DHCPDISCOVER.getValue(), (int) option.getData()[0]);
+
+        option = dhcp.getOptions().get(1);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(CLIENT_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(2);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_HostName.getValue(), option.code);
+        assertEquals(9, option.length);
+        assertArrayEquals(HOSTNAME.getBytes(Charsets.US_ASCII), option.getData());
+
+        option = dhcp.getOptions().get(3);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RequestedParameters.getValue(),
+                     option.code);
+        assertEquals(13, option.length);
+        assertArrayEquals(new byte[]{1, 28, 2, 3, 15, 6, 119, 12, 44, 47, 26, 121, 42},
+                     option.getData());
+
+        option = dhcp.getOptions().get(4);
+        assertTrue(option instanceof DhcpRelayAgentOption);
+        DhcpRelayAgentOption relayAgentOption = (DhcpRelayAgentOption) option;
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue(), relayAgentOption.code);
+        assertEquals(12, relayAgentOption.length);
+        DhcpOption subOption = relayAgentOption
+                .getSubOption(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+        assertEquals(10, subOption.getLength());
+        assertArrayEquals(CIRCUIT_ID.getBytes(Charsets.US_ASCII), subOption.getData());
+
+        option = dhcp.getOptions().get(5);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_END.getValue(), option.code);
+        assertEquals(0, option.length);
+    }
+
+    /**
+     * Tests deserialize discover packet.
+     */
+    @Test
+    public void testDeserializeOffer() throws Exception {
+        byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(OFFER));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP dhcp = (DHCP) eth.getPayload().getPayload().getPayload();
+
+        assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+        assertEquals(HW_TYPE, dhcp.getHardwareType());
+        assertEquals(HW_ADDR_LEN, dhcp.getHardwareAddressLength());
+        assertEquals(HOPS, dhcp.getHops());
+        assertEquals(XID, dhcp.getTransactionId());
+        assertEquals(SECS, dhcp.getSeconds());
+        assertEquals(FLAGS, dhcp.getFlags());
+        assertEquals(NO_IP, dhcp.getClientIPAddress());
+        assertEquals(CLIENT_IP.toInt(), dhcp.getYourIPAddress());
+        assertEquals(SERVER_IP.toInt(), dhcp.getServerIPAddress());
+        assertEquals(GW_IP.toInt(), dhcp.getGatewayIPAddress());
+        assertTrue(Arrays.equals(CLIENT_HW_ADDR.toBytes(), dhcp.getClientHardwareAddress()));
+        assertEquals(EMPTY, dhcp.getServerName());
+        assertEquals(EMPTY, dhcp.getBootFileName());
+        assertEquals(9, dhcp.getOptions().size());
+
+        DhcpOption option = dhcp.getOptions().get(0);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue(), option.code);
+        assertEquals(1, option.length);
+        assertEquals(DHCP.MsgType.DHCPOFFER.getValue(), (int) option.getData()[0]);
+
+        option = dhcp.getOptions().get(1);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DHCPServerIp.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(SERVER_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(2);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_LeaseTime.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(new byte[]{0, 0, 2, 88}, option.getData());
+
+        option = dhcp.getOptions().get(3);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_SubnetMask.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(SUBNET_MASK.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(4);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RouterAddress.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(GW_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(5);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DomainName.getValue(), option.code);
+        assertEquals(13, option.length);
+        assertArrayEquals(DOMAIN_NAME.getBytes(Charsets.US_ASCII), option.getData());
+
+        option = dhcp.getOptions().get(6);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DomainServer.getValue(), option.code);
+        assertEquals(8, option.length);
+        assertArrayEquals(ArrayUtils.addAll(DNS_1.toOctets(), DNS_2.toOctets()),
+                          option.getData());
+
+        option = dhcp.getOptions().get(7);
+        assertTrue(option instanceof DhcpRelayAgentOption);
+        DhcpRelayAgentOption relayAgentOption = (DhcpRelayAgentOption) option;
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue(), relayAgentOption.code);
+        assertEquals(12, relayAgentOption.length);
+        DhcpOption subOption = relayAgentOption
+                .getSubOption(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+        assertEquals(10, subOption.getLength());
+        assertArrayEquals(CIRCUIT_ID.getBytes(Charsets.US_ASCII), subOption.getData());
+
+        option = dhcp.getOptions().get(8);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_END.getValue(), option.code);
+        assertEquals(0, option.length);
+    }
+
+    /**
+     * Tests deserialize discover packet.
+     */
+    @Test
+    public void testDeserializeRequest() throws Exception {
+        byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(REQUEST));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP dhcp = (DHCP) eth.getPayload().getPayload().getPayload();
+
+        assertEquals(DHCP.OPCODE_REQUEST, dhcp.getOpCode());
+        assertEquals(HW_TYPE, dhcp.getHardwareType());
+        assertEquals(HW_ADDR_LEN, dhcp.getHardwareAddressLength());
+        assertEquals(HOPS, dhcp.getHops());
+        assertEquals(XID, dhcp.getTransactionId());
+        assertEquals(SECS, dhcp.getSeconds());
+        assertEquals(FLAGS, dhcp.getFlags());
+        assertEquals(NO_IP, dhcp.getClientIPAddress());
+        assertEquals(NO_IP, dhcp.getYourIPAddress());
+        assertEquals(NO_IP, dhcp.getServerIPAddress());
+        assertEquals(GW_IP.toInt(), dhcp.getGatewayIPAddress());
+        assertTrue(Arrays.equals(CLIENT_HW_ADDR.toBytes(), dhcp.getClientHardwareAddress()));
+        assertEquals(EMPTY, dhcp.getServerName());
+        assertEquals(EMPTY, dhcp.getBootFileName());
+        assertEquals(7, dhcp.getOptions().size());
+
+        DhcpOption option = dhcp.getOptions().get(0);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue(), option.code);
+        assertEquals(1, option.length);
+        assertEquals(DHCP.MsgType.DHCPREQUEST.getValue(), (int) option.getData()[0]);
+
+        option = dhcp.getOptions().get(1);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DHCPServerIp.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(SERVER_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(2);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(CLIENT_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(3);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_HostName.getValue(), option.code);
+        assertEquals(9, option.length);
+        assertArrayEquals(HOSTNAME.getBytes(Charsets.US_ASCII), option.getData());
+
+        option = dhcp.getOptions().get(4);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RequestedParameters.getValue(),
+                     option.code);
+        assertEquals(13, option.length);
+        assertArrayEquals(new byte[]{1, 28, 2, 3, 15, 6, 119, 12, 44, 47, 26, 121, 42},
+                          option.getData());
+
+        option = dhcp.getOptions().get(5);
+        assertTrue(option instanceof DhcpRelayAgentOption);
+        DhcpRelayAgentOption relayAgentOption = (DhcpRelayAgentOption) option;
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue(), relayAgentOption.code);
+        assertEquals(12, relayAgentOption.length);
+        DhcpOption subOption = relayAgentOption
+                .getSubOption(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+        assertEquals(10, subOption.getLength());
+        assertArrayEquals(CIRCUIT_ID.getBytes(Charsets.US_ASCII), subOption.getData());
+
+        option = dhcp.getOptions().get(6);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_END.getValue(), option.code);
+        assertEquals(0, option.length);
+    }
+
+    /**
+     * Tests deserialize discover packet.
+     */
+    @Test
+    public void testDeserializeAck() throws Exception {
+        byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(ACK));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        DHCP dhcp = (DHCP) eth.getPayload().getPayload().getPayload();
+
+        assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+        assertEquals(HW_TYPE, dhcp.getHardwareType());
+        assertEquals(HW_ADDR_LEN, dhcp.getHardwareAddressLength());
+        assertEquals(HOPS, dhcp.getHops());
+        assertEquals(XID, dhcp.getTransactionId());
+        assertEquals(SECS, dhcp.getSeconds());
+        assertEquals(FLAGS, dhcp.getFlags());
+        assertEquals(NO_IP, dhcp.getClientIPAddress());
+        assertEquals(CLIENT_IP.toInt(), dhcp.getYourIPAddress());
+        assertEquals(SERVER_IP.toInt(), dhcp.getServerIPAddress());
+        assertEquals(GW_IP.toInt(), dhcp.getGatewayIPAddress());
+        assertTrue(Arrays.equals(CLIENT_HW_ADDR.toBytes(), dhcp.getClientHardwareAddress()));
+        assertEquals(EMPTY, dhcp.getServerName());
+        assertEquals(EMPTY, dhcp.getBootFileName());
+        assertEquals(9, dhcp.getOptions().size());
+
+        DhcpOption option = dhcp.getOptions().get(0);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue(), option.code);
+        assertEquals(1, option.length);
+        assertEquals(DHCP.MsgType.DHCPACK.getValue(), (int) option.getData()[0]);
+
+        option = dhcp.getOptions().get(1);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DHCPServerIp.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(SERVER_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(2);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_LeaseTime.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(new byte[]{0, 0, 2, 88}, option.getData());
+
+        option = dhcp.getOptions().get(3);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_SubnetMask.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(SUBNET_MASK.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(4);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_RouterAddress.getValue(), option.code);
+        assertEquals(4, option.length);
+        assertArrayEquals(GW_IP.toOctets(), option.getData());
+
+        option = dhcp.getOptions().get(5);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DomainName.getValue(), option.code);
+        assertEquals(13, option.length);
+        assertArrayEquals(DOMAIN_NAME.getBytes(Charsets.US_ASCII), option.getData());
+
+        option = dhcp.getOptions().get(6);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_DomainServer.getValue(), option.code);
+        assertEquals(8, option.length);
+        assertArrayEquals(ArrayUtils.addAll(DNS_1.toOctets(), DNS_2.toOctets()),
+                          option.getData());
+
+        option = dhcp.getOptions().get(7);
+        assertTrue(option instanceof DhcpRelayAgentOption);
+        DhcpRelayAgentOption relayAgentOption = (DhcpRelayAgentOption) option;
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue(), relayAgentOption.code);
+        assertEquals(12, relayAgentOption.length);
+        DhcpOption subOption = relayAgentOption
+                .getSubOption(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+        assertEquals(10, subOption.getLength());
+        assertArrayEquals(CIRCUIT_ID.getBytes(Charsets.US_ASCII), subOption.getData());
+
+        option = dhcp.getOptions().get(8);
+        assertEquals(DHCP.DHCPOptionCode.OptionCode_END.getValue(), option.code);
+        assertEquals(0, option.length);
+    }
+
+    /**
+     * Test option with option length > 128.
+     */
+    @Test
+    public void longOptionTest() throws Exception {
+        byte[] data = Resources.toByteArray(Dhcp6RelayTest.class.getResource(LONG_OPT));
+        DHCP dhcp = DHCP.deserializer().deserialize(data, 0, data.length);
+        assertEquals(2, dhcp.getOptions().size());
+        DhcpOption hostnameOption = dhcp.getOption(DHCP.DHCPOptionCode.OptionCode_HostName);
+        DhcpOption endOption = dhcp.getOption(DHCP.DHCPOptionCode.OptionCode_END);
+        assertNotNull(hostnameOption);
+        assertNotNull(endOption);
+
+        // Host name contains 200 "A"
+        StringBuilder hostnameBuilder = new StringBuilder();
+        IntStream.range(0, 200).forEach(i -> hostnameBuilder.append("A"));
+        String hostname = hostnameBuilder.toString();
+
+        assertEquals((byte) 200, hostnameOption.getLength());
+        assertArrayEquals(hostname.getBytes(Charsets.US_ASCII), hostnameOption.getData());
+    }
+}
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_advertise.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_advertise.bin
index edc32db..5ebba12 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_advertise.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_advertise.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_advertise.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_advertise.bin
index 8ea7fa1..e30948c 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_advertise.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_advertise.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_reply.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_reply.bin
index b08ac4a..8b4fcff 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_reply.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_reply.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_request.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_request.bin
index bbaeb48..18d80ae 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_request.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_request.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_solicit.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_solicit.bin
index 0890d69..08938e5 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_solicit.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_relay_solicit.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_reply.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_reply.bin
index 3d63acc..f8758e0 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_reply.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_reply.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_request.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_request.bin
index a8ec6bf..104d949 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_request.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_request.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_solicit.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_solicit.bin
index 9ab2d29..be8b2ca 100644
--- a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_solicit.bin
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_solicit.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_ack.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_ack.bin
new file mode 100644
index 0000000..3ffb65b
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_ack.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_discover.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_discover.bin
new file mode 100644
index 0000000..e486eea
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_discover.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_long_opt.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_long_opt.bin
new file mode 100644
index 0000000..0ed8ba8
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_long_opt.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_offer.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_offer.bin
new file mode 100644
index 0000000..2bf10f7
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_offer.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_request.bin b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_request.bin
new file mode 100644
index 0000000..38111c9
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp_request.bin
Binary files differ