[CORD-1589] DHCPv6 option de/serializers

Change-Id: I8bd5ffaffc601dcd0d00ec2de9f570e38a995a1d
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 6310ec5..c324d28 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -16,14 +16,22 @@
 
 package org.onlab.packet;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6IaTaOption;
 import org.onlab.packet.dhcp.Dhcp6Option;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
 
 import java.nio.ByteBuffer;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -47,7 +55,7 @@
     private static final int TRANSACTION_ID_MASK = 0x00ffffff;
 
     // Relay message types
-    private static final Set<Byte> RELAY_MSG_TYPES =
+    public static final Set<Byte> RELAY_MSG_TYPES =
             ImmutableSet.of(MsgType.RELAY_FORW.value,
                             MsgType.RELAY_REPL.value);
 
@@ -90,6 +98,13 @@
         }
     }
 
+    private static final Map<Short, Deserializer<Dhcp6Option>> OPT_DESERIALIZERS =
+            ImmutableMap.of(OptionCode.IA_NA.value, Dhcp6IaNaOption.deserializer(),
+                            OptionCode.IA_TA.value, Dhcp6IaTaOption.deserializer(),
+                            OptionCode.IAADDR.value, Dhcp6IaAddressOption.deserializer(),
+                            OptionCode.RELAY_MSG.value, Dhcp6RelayOption.deserializer(),
+                            OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer());
+
     // general field
     private byte msgType; // 1 byte
     private List<Dhcp6Option> options;
@@ -138,9 +153,7 @@
 
         // serialize options
         options.forEach(option -> {
-            bb.putShort(option.getCode());
-            bb.putShort(option.getLength());
-            bb.put(option.getData());
+            bb.put(option.serialize());
         });
 
         return bb.array();
@@ -170,7 +183,7 @@
             }
 
             // peek message type
-            dhcp6.msgType = bb.array()[0];
+            dhcp6.msgType = (byte) (0xff & bb.array()[offset]);
             if (RELAY_MSG_TYPES.contains(dhcp6.msgType)) {
                 bb.get(); // drop message type
                 dhcp6.hopCount = bb.get();
@@ -186,26 +199,25 @@
             }
 
             dhcp6.options = Lists.newArrayList();
-            while (bb.remaining() >= OPT_CODE_SIZE) {
-                Dhcp6Option option = new Dhcp6Option();
-                short code = bb.getShort();
-                if (bb.remaining() < OPT_LEN_SIZE) {
+            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());
+                short code = optByteBuffer.getShort();
+                short optionLen = (short) (0xffff & optByteBuffer.getShort());
+                if (optByteBuffer.remaining() < optionLen) {
                     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];
+                Dhcp6Option option;
+                byte[] optionData = new byte[Dhcp6Option.DEFAULT_LEN + optionLen];
                 bb.get(optionData);
-
-                option.setCode(code);
-                option.setLength(optionLen);
-                option.setData(optionData);
+                if (OPT_DESERIALIZERS.containsKey(code)) {
+                    option = OPT_DESERIALIZERS.get(code).deserialize(optionData, 0, optionData.length);
+                } else {
+                    option = Dhcp6Option.deserializer().deserialize(optionData, 0, optionData.length);
+                }
+                option.setParent(dhcp6);
                 dhcp6.options.add(option);
             }
 
@@ -268,6 +280,15 @@
     }
 
     /**
+     * Gets IPv6 link address.
+     *
+     * @return the IPv6 link address
+     */
+    public Ip6Address getIp6LinkAddress() {
+        return linkAddress == null ? null : Ip6Address.valueOf(linkAddress);
+    }
+
+    /**
      * Gets the peer address of this DHCPv6 relay message.
      *
      * @return the link address
@@ -277,6 +298,15 @@
     }
 
     /**
+     * Gets IPv6 peer address.
+     *
+     * @return the IPv6 peer address
+     */
+    public Ip6Address getIp6PeerAddress() {
+        return peerAddress == null ? null : Ip6Address.valueOf(peerAddress);
+    }
+
+    /**
      * Sets message type.
      *
      * @param msgType the message type
@@ -329,4 +359,24 @@
     public void setPeerAddress(byte[] peerAddress) {
         this.peerAddress = peerAddress;
     }
+
+    @Override
+    public String toString() {
+        if (RELAY_MSG_TYPES.contains(msgType)) {
+            // relay message
+            return toStringHelper(getClass())
+                    .add("msgType", msgType)
+                    .add("hopCount", hopCount)
+                    .add("linkAddress", Ip6Address.valueOf(linkAddress))
+                    .add("peerAddress", Ip6Address.valueOf(peerAddress))
+                    .add("options", options)
+                    .toString();
+        } else {
+            return toStringHelper(getClass())
+                    .add("msgType", msgType)
+                    .add("transactionId", transactionId)
+                    .add("options", options)
+                    .toString();
+        }
+    }
 }
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
new file mode 100644
index 0000000..cec4d97
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientIdOption.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dhcp;
+
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * DHCPv6 Client Identifier Option.
+ */
+public final class Dhcp6ClientIdOption extends Dhcp6Option {
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.CLIENTID.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) payload.serialize().length;
+    }
+
+    @Override
+    public byte[] getData() {
+        return payload.serialize();
+    }
+
+    @Override
+    public void setData(byte[] data) {
+        try {
+            Dhcp6Duid duid = Dhcp6Duid.deserializer().deserialize(data, 0, data.length);
+            this.setDuid(duid);
+        } catch (DeserializationException e) {
+            throw new RuntimeException("Invalid DUID");
+        }
+
+    }
+
+    public Dhcp6Duid getDuid() {
+        return (Dhcp6Duid) payload;
+    }
+
+    public void setDuid(Dhcp6Duid duid) {
+        this.setPayload(duid);
+        duid.setParent(this);
+    }
+
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            Dhcp6ClientIdOption clientIdentifier = new Dhcp6ClientIdOption();
+
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid length of Client Id option");
+            }
+
+            Dhcp6Duid duid =
+                    Dhcp6Duid.deserializer().deserialize(dhcp6Option.getData(), 0, dhcp6Option.getLength());
+            clientIdentifier.setPayload(duid);
+            return clientIdentifier;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        ByteBuffer bb = ByteBuffer.allocate(this.getLength() + Dhcp6Option.DEFAULT_LEN);
+        bb.putShort(getCode());
+        bb.putShort(getLength());
+        bb.put(payload.serialize());
+        return bb.array();
+    }
+}
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
new file mode 100644
index 0000000..5930c87
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6Duid.java
@@ -0,0 +1,181 @@
+/*
+ * 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.dhcp;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+
+import java.nio.ByteBuffer;
+
+public class Dhcp6Duid extends BasePacket {
+    private static final int DEFAULT_LLT_LEN = 8;
+    private static final int DEFAULT_EN_LEN = 6;
+    private static final int DEFAULT_LL_LEN = 4;
+    public enum DuidType {
+        DUID_LLT((short) 1),
+        DUID_EN((short) 2),
+        DUID_LL((short) 3);
+
+        private short value;
+        DuidType(short value) {
+            this.value = value;
+        }
+
+        public short getValue() {
+            return value;
+        }
+
+        public static DuidType of(short type) {
+            switch (type) {
+                case 1:
+                    return DUID_LLT;
+                case 2:
+                    return DUID_EN;
+                case 3:
+                    return DUID_LL;
+                default:
+                    throw new RuntimeException("Unknown type: " + type);
+            }
+        }
+    }
+    // general field
+    private DuidType duidType;
+
+    // fields for DUID_LLT & DUID_LL
+    private short hardwareType;
+    private int duidTime;
+    private byte[] linkLayerAddress;
+
+    // fields for DUID_EN
+    private int enterpriseNumber;
+    private byte[] identifier;
+
+    public DuidType getDuidType() {
+        return duidType;
+    }
+
+    public void setDuidType(DuidType duidType) {
+        this.duidType = duidType;
+    }
+
+    public short getHardwareType() {
+        return hardwareType;
+    }
+
+    public void setHardwareType(short hardwareType) {
+        this.hardwareType = hardwareType;
+    }
+
+    public int getDuidTime() {
+        return duidTime;
+    }
+
+    public void setDuidTime(int duidTime) {
+        this.duidTime = duidTime;
+    }
+
+    public byte[] getLinkLayerAddress() {
+        return linkLayerAddress;
+    }
+
+    public void setLinkLayerAddress(byte[] linkLayerAddress) {
+        this.linkLayerAddress = linkLayerAddress;
+    }
+
+    public int getEnterpriseNumber() {
+        return enterpriseNumber;
+    }
+
+    public void setEnterpriseNumber(int enterpriseNumber) {
+        this.enterpriseNumber = enterpriseNumber;
+    }
+
+    public byte[] getIdentifier() {
+        return identifier;
+    }
+
+    public void setIdentifier(byte[] identifier) {
+        this.identifier = identifier;
+    }
+
+    @Override
+    public byte[] serialize() {
+        ByteBuffer byteBuffer;
+        switch (duidType) {
+            case DUID_LLT:
+                byteBuffer = ByteBuffer.allocate(DEFAULT_LLT_LEN + linkLayerAddress.length);
+                byteBuffer.putShort(duidType.value);
+                byteBuffer.putShort(hardwareType);
+                byteBuffer.putInt(duidTime);
+                byteBuffer.put(linkLayerAddress);
+                break;
+            case DUID_EN:
+                byteBuffer = ByteBuffer.allocate(DEFAULT_EN_LEN + identifier.length);
+                byteBuffer.putShort(duidType.value);
+                byteBuffer.putInt(enterpriseNumber);
+                byteBuffer.put(identifier);
+                break;
+            case DUID_LL:
+                byteBuffer = ByteBuffer.allocate(DEFAULT_LL_LEN + linkLayerAddress.length);
+                byteBuffer.putShort(duidType.value);
+                byteBuffer.putShort(hardwareType);
+                byteBuffer.put(linkLayerAddress);
+                break;
+            default:
+                throw new RuntimeException("Unknown duidType: " + duidType.toString());
+        }
+        return byteBuffer.array();
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        return null;
+    }
+
+    public static Deserializer<Dhcp6Duid> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6Duid duid = new Dhcp6Duid();
+            ByteBuffer byteBuffer = ByteBuffer.wrap(data, offset, length);
+
+            DuidType duidType = DuidType.of(byteBuffer.getShort());
+            duid.setDuidType(duidType);
+            switch (duidType) {
+                case DUID_LLT:
+                    duid.setHardwareType(byteBuffer.getShort());
+                    duid.setDuidTime(byteBuffer.getInt());
+                    duid.linkLayerAddress = new byte[length - DEFAULT_LLT_LEN];
+                    byteBuffer.get(duid.linkLayerAddress);
+                    break;
+                case DUID_EN:
+                    duid.setEnterpriseNumber(byteBuffer.getInt());
+                    duid.identifier = new byte[length - DEFAULT_EN_LEN];
+                    byteBuffer.get(duid.identifier);
+                    break;
+                case DUID_LL:
+                    duid.setHardwareType(byteBuffer.getShort());
+                    duid.linkLayerAddress = new byte[length - DEFAULT_LL_LEN];
+                    byteBuffer.get(duid.linkLayerAddress);
+                    break;
+                default:
+                    throw new RuntimeException("Unknown type: " + duidType);
+            }
+            return duid;
+        };
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaAddressOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaAddressOption.java
new file mode 100644
index 0000000..511b5c7
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaAddressOption.java
@@ -0,0 +1,203 @@
+/*
+ * 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.dhcp;
+
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * IA Address option for DHCPv6.
+ * Based on RFC-3315.
+ */
+public final class Dhcp6IaAddressOption extends Dhcp6Option {
+    public static final int DEFAULT_LEN = 24;
+
+    private Ip6Address ip6Address;
+    private int preferredLifetime;
+    private int validLifetime;
+    private IPacket options;
+
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.IAADDR.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) (options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length);
+    }
+
+    /**
+     * Sets IPv6 address.
+     *
+     * @param ip6Address the IPv6 address
+     */
+    public void setIp6Address(Ip6Address ip6Address) {
+        this.ip6Address = ip6Address;
+    }
+
+    /**
+     * Sets preferred lifetime.
+     *
+     * @param preferredLifetime the preferred lifetime
+     */
+    public void setPreferredLifetime(int preferredLifetime) {
+        this.preferredLifetime = preferredLifetime;
+    }
+
+    /**
+     * Sets valid lifetime.
+     *
+     * @param validLifetime the valid lifetime
+     */
+    public void setValidLifetime(int validLifetime) {
+        this.validLifetime = validLifetime;
+    }
+
+    /**
+     * Sets options data.
+     *
+     * @param options the options data
+     */
+    public void setOptions(IPacket options) {
+        this.options = options;
+    }
+
+    /**
+     * Gets IPv6 address.
+     *
+     * @return the IPv6 address
+     */
+    public Ip6Address getIp6Address() {
+        return ip6Address;
+    }
+
+    /**
+     * Gets preferred lifetime.
+     *
+     * @return the preferred lifetime
+     */
+    public int getPreferredLifetime() {
+        return preferredLifetime;
+    }
+
+    /**
+     * Gets valid lifetime.
+     *
+     * @return the valid lifetime
+     */
+    public int getValidLifetime() {
+        return validLifetime;
+    }
+
+    /**
+     * Gets options of IA Address option.
+     *
+     * @return the options data
+     */
+    public IPacket getOptions() {
+        return options;
+    }
+
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            iaAddressOption.setPayload(dhcp6Option.getPayload());
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid length of IA address option");
+            }
+            ByteBuffer bb = ByteBuffer.wrap(dhcp6Option.getData());
+            byte[] ipv6Addr = new byte[16];
+            bb.get(ipv6Addr);
+            iaAddressOption.ip6Address = Ip6Address.valueOf(ipv6Addr);
+            iaAddressOption.preferredLifetime = bb.getInt();
+            iaAddressOption.validLifetime = bb.getInt();
+
+            // options length of IA Address option
+            int optionsLen = dhcp6Option.getLength() - DEFAULT_LEN;
+            if (optionsLen > 0) {
+                byte[] optionsData = new byte[optionsLen];
+                bb.get(optionsData);
+                iaAddressOption.options =
+                        Data.deserializer().deserialize(optionsData, 0, optionsLen);
+            }
+            return iaAddressOption;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        int payloadLen = options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length;
+        ByteBuffer bb = ByteBuffer.allocate(payloadLen + Dhcp6Option.DEFAULT_LEN);
+        bb.putShort(DHCP6.OptionCode.IAADDR.value());
+        bb.putShort((short) payloadLen);
+        bb.put(ip6Address.toOctets());
+        bb.putInt(preferredLifetime);
+        bb.putInt(validLifetime);
+        if (options != null) {
+            bb.put(options.serialize());
+        }
+        return bb.array();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), ip6Address, preferredLifetime,
+                            validLifetime, options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof Dhcp6IaAddressOption)) {
+            return false;
+        }
+        final Dhcp6IaAddressOption other = (Dhcp6IaAddressOption) obj;
+
+        return Objects.equals(getCode(), other.getCode()) &&
+                Objects.equals(getLength(), other.getLength()) &&
+                Objects.equals(ip6Address, other.ip6Address) &&
+                Objects.equals(preferredLifetime, other.preferredLifetime) &&
+                Objects.equals(validLifetime, other.validLifetime) &&
+                Objects.equals(options, other.options);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("ip6Address", ip6Address)
+                .add("preferredLifetime", preferredLifetime)
+                .add("validLifetime", validLifetime)
+                .add("options", options)
+                .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
new file mode 100644
index 0000000..db190c3
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaNaOption.java
@@ -0,0 +1,243 @@
+/*
+ * 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.dhcp;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * DHCPv6 Identity Association for Non-temporary Addresses Option.
+ * Based on RFC-3315
+ */
+public final class Dhcp6IaNaOption extends Dhcp6Option {
+    public static final int DEFAULT_LEN = 12;
+    private int iaId;
+    private int t1;
+    private int t2;
+    private List<Dhcp6Option> options;
+
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.IA_NA.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) (DEFAULT_LEN + options.stream()
+                        .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+                        .sum());
+
+    }
+
+    /**
+     * Gets Identity Association ID.
+     *
+     * @return the Identity Association ID
+     */
+    public int getIaId() {
+        return iaId;
+    }
+
+    /**
+     * Sets Identity Association ID.
+     *
+     * @param iaId the Identity Association ID.
+     */
+    public void setIaId(int iaId) {
+        this.iaId = iaId;
+    }
+
+    /**
+     * Gets time 1.
+     * The time at which the client contacts the
+     * server from which the addresses in the IA_NA
+     * were obtained to extend the lifetimes of the
+     * addresses assigned to the IA_NA; T1 is a
+     * time duration relative to the current time
+     * expressed in units of seconds.
+     *
+     * @return the value of time 1
+     */
+    public int getT1() {
+        return t1;
+    }
+
+    /**
+     * Sets time 1.
+     *
+     * @param t1 the value of time 1
+     */
+    public void setT1(int t1) {
+        this.t1 = t1;
+    }
+
+    /**
+     * Gets time 2.
+     * The time at which the client contacts any
+     * available server to extend the lifetimes of
+     * the addresses assigned to the IA_NA; T2 is a
+     * time duration relative to the current time
+     * expressed in units of seconds.
+     *
+     * @return the value of time 2
+     */
+    public int getT2() {
+        return t2;
+    }
+
+    /**
+     * Sets time 2.
+     *
+     * @param t2 the value of time 2
+     */
+    public void setT2(int t2) {
+        this.t2 = t2;
+    }
+
+    /**
+     * Gets sub-options.
+     *
+     * @return sub-options of this option
+     */
+    public List<Dhcp6Option> getOptions() {
+        return options;
+    }
+
+    /**
+     * Sets sub-options.
+     *
+     * @param options the sub-options of this option
+     */
+    public void setOptions(List<Dhcp6Option> options) {
+        this.options = options;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6IaNaOption() {
+    }
+
+    /**
+     * Constructs a DHCPv6 IA NA option with DHCPv6 option.
+     *
+     * @param dhcp6Option the DHCPv6 option
+     */
+    public Dhcp6IaNaOption(Dhcp6Option dhcp6Option) {
+        super(dhcp6Option);
+    }
+
+    /**
+     * Gets deserializer.
+     *
+     * @return the deserializer
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid IA NA option data");
+            }
+            Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption(dhcp6Option);
+            byte[] optionData = iaNaOption.getData();
+            ByteBuffer bb = ByteBuffer.wrap(optionData);
+            iaNaOption.iaId = bb.getInt();
+            iaNaOption.t1 = bb.getInt();
+            iaNaOption.t2 = bb.getInt();
+
+            iaNaOption.options = Lists.newArrayList();
+            while (bb.remaining() >= Dhcp6Option.DEFAULT_LEN) {
+                Dhcp6Option option;
+                ByteBuffer optByteBuffer = ByteBuffer.wrap(optionData,
+                                                           bb.position(),
+                                                           optionData.length - bb.position());
+                short code = optByteBuffer.getShort();
+                short len = optByteBuffer.getShort();
+                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + len];
+                bb.get(subOptData);
+
+                // TODO: put more sub-options?
+                if (code == DHCP6.OptionCode.IAADDR.value()) {
+                    option = Dhcp6IaAddressOption.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                } else {
+                    option = Dhcp6Option.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                }
+                iaNaOption.options.add(option);
+            }
+            return iaNaOption;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        int payloadLen = DEFAULT_LEN + options.stream()
+                .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+                .sum();
+        int len = Dhcp6Option.DEFAULT_LEN + payloadLen;
+        ByteBuffer bb = ByteBuffer.allocate(len);
+        bb.putShort(DHCP6.OptionCode.IA_NA.value());
+        bb.putShort((short) payloadLen);
+        bb.putInt(iaId);
+        bb.putInt(t1);
+        bb.putInt(t2);
+
+        options.stream().map(Dhcp6Option::serialize).forEach(bb::put);
+        return bb.array();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(iaId, t1, t2, options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Dhcp6IaNaOption other = (Dhcp6IaNaOption) obj;
+        return Objects.equals(this.iaId, other.iaId)
+                && Objects.equals(this.t1, other.t1)
+                && Objects.equals(this.t2, other.t2)
+                && Objects.equals(this.options, other.options);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("iaId", iaId)
+                .add("t1", t1)
+                .add("t2", t2)
+                .add("options", options)
+                .toString();
+    }
+}
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
new file mode 100644
index 0000000..f075b4d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaTaOption.java
@@ -0,0 +1,178 @@
+/*
+ * 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.dhcp;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+public final class Dhcp6IaTaOption extends Dhcp6Option {
+    public static final int DEFAULT_LEN = 4;
+    private int iaId;
+    private List<Dhcp6Option> options;
+
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.IA_TA.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) (DEFAULT_LEN + options.stream()
+                .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+                .sum());
+    }
+
+    /**
+     * Gets Identity Association ID.
+     *
+     * @return the Identity Association ID
+     */
+    public int getIaId() {
+        return iaId;
+    }
+
+    /**
+     * Sets Identity Association ID.
+     *
+     * @param iaId the Identity Association ID.
+     */
+    public void setIaId(int iaId) {
+        this.iaId = iaId;
+    }
+
+    /**
+     * Gets sub-options.
+     *
+     * @return sub-options of this option
+     */
+    public List<Dhcp6Option> getOptions() {
+        return options;
+    }
+
+    /**
+     * Sets sub-options.
+     *
+     * @param options the sub-options of this option
+     */
+    public void setOptions(List<Dhcp6Option> options) {
+        this.options = options;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6IaTaOption() {
+    }
+
+    /**
+     * Constructs a DHCPv6 IA TA option with DHCPv6 option.
+     *
+     * @param dhcp6Option the DHCPv6 option
+     */
+    public Dhcp6IaTaOption(Dhcp6Option dhcp6Option) {
+        super(dhcp6Option);
+    }
+
+    /**
+     * Gets deserializer.
+     *
+     * @return the deserializer
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, length) -> {
+            Dhcp6Option dhcp6Option =
+                    Dhcp6Option.deserializer().deserialize(data, offset, length);
+            if (dhcp6Option.getLength() < DEFAULT_LEN) {
+                throw new DeserializationException("Invalid IA NA option data");
+            }
+            Dhcp6IaTaOption iaTaOption = new Dhcp6IaTaOption(dhcp6Option);
+            byte[] optionData = iaTaOption.getData();
+            ByteBuffer bb = ByteBuffer.wrap(optionData);
+            iaTaOption.iaId = bb.getInt();
+
+            iaTaOption.options = Lists.newArrayList();
+            while (bb.remaining() >= Dhcp6Option.DEFAULT_LEN) {
+                Dhcp6Option option;
+                ByteBuffer optByteBuffer = ByteBuffer.wrap(optionData,
+                                                           bb.position(),
+                                                           optionData.length - bb.position());
+                short code = optByteBuffer.getShort();
+                short len = optByteBuffer.getShort();
+                byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + len];
+                bb.get(subOptData);
+
+                // TODO: put more sub-options?
+                if (code == DHCP6.OptionCode.IAADDR.value()) {
+                    option = Dhcp6IaAddressOption.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                } else {
+                    option = Dhcp6Option.deserializer()
+                            .deserialize(subOptData, 0, subOptData.length);
+                }
+                iaTaOption.options.add(option);
+            }
+            return iaTaOption;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        int payloadLen = DEFAULT_LEN + options.stream()
+                .mapToInt(opt -> (int) opt.getLength())
+                .sum();
+        int len = Dhcp6Option.DEFAULT_LEN + payloadLen;
+        ByteBuffer bb = ByteBuffer.allocate(len);
+        bb.putShort(DHCP6.OptionCode.IA_TA.value());
+        bb.putShort((short) payloadLen);
+        bb.putInt(iaId);
+        options.stream().map(Dhcp6Option::serialize).forEach(bb::put);
+        return bb.array();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(iaId, options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final Dhcp6IaTaOption other = (Dhcp6IaTaOption) obj;
+        return Objects.equals(this.iaId, other.iaId)
+                && Objects.equals(this.options, other.options);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("iaId", iaId)
+                .add("options", options)
+                .toString();
+    }
+}
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 dc34075..4da0a47 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
@@ -16,14 +16,45 @@
 
 package org.onlab.packet.dhcp;
 
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Representation of an DHCPv6 Option.
  * Base on RFC-3315.
  */
-public class Dhcp6Option {
+public class Dhcp6Option extends BasePacket {
+    public static final int DEFAULT_LEN = 4;
     private short code;
     private short length;
-    private byte[] data;
+    // XXX: use "payload" from BasePacket for option data.
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6Option() {
+    }
+
+    /**
+     * Constructs a DHCPv6 option based on information from other DHCPv6 option.
+     *
+     * @param dhcp6Option other DHCPv6 option
+     */
+    public Dhcp6Option(Dhcp6Option dhcp6Option) {
+        this.code = (short) (0xffff & dhcp6Option.code);
+        this.length = (short) (0xffff & dhcp6Option.length);
+        this.payload = dhcp6Option.payload;
+        this.payload.setParent(this);
+    }
 
     /**
      * Sets the code of this option.
@@ -35,12 +66,16 @@
     }
 
     /**
-     * Sets the data and length of this option.
+     * Sets the data of this option.
      *
      * @param data the data to set
      */
     public void setData(byte[] data) {
-        this.data = data;
+        try {
+            this.payload = Data.deserializer().deserialize(data, 0, data.length);
+        } catch (DeserializationException e) {
+            throw new RuntimeException("Invalid data");
+        }
     }
 
     /**
@@ -76,6 +111,80 @@
      * @return the data of this option
      */
     public byte[] getData() {
-        return data;
+        return payload.serialize();
+    }
+
+    /**
+     * Gets deserializer of DHCPv6 option.
+     *
+     * @return the deserializer of DHCPv6 option
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, len) -> {
+            Dhcp6Option dhcp6Option = new Dhcp6Option();
+            if (len < DEFAULT_LEN) {
+                throw new DeserializationException("DHCPv6 option code length" +
+                                                           "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];
+            bb.get(optData);
+            dhcp6Option.setData(optData);
+            return dhcp6Option;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        ByteBuffer bb = ByteBuffer.allocate(DEFAULT_LEN + getLength());
+        bb.putShort(getCode());
+        bb.putShort(getLength());
+        bb.put(payload.serialize());
+        return bb.array();
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        try {
+            return deserializer().deserialize(data, offset, length);
+        } catch (DeserializationException e) {
+            throw new RuntimeException("Can't deserialize data for DHCPv6 option.", e);
+        }
+    }
+
+    protected ToStringHelper getToStringHelper() {
+        return toStringHelper(Dhcp6Option.class)
+                .add("code", code)
+                .add("length", length);
+    }
+
+    @Override
+    public String toString() {
+        return getToStringHelper()
+                .add("data", payload.toString())
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hash(code, length);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Dhcp6Option other = (Dhcp6Option) obj;
+        return Objects.equals(this.code, other.code)
+                && Objects.equals(this.length, other.length);
     }
 }
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
new file mode 100644
index 0000000..bf3a1af
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6RelayOption.java
@@ -0,0 +1,70 @@
+/*
+ * 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.dhcp;
+
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+
+/**
+ * Relay option for DHCPv6.
+ * Based on RFC-3315.
+ */
+public final class Dhcp6RelayOption extends Dhcp6Option {
+    @Override
+    public short getCode() {
+        return DHCP6.OptionCode.RELAY_MSG.value();
+    }
+
+    @Override
+    public short getLength() {
+        return (short) payload.serialize().length;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Dhcp6RelayOption() {
+    }
+
+    /**
+     * Constructs a DHCPv6 relay option with DHCPv6 option.
+     *
+     * @param dhcp6Option the DHCPv6 option
+     */
+    public Dhcp6RelayOption(Dhcp6Option dhcp6Option) {
+        super(dhcp6Option);
+    }
+
+    /**
+     * Gets deserializer for DHCPv6 relay option.
+     *
+     * @return the deserializer
+     */
+    public static Deserializer<Dhcp6Option> deserializer() {
+        return (data, offset, len) -> {
+            Dhcp6Option dhcp6Option = Dhcp6Option.deserializer().deserialize(data, offset, len);
+            IPacket payload = DHCP6.deserializer()
+                    .deserialize(dhcp6Option.getData(), 0, dhcp6Option.getLength());
+            Dhcp6RelayOption relayOption = new Dhcp6RelayOption(dhcp6Option);
+            relayOption.setPayload(payload);
+            payload.setParent(relayOption);
+            return relayOption;
+        };
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java b/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java
deleted file mode 100644
index 7b83c00..0000000
--- a/utils/misc/src/test/java/org/onlab/packet/Dhcp6Test.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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 org.onlab.packet.dhcp.Dhcp6Option;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import java.nio.ByteBuffer;
-
-public class Dhcp6Test {
-    private static final int OPT_CLIENT_ID = 0xBEEFBEEF;
-    private static final byte[] OPT_CLIENT_ID_BYTE_ARR =
-            {(byte) 0xBE, (byte) 0xEF, (byte) 0xBE, (byte) 0xEF};
-    private static final short OPT_CLIENT_ID_SIZE = 4;
-    private static final int OPT_AUTH = 0xBA11BA11;
-    private static final byte[] OPT_AUTH_BYTE_AR =
-            {(byte) 0xBA, 0x11, (byte) 0xBA, 0x11};
-    private static final short OPT_AUTH_SIZE = 4;
-    private static final int TRANSACTION_ID = 0xC0FFEE;
-    private static final byte[] TRANSACTION_ID_BYTE_ARR =
-            {(byte) 0xC0, (byte) 0xFF, (byte) 0xEE};
-    private static final byte HOP_COUNT = 3;
-    private static final Ip6Address LINK_ADDRESS = Ip6Address.valueOf("1111:2222::8888");
-    private static final Ip6Address PEER_ADDRESS = Ip6Address.valueOf("3333:4444::9999");
-    Deserializer<DHCP6> deserializer = DHCP6.deserializer();
-    private byte[] byteHeader;
-
-    @Test
-    public void testDeserializeBadInput() throws Exception {
-        PacketTestUtils.testDeserializeBadInput(deserializer);
-    }
-
-    /**
-     * Truncated a simple DHCPv6 payload.
-     */
-    @Test
-    public void testDeserializeTruncated() throws Exception {
-        ByteBuffer bb = ByteBuffer.allocate(4);
-        bb.put(DHCP6.MsgType.REQUEST.value());
-        bb.put(TRANSACTION_ID_BYTE_ARR);
-        byteHeader = bb.array();
-
-        PacketTestUtils.testDeserializeTruncated(deserializer, byteHeader);
-    }
-
-    /**
-     * Basic DHCPv6 header with one msg type and one transaction id.
-     */
-    @Test
-    public void testDeserializeDefaultPayload() throws Exception {
-        ByteBuffer bb = ByteBuffer.allocate(12);
-        bb.put(DHCP6.MsgType.REQUEST.value());
-        bb.put(TRANSACTION_ID_BYTE_ARR);
-
-        // put a simple client id (4 bytes)
-        bb.putShort(DHCP6.OptionCode.CLIENTID.value());
-        bb.putShort(OPT_CLIENT_ID_SIZE);
-        bb.putInt(OPT_CLIENT_ID);
-        byteHeader = bb.array();
-
-        DHCP6 dhcp6 = deserializer.deserialize(byteHeader, 0, byteHeader.length);
-        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REQUEST.value());
-        assertEquals(dhcp6.getTransactionId(), TRANSACTION_ID);
-        assertEquals(dhcp6.getOptions().size(), 1);
-
-        Dhcp6Option clientIdOption = dhcp6.getOptions().get(0);
-        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
-        assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
-    }
-
-    /**
-     * DHCPv6 header with relay agent information.
-     */
-    @Test
-    public void testDeserializeRelayAgent() throws Exception {
-        ByteBuffer bb = ByteBuffer.allocate(42);
-        bb.put(DHCP6.MsgType.RELAY_FORW.value());
-        bb.put(HOP_COUNT);
-
-        bb.put(LINK_ADDRESS.toOctets());
-        bb.put(PEER_ADDRESS.toOctets());
-
-        // put a simple client id (4 bytes)
-        bb.putShort(DHCP6.OptionCode.CLIENTID.value());
-        bb.putShort(OPT_CLIENT_ID_SIZE);
-        bb.putInt(OPT_CLIENT_ID);
-        byteHeader = bb.array();
-
-        DHCP6 dhcp6 = deserializer.deserialize(byteHeader, 0, byteHeader.length);
-        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.RELAY_FORW.value());
-        assertEquals(dhcp6.getHopCount(), HOP_COUNT);
-        assertArrayEquals(dhcp6.getLinkAddress(), LINK_ADDRESS.toOctets());
-        assertArrayEquals(dhcp6.getPeerAddress(), PEER_ADDRESS.toOctets());
-        assertEquals(dhcp6.getOptions().size(), 1);
-
-        Dhcp6Option clientIdOption = dhcp6.getOptions().get(0);
-        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
-        assertArrayEquals(clientIdOption.getData(), OPT_CLIENT_ID_BYTE_ARR);
-    }
-
-    /**
-     * Serialize DHCPv6 header with default payload and options.
-     */
-    @Test
-    public void testSerializeDefaultPayload() throws Exception {
-        DHCP6 dhcp6 = new DHCP6();
-        dhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
-        dhcp6.setTransactionId(TRANSACTION_ID);
-
-        Dhcp6Option opt1 = new Dhcp6Option();
-        opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
-        opt1.setLength(OPT_CLIENT_ID_SIZE);
-        opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
-
-
-        Dhcp6Option opt2 = new Dhcp6Option();
-        opt2.setCode(DHCP6.OptionCode.AUTH.value());
-        opt2.setLength(OPT_AUTH_SIZE);
-        opt2.setData(OPT_AUTH_BYTE_AR);
-
-        dhcp6.setOptions(ImmutableList.of(opt1, opt2));
-
-        byte[] serialized = dhcp6.serialize();
-        ByteBuffer expected = ByteBuffer.allocate(20)
-                .put(DHCP6.MsgType.REQUEST.value())
-                .put(TRANSACTION_ID_BYTE_ARR)
-                .putShort(DHCP6.OptionCode.CLIENTID.value())
-                .putShort(OPT_CLIENT_ID_SIZE)
-                .putInt(OPT_CLIENT_ID)
-                .putShort(DHCP6.OptionCode.AUTH.value())
-                .putShort(OPT_AUTH_SIZE)
-                .putInt(OPT_AUTH);
-
-        assertArrayEquals(serialized, expected.array());
-    }
-
-    /**
-     * Serialize DHCPv6 header with relay agent payload and options.
-     */
-    @Test
-    public void testSerializeRelayAgent() throws Exception {
-        DHCP6 dhcp6 = new DHCP6();
-        dhcp6.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
-        dhcp6.setHopCount(HOP_COUNT);
-        dhcp6.setLinkAddress(LINK_ADDRESS.toOctets());
-        dhcp6.setPeerAddress(PEER_ADDRESS.toOctets());
-
-        Dhcp6Option opt1 = new Dhcp6Option();
-        opt1.setCode(DHCP6.OptionCode.CLIENTID.value());
-        opt1.setLength(OPT_CLIENT_ID_SIZE);
-        opt1.setData(OPT_CLIENT_ID_BYTE_ARR);
-
-
-        Dhcp6Option opt2 = new Dhcp6Option();
-        opt2.setCode(DHCP6.OptionCode.AUTH.value());
-        opt2.setLength(OPT_AUTH_SIZE);
-        opt2.setData(OPT_AUTH_BYTE_AR);
-
-        dhcp6.setOptions(ImmutableList.of(opt1, opt2));
-
-        byte[] serialized = dhcp6.serialize();
-        ByteBuffer expected = ByteBuffer.allocate(50)
-                .put(DHCP6.MsgType.RELAY_FORW.value())
-                .put(HOP_COUNT)
-                .put(LINK_ADDRESS.toOctets())
-                .put(PEER_ADDRESS.toOctets())
-                .putShort(DHCP6.OptionCode.CLIENTID.value())
-                .putShort(OPT_CLIENT_ID_SIZE)
-                .putInt(OPT_CLIENT_ID)
-                .putShort(DHCP6.OptionCode.AUTH.value())
-                .putShort(OPT_AUTH_SIZE)
-                .putInt(OPT_AUTH);
-
-        assertArrayEquals(serialized, expected.array());
-    }
-}
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
new file mode 100644
index 0000000..f8a0425
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6RelayTest.java
@@ -0,0 +1,586 @@
+/*
+ * 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.dhcp;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test serializing/deserializing DHCPv6 relay message.
+ */
+public class Dhcp6RelayTest {
+    private static final String SOLICIT = "dhcp6_relay_solicit.bin";
+    private static final String ADVERTISE = "dhcp6_relay_advertise.bin";
+    private static final String REQUEST = "dhcp6_relay_request.bin";
+    private static final String REPLY = "dhcp6_relay_reply.bin";
+
+    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 int IA_ID = 1;
+    private static final int T1_CLIENT = 3600;
+    private static final int T2_CLIENT = 5400;
+    private static final int T1_SERVER = 0;
+    private static final int T2_SERVER = 0;
+    private static final Ip6Address IA_ADDRESS = Ip6Address.valueOf("2000::201");
+    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 MacAddress CLIENT_MAC = MacAddress.valueOf("00:bb:00:00:00:01");
+    private static final int CLIENT_DUID_TIME = 0x210016b4;
+
+    /**
+     * Test deserialize relay message with solicit message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeSolicit() throws Exception {
+        byte[] data = IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT));
+        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
+        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);
+        Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
+        assertEquals(option.getLength(), 56);
+        assertTrue(option.getPayload() instanceof DHCP6);
+
+        DHCP6 relaiedDhcp6 = (DHCP6) option.getPayload();
+        assertEquals(relaiedDhcp6.getMsgType(), DHCP6.MsgType.SOLICIT.value());
+        assertEquals(relaiedDhcp6.getTransactionId(), XID_1);
+        assertEquals(relaiedDhcp6.getOptions().size(), 4);
+
+        // Client ID
+        option = relaiedDhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // ORO
+        option = relaiedDhcp6.getOptions().get(1);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ORO.value());
+        assertEquals(option.getLength(), 8);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+
+        // ELAPSED_TIME
+        option = relaiedDhcp6.getOptions().get(2);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ELAPSED_TIME.value());
+        assertEquals(option.getLength(), 2);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 0});
+
+        // IA NA
+        option = relaiedDhcp6.getOptions().get(3);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 12);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_CLIENT);
+        assertEquals(iaNaOption.getT2(), T2_CLIENT);
+        assertEquals(iaNaOption.getOptions().size(), 0);
+
+        assertArrayEquals(data, relayMsg.serialize());
+    }
+
+    /**
+     * Test serialize relay message with solicit message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeSolicit() throws Exception {
+        DHCP6 relayMsg = new DHCP6();
+        relayMsg.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
+        relayMsg.setHopCount((byte) HOP_COUNT);
+        relayMsg.setLinkAddress(LINK_ADDRESS.toOctets());
+        relayMsg.setPeerAddress(PEER_ADDRESS.toOctets());
+
+        DHCP6 relaiedDhcp6 = new DHCP6();
+        relaiedDhcp6.setMsgType(DHCP6.MsgType.SOLICIT.value());
+        relaiedDhcp6.setTransactionId(XID_1);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Option request
+        Dhcp6Option option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ORO.value());
+        option.setLength((short) 8);
+        option.setData(new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+        options.add(option);
+
+        // Elapsed Time
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ELAPSED_TIME.value());
+        option.setLength((short) 2);
+        option.setData(new byte[]{0, 0});
+        options.add(option);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_CLIENT);
+        iaNaOption.setT2(T2_CLIENT);
+        iaNaOption.setOptions(Collections.emptyList());
+        options.add(iaNaOption);
+        relaiedDhcp6.setOptions(options);
+
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(relaiedDhcp6);
+
+        relayMsg.setOptions(ImmutableList.of(relayOption));
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT)),
+                          relayMsg.serialize());
+    }
+
+    /**
+     * Test deserialize relay message with advertise message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeAdvertise() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(ADVERTISE));
+        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
+
+        assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_REPL.value());
+        assertEquals(relayMsg.getHopCount(), HOP_COUNT);
+        assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
+        assertEquals(relayMsg.getIp6PeerAddress(), PEER_ADDRESS);
+
+        assertEquals(relayMsg.getOptions().size(), 1);
+        Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
+        assertEquals(option.getLength(), 84);
+        assertTrue(option.getPayload() instanceof DHCP6);
+
+        DHCP6 relaiedDhcp6 = (DHCP6) option.getPayload();
+        assertEquals(relaiedDhcp6.getMsgType(), DHCP6.MsgType.ADVERTISE.value());
+        assertEquals(relaiedDhcp6.getTransactionId(), XID_1);
+        assertEquals(relaiedDhcp6.getOptions().size(), 3);
+
+        // IA NA
+        option = relaiedDhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_SERVER);
+        assertEquals(iaNaOption.getT2(), T2_SERVER);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_SERVER);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_SERVER);
+        assertNull(iaAddressOption.getOptions());
+
+        // Client ID
+        option = relaiedDhcp6.getOptions().get(1);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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());
+    }
+
+    /**
+     * Test serialize relay message with advertise message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeAdvertise() throws Exception {
+        DHCP6 relayMsg = new DHCP6();
+        relayMsg.setMsgType(DHCP6.MsgType.RELAY_REPL.value());
+        relayMsg.setHopCount((byte) HOP_COUNT);
+        relayMsg.setLinkAddress(LINK_ADDRESS.toOctets());
+        relayMsg.setPeerAddress(PEER_ADDRESS.toOctets());
+
+        DHCP6 relaiedDhcp6 = new DHCP6();
+        relaiedDhcp6.setMsgType(DHCP6.MsgType.ADVERTISE.value());
+        relaiedDhcp6.setTransactionId(XID_1);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_SERVER);
+        iaAddressOption.setValidLifetime(VALID_LT_SERVER);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_SERVER);
+        iaNaOption.setT2(T2_SERVER);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        relaiedDhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(relaiedDhcp6);
+
+        relayMsg.setOptions(ImmutableList.of(relayOption));
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(ADVERTISE)),
+                          relayMsg.serialize());
+    }
+
+    /**
+     * Test deserialize relay message with request message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeRequest() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(REQUEST));
+        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
+
+        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);
+        Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
+        assertEquals(option.getLength(), 102);
+        assertTrue(option.getPayload() instanceof DHCP6);
+
+        DHCP6 relaiedDhcp6 = (DHCP6) option.getPayload();
+        assertEquals(relaiedDhcp6.getMsgType(), DHCP6.MsgType.REQUEST.value());
+        assertEquals(relaiedDhcp6.getTransactionId(), XID_2);
+        assertEquals(relaiedDhcp6.getOptions().size(), 5);
+
+        // Client ID
+        option = relaiedDhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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});
+
+        // Option Request
+        option = relaiedDhcp6.getOptions().get(2);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ORO.value());
+        assertEquals(option.getLength(), 8);
+        assertArrayEquals(option.getData(), new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+
+        // ELAPSED_TIME
+        option = relaiedDhcp6.getOptions().get(3);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ELAPSED_TIME.value());
+        assertEquals(option.getLength(), 2);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 0});
+
+        // IA NA
+        option = relaiedDhcp6.getOptions().get(4);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_CLIENT);
+        assertEquals(iaNaOption.getT2(), T2_CLIENT);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ);
+        assertNull(iaAddressOption.getOptions());
+
+        assertArrayEquals(data, relayMsg.serialize());
+    }
+
+    /**
+     * Test serialize relay message with request message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeRequest() throws Exception {
+        DHCP6 relayMsg = new DHCP6();
+        relayMsg.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
+        relayMsg.setHopCount((byte) HOP_COUNT);
+        relayMsg.setLinkAddress(LINK_ADDRESS.toOctets());
+        relayMsg.setPeerAddress(PEER_ADDRESS.toOctets());
+
+        DHCP6 relaiedDhcp6 = new DHCP6();
+        relaiedDhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
+        relaiedDhcp6.setTransactionId(XID_2);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        // Option request
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ORO.value());
+        option.setLength((short) 8);
+        option.setData(new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+        options.add(option);
+
+        // Elapsed Time
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ELAPSED_TIME.value());
+        option.setLength((short) 2);
+        option.setData(new byte[]{0, 0});
+        options.add(option);
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_CLIENT);
+        iaNaOption.setT2(T2_CLIENT);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        relaiedDhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(relaiedDhcp6);
+
+        relayMsg.setOptions(ImmutableList.of(relayOption));
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(REQUEST)),
+                          relayMsg.serialize());
+    }
+
+    /**
+     * Test deserialize relay message with reply message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeReply() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(REPLY));
+        DHCP6 relayMsg = DHCP6.deserializer().deserialize(data, 0, data.length);
+
+        assertEquals(relayMsg.getMsgType(), DHCP6.MsgType.RELAY_REPL.value());
+        assertEquals(relayMsg.getHopCount(), HOP_COUNT);
+        assertEquals(relayMsg.getIp6LinkAddress(), LINK_ADDRESS);
+        assertEquals(relayMsg.getIp6PeerAddress(), PEER_ADDRESS);
+
+        assertEquals(relayMsg.getOptions().size(), 1);
+        Dhcp6Option option = relayMsg.getOptions().get(0);
+        assertEquals(option.getCode(), DHCP6.OptionCode.RELAY_MSG.value());
+        assertEquals(option.getLength(), 84);
+        assertTrue(option.getPayload() instanceof DHCP6);
+
+        DHCP6 relaiedDhcp6 = (DHCP6) option.getPayload();
+        assertEquals(relaiedDhcp6.getMsgType(), DHCP6.MsgType.REPLY.value());
+        assertEquals(relaiedDhcp6.getTransactionId(), XID_2);
+        assertEquals(relaiedDhcp6.getOptions().size(), 3);
+
+        // IA NA
+        option = relaiedDhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_SERVER);
+        assertEquals(iaNaOption.getT2(), T2_SERVER);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_SERVER);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_SERVER);
+        assertNull(iaAddressOption.getOptions());
+
+        // Client ID
+        option = relaiedDhcp6.getOptions().get(1);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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());
+    }
+
+    @Test
+    public void serializeReply() throws Exception {
+        DHCP6 relayMsg = new DHCP6();
+        relayMsg.setMsgType(DHCP6.MsgType.RELAY_REPL.value());
+        relayMsg.setHopCount((byte) HOP_COUNT);
+        relayMsg.setLinkAddress(LINK_ADDRESS.toOctets());
+        relayMsg.setPeerAddress(PEER_ADDRESS.toOctets());
+
+        DHCP6 relaiedDhcp6 = new DHCP6();
+        relaiedDhcp6.setMsgType(DHCP6.MsgType.REPLY.value());
+        relaiedDhcp6.setTransactionId(XID_2);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_SERVER);
+        iaAddressOption.setValidLifetime(VALID_LT_SERVER);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_SERVER);
+        iaNaOption.setT2(T2_SERVER);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        relaiedDhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(relaiedDhcp6);
+
+        relayMsg.setOptions(ImmutableList.of(relayOption));
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(REPLY)),
+                          relayMsg.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
new file mode 100644
index 0000000..4996ae6
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/dhcp/Dhcp6Test.java
@@ -0,0 +1,524 @@
+/*
+ * 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.dhcp;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.PacketTestUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNull;
+
+public class Dhcp6Test {
+    private static final String SOLICIT = "dhcp6_solicit.bin";
+    private static final String ADVERTISE = "dhcp6_advertise.bin";
+    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 IA_ID = 1;
+    private static final int T1_CLIENT = 3600;
+    private static final int T2_CLIENT = 5400;
+    private static final int T1_SERVER = 0;
+    private static final int T2_SERVER = 0;
+    private static final Ip6Address IA_ADDRESS = Ip6Address.valueOf("2000::201");
+    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 MacAddress CLIENT_MAC = MacAddress.valueOf("00:bb:00:00:00:01");
+    private static final int CLIENT_DUID_TIME = 0x210016b4;
+
+
+    private Deserializer<DHCP6> deserializer = DHCP6.deserializer();
+
+
+    @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(new byte[]{0x00, 0x00});
+        PacketTestUtils.testDeserializeTruncated(deserializer, bb.array());
+    }
+
+    /**
+     * Test DHCPv6 solicit message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void testDeserializeSolicit() throws Exception {
+        byte[] data = IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT));
+        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.SOLICIT.value());
+        assertEquals(dhcp6.getTransactionId(), XID_1);
+        assertEquals(dhcp6.getOptions().size(), 4);
+
+        // Client ID
+        Dhcp6Option option = dhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // ORO
+        option = dhcp6.getOptions().get(1);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ORO.value());
+        assertEquals(option.getLength(), 8);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+
+        // ELAPSED_TIME
+        option = dhcp6.getOptions().get(2);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ELAPSED_TIME.value());
+        assertEquals(option.getLength(), 2);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 0});
+
+        // IA NA
+        option = dhcp6.getOptions().get(3);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 12);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_CLIENT);
+        assertEquals(iaNaOption.getT2(), T2_CLIENT);
+        assertEquals(iaNaOption.getOptions().size(), 0);
+
+        assertArrayEquals(data, dhcp6.serialize());
+    }
+
+    /**
+     * Test serialize solicit message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeSolicit() throws Exception {
+        DHCP6 dhcp6 = new DHCP6();
+        dhcp6.setMsgType(DHCP6.MsgType.SOLICIT.value());
+        dhcp6.setTransactionId(XID_1);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Option request
+        Dhcp6Option option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ORO.value());
+        option.setLength((short) 8);
+        option.setData(new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+        options.add(option);
+
+        // Elapsed Time
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ELAPSED_TIME.value());
+        option.setLength((short) 2);
+        option.setData(new byte[]{0, 0});
+        options.add(option);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_CLIENT);
+        iaNaOption.setT2(T2_CLIENT);
+        iaNaOption.setOptions(Collections.emptyList());
+        options.add(iaNaOption);
+        dhcp6.setOptions(options);
+
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(dhcp6);
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(SOLICIT)),
+                          dhcp6.serialize());
+    }
+
+    /**
+     * Test deserialize advertise message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeAdvertise() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(ADVERTISE));
+
+
+        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.ADVERTISE.value());
+        assertEquals(dhcp6.getTransactionId(), XID_1);
+        assertEquals(dhcp6.getOptions().size(), 3);
+
+        // IA NA
+        Dhcp6Option option = dhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_SERVER);
+        assertEquals(iaNaOption.getT2(), T2_SERVER);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_SERVER);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_SERVER);
+        assertNull(iaAddressOption.getOptions());
+
+        // Client ID
+        option = dhcp6.getOptions().get(1);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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());
+    }
+
+    /**
+     * Test serialize advertise message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeAdvertise() throws Exception {
+        DHCP6 dhcp6 = new DHCP6();
+        dhcp6.setMsgType(DHCP6.MsgType.ADVERTISE.value());
+        dhcp6.setTransactionId(XID_1);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_SERVER);
+        iaAddressOption.setValidLifetime(VALID_LT_SERVER);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_SERVER);
+        iaNaOption.setT2(T2_SERVER);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        dhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(dhcp6);
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(ADVERTISE)),
+                          dhcp6.serialize());
+    }
+
+    /**
+     * Test deserialize request message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeRequest() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(REQUEST));
+        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REQUEST.value());
+        assertEquals(dhcp6.getTransactionId(), XID_2);
+        assertEquals(dhcp6.getOptions().size(), 5);
+
+        // Client ID
+        Dhcp6Option option = dhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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});
+
+        // Option Request
+        option = dhcp6.getOptions().get(2);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ORO.value());
+        assertEquals(option.getLength(), 8);
+        assertArrayEquals(option.getData(), new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+
+        // ELAPSED_TIME
+        option = dhcp6.getOptions().get(3);
+        assertEquals(option.getCode(), DHCP6.OptionCode.ELAPSED_TIME.value());
+        assertEquals(option.getLength(), 2);
+        assertArrayEquals(option.getData(),
+                          new byte[]{0, 0});
+
+        // IA NA
+        option = dhcp6.getOptions().get(4);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_CLIENT);
+        assertEquals(iaNaOption.getT2(), T2_CLIENT);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_REQ);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_REQ);
+        assertNull(iaAddressOption.getOptions());
+
+        assertArrayEquals(data, dhcp6.serialize());
+    }
+
+    /**
+     * Test serialize request message.
+     *
+     * @throws Exception exception while serialize the DHCPv6 payload
+     */
+    @Test
+    public void serializeRequest() throws Exception {
+        DHCP6 dhcp6 = new DHCP6();
+        dhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
+        dhcp6.setTransactionId(XID_2);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        // Option request
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ORO.value());
+        option.setLength((short) 8);
+        option.setData(new byte[]{0, 23, 0, 24, 0, 39, 0, 31});
+        options.add(option);
+
+        // Elapsed Time
+        option = new Dhcp6Option();
+        option.setCode(DHCP6.OptionCode.ELAPSED_TIME.value());
+        option.setLength((short) 2);
+        option.setData(new byte[]{0, 0});
+        options.add(option);
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_REQ);
+        iaAddressOption.setValidLifetime(VALID_LT_REQ);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_CLIENT);
+        iaNaOption.setT2(T2_CLIENT);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        dhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(dhcp6);
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(REQUEST)),
+                          dhcp6.serialize());
+    }
+
+    /**
+     * Test deserialize relay message with reply message.
+     *
+     * @throws Exception exception while deserialize the DHCPv6 payload
+     */
+    @Test
+    public void deserializeReply() throws Exception {
+        byte[] data = IOUtils.toByteArray(getClass().getResource(REPLY));
+
+        DHCP6 dhcp6 = DHCP6.deserializer().deserialize(data, 0, data.length);
+        assertEquals(dhcp6.getMsgType(), DHCP6.MsgType.REPLY.value());
+        assertEquals(dhcp6.getTransactionId(), XID_2);
+        assertEquals(dhcp6.getOptions().size(), 3);
+
+        // IA NA
+        Dhcp6Option option = dhcp6.getOptions().get(0);
+        assertTrue(option instanceof Dhcp6IaNaOption);
+        Dhcp6IaNaOption iaNaOption = (Dhcp6IaNaOption) option;
+        assertEquals(iaNaOption.getCode(), DHCP6.OptionCode.IA_NA.value());
+        assertEquals(iaNaOption.getLength(), 40);
+        assertEquals(iaNaOption.getIaId(), IA_ID);
+        assertEquals(iaNaOption.getT1(), T1_SERVER);
+        assertEquals(iaNaOption.getT2(), T2_SERVER);
+        assertEquals(iaNaOption.getOptions().size(), 1);
+
+        // IA Address (in IA NA)
+        assertTrue(iaNaOption.getOptions().get(0) instanceof Dhcp6IaAddressOption);
+        Dhcp6IaAddressOption iaAddressOption =
+                (Dhcp6IaAddressOption) iaNaOption.getOptions().get(0);
+        assertEquals(iaAddressOption.getIp6Address(), IA_ADDRESS);
+        assertEquals(iaAddressOption.getPreferredLifetime(), PREFFERRED_LT_SERVER);
+        assertEquals(iaAddressOption.getValidLifetime(), VALID_LT_SERVER);
+        assertNull(iaAddressOption.getOptions());
+
+        // Client ID
+        option = dhcp6.getOptions().get(1);
+        assertTrue(option instanceof Dhcp6ClientIdOption);
+        Dhcp6ClientIdOption clientIdOption = (Dhcp6ClientIdOption) option;
+        assertEquals(clientIdOption.getCode(), DHCP6.OptionCode.CLIENTID.value());
+        assertEquals(clientIdOption.getLength(), 14);
+        assertEquals(clientIdOption.getDuid().getDuidType(), Dhcp6Duid.DuidType.DUID_LLT);
+        assertEquals(clientIdOption.getDuid().getHardwareType(), 1);
+        assertEquals(clientIdOption.getDuid().getDuidTime(), CLIENT_DUID_TIME);
+        assertArrayEquals(clientIdOption.getDuid().getLinkLayerAddress(), CLIENT_MAC.toBytes());
+
+        // Server ID
+        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());
+    }
+
+    @Test
+    public void serializeReply() throws Exception {
+        DHCP6 dhcp6 = new DHCP6();
+        dhcp6.setMsgType(DHCP6.MsgType.REPLY.value());
+        dhcp6.setTransactionId(XID_2);
+        List<Dhcp6Option> options = Lists.newArrayList();
+
+        // IA address
+        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+        iaAddressOption.setIp6Address(IA_ADDRESS);
+        iaAddressOption.setPreferredLifetime(PREFFERRED_LT_SERVER);
+        iaAddressOption.setValidLifetime(VALID_LT_SERVER);
+
+        // IA NA
+        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+        iaNaOption.setIaId(IA_ID);
+        iaNaOption.setT1(T1_SERVER);
+        iaNaOption.setT2(T2_SERVER);
+        iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+        options.add(iaNaOption);
+
+        // Client ID
+        Dhcp6Duid duid = new Dhcp6Duid();
+        duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        duid.setHardwareType((short) 1);
+        duid.setDuidTime(CLIENT_DUID_TIME);
+        duid.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        clientIdOption.setDuid(duid);
+        options.add(clientIdOption);
+
+        // Server ID
+        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});
+        options.add(option);
+
+        dhcp6.setOptions(options);
+
+        Dhcp6RelayOption relayOption = new Dhcp6RelayOption();
+        relayOption.setPayload(dhcp6);
+
+        assertArrayEquals(IOUtils.toByteArray(Dhcp6RelayTest.class.getResource(REPLY)),
+                          dhcp6.serialize());
+    }
+}
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
new file mode 100644
index 0000000..edc32db
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8ea7fa1
--- /dev/null
+++ 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
new file mode 100644
index 0000000..b08ac4a
--- /dev/null
+++ 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
new file mode 100644
index 0000000..bbaeb48
--- /dev/null
+++ 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
new file mode 100644
index 0000000..0890d69
--- /dev/null
+++ 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
new file mode 100644
index 0000000..3d63acc
--- /dev/null
+++ 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
new file mode 100644
index 0000000..a8ec6bf
--- /dev/null
+++ 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
new file mode 100644
index 0000000..9ab2d29
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/dhcp/dhcp6_solicit.bin
Binary files differ