ADD/REMOVE Option82

Change-Id: I51cfb92f4b2d07c5b588fd96ceb6a37afc6f8f38
diff --git a/pom.xml b/pom.xml
index 6ab1da6..2a9d48a 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Copyright 2016-present Open Networking Laboratory
+  ~ Copyright 2016-present Open Networking Foundation
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.onosproject</groupId>
         <artifactId>onos-dependencies</artifactId>
-        <version>1.10.0</version>
+        <version>1.8.0</version>
         <relativePath></relativePath>
     </parent>
 
@@ -40,7 +40,7 @@
         <onos.app.category>default</onos.app.category>
         <onos.app.url>http://opencord.org</onos.app.url>
         <onos.app.readme>DHCP L2 Relay Agent Application.</onos.app.readme>
-        <onos.version>1.10.0</onos.version>
+        <onos.version>1.8.0</onos.version>
     </properties>
 
     <dependencies>
@@ -52,8 +52,8 @@
 
         <dependency>
             <groupId>org.opencord</groupId>
-            <artifactId>sadis</artifactId>
-            <version>1.0.0</version>
+            <artifactId>sadis-api</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
         </dependency>
 
         <dependency>
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
index a224b6d..cdffe4e 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Laboratory
+ * Copyright 2017-present Open Networking Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
 package org.opencord.dhcpl2relay;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -23,17 +25,19 @@
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.MacAddress;
+import org.onlab.packet.DeserializationException;
 import org.onlab.packet.DHCP;
 import org.onlab.packet.DHCPOption;
 import org.onlab.packet.DHCPPacketType;
-import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
-
+import org.opencord.dhcpl2relay.packet.DhcpEthernet;
+import org.opencord.dhcpl2relay.packet.DhcpOption82;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
@@ -61,15 +65,16 @@
 
 import org.opencord.sadis.SubscriberAndDeviceInformation;
 import org.opencord.sadis.SubscriberAndDeviceInformationService;
-
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
-import java.util.Set;
+import java.util.List;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
 import static org.onlab.packet.MacAddress.valueOf;
@@ -144,7 +149,9 @@
         packetService.addProcessor(dhcpRelayPacketProcessor,
                 PacketProcessor.director(0));
         requestDhcpPackets();
-        modified(context);
+        if (context != null) {
+            modified(context);
+        }
 
         log.info("DHCP-L2-RELAY Started");
     }
@@ -163,6 +170,7 @@
 
     @Modified
     protected void modified(ComponentContext context) {
+
         Dictionary<?, ?> properties = context.getProperties();
 
         Boolean o = Tools.isPropertyEnabled(properties, "option82");
@@ -186,8 +194,15 @@
             log.warn("Dhcp Server info not available");
             return;
         }
+        if (dhcpServerConnectPoint == null) {
+            dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
+            requestDhcpPackets();
+        } else {
+            cancelDhcpPackets();
+            dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
+            requestDhcpPackets();
+        }
 
-        dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
         log.info("dhcp server connect point: " + dhcpServerConnectPoint);
     }
 
@@ -224,16 +239,14 @@
                     .matchIPProtocol(IPv4.PROTOCOL_UDP)
                     .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
             packetService.cancelPackets(selectorServer.build(),
-                    PacketPriority.CONTROL, appId,
-                    Optional.of(dhcpServerConnectPoint.deviceId()));
+                    PacketPriority.CONTROL, appId, Optional.of(dhcpServerConnectPoint.deviceId()));
 
             TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
                     .matchEthType(Ethernet.TYPE_IPV4)
                     .matchIPProtocol(IPv4.PROTOCOL_UDP)
                     .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
             packetService.cancelPackets(selectorClient.build(),
-                    PacketPriority.CONTROL, appId,
-                    Optional.of(dhcpServerConnectPoint.deviceId()));
+                    PacketPriority.CONTROL, appId, Optional.of(dhcpServerConnectPoint.deviceId()));
         }
     }
 
@@ -263,7 +276,7 @@
 
     private MacAddress relayAgentMacAddress(PacketContext context) {
 
-        SubscriberAndDeviceInformation device = getDevice(context);
+        SubscriberAndDeviceInformation device = this.getDevice(context);
         if (device == null) {
             log.warn("Device not found for {}", context.inPacket().
                     receivedFrom());
@@ -275,12 +288,10 @@
 
     private String nasPortId(PacketContext context) {
         Port p = deviceService.getPort(context.inPacket().receivedFrom());
-
         return p.annotations().value(AnnotationKeys.PORT_NAME);
     }
 
     private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
-
         return subsService.get(nasPortId(context));
     }
 
@@ -314,21 +325,28 @@
             }
 
             // process the packet and get the payload
-            Ethernet packet = context.inPacket().parsed();
+            DhcpEthernet packet = null;
+            ByteBuffer byteBuffer = context.inPacket().unparsed();
+            try {
+                packet = DhcpEthernet.deserializer().deserialize(byteBuffer.array(), 0, byteBuffer.array().length);
+            } catch (DeserializationException e) {
+                log.warn("Unable to deserialize packet");
+            }
+
             if (packet == null) {
                 log.warn("Packet is null");
                 return;
             }
 
-            //log.info("Got a packet of type {}", packet.getEtherType());
+            log.debug("Got a packet ", packet);
 
-            if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
+            if (packet.getEtherType() == DhcpEthernet.TYPE_IPV4) {
                 IPv4 ipv4Packet = (IPv4) packet.getPayload();
 
                 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
                     UDP udpPacket = (UDP) ipv4Packet.getPayload();
                     if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
-                        udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
+                            udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
                         DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
                         //This packet is dhcp.
                         processDhcpPacket(context, packet, dhcpPayload);
@@ -338,7 +356,7 @@
         }
 
         //forward the packet to ConnectPoint where the DHCP server is attached.
-        private void forwardPacket(Ethernet packet) {
+        private void forwardPacket(DhcpEthernet packet) {
 
             if (dhcpServerConnectPoint != null) {
                 TrafficTreatment t = DefaultTrafficTreatment.builder()
@@ -347,8 +365,8 @@
                         dhcpServerConnectPoint.deviceId(), t,
                         ByteBuffer.wrap(packet.serialize()));
                 if (log.isTraceEnabled()) {
-                    log.trace("Relaying packet to dhcp server {} at {}",
-                            packet, dhcpServerConnectPoint);
+                log.trace("Relaying packet to dhcp server {} at {}",
+                        packet, dhcpServerConnectPoint);
                 }
                 packetService.emit(o);
             } else {
@@ -357,7 +375,7 @@
         }
 
         //process the dhcp packet before sending to server
-        private void processDhcpPacket(PacketContext context, Ethernet packet,
+        private void processDhcpPacket(PacketContext context, DhcpEthernet packet,
                                        DHCP dhcpPayload) {
             if (dhcpPayload == null) {
                 log.warn("DHCP payload is null");
@@ -365,6 +383,7 @@
             }
 
             DHCPPacketType incomingPacketType = null;
+
             for (DHCPOption option : dhcpPayload.getOptions()) {
                 if (option.getCode() == OptionCode_MessageType.getValue()) {
                     byte[] data = option.getData();
@@ -372,75 +391,86 @@
                 }
             }
             log.info("Received DHCP Packet of type {}", incomingPacketType);
-            log.trace("Processing Packet {}", packet);
 
             switch (incomingPacketType) {
-            case DHCPDISCOVER:
-                Ethernet ethernetPacketDiscover =
-                    processDhcpPacketFromClient(context, packet);
-                if (ethernetPacketDiscover != null) {
-                    forwardPacket(ethernetPacketDiscover);
-                }
-                break;
-            case DHCPOFFER:
-                //reply to dhcp client.
-                Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
-                if (ethernetPacketOffer != null) {
-                    sendReply(ethernetPacketOffer, dhcpPayload);
-                }
-                break;
-            case DHCPREQUEST:
-                Ethernet ethernetPacketRequest =
-                    processDhcpPacketFromClient(context, packet);
-                if (ethernetPacketRequest != null) {
-                    forwardPacket(ethernetPacketRequest);
-                }
-                break;
-            case DHCPACK:
-                //reply to dhcp client.
-                Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
-                if (ethernetPacketAck != null) {
-                    sendReply(ethernetPacketAck, dhcpPayload);
-                }
-                break;
-            default:
-                break;
+                case DHCPDISCOVER:
+                    DhcpEthernet ethernetPacketDiscover =
+                            processDhcpPacketFromClient(context, packet);
+                    if (ethernetPacketDiscover != null) {
+                        forwardPacket(ethernetPacketDiscover);
+                    }
+                    break;
+                case DHCPOFFER:
+                    //reply to dhcp client.
+                    DhcpEthernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
+                    if (ethernetPacketOffer != null) {
+                        sendReply(ethernetPacketOffer, dhcpPayload);
+                    }
+                    break;
+                case DHCPREQUEST:
+                    DhcpEthernet ethernetPacketRequest =
+                            processDhcpPacketFromClient(context, packet);
+                    if (ethernetPacketRequest != null) {
+                        forwardPacket(ethernetPacketRequest);
+                    }
+                    break;
+                case DHCPACK:
+                    //reply to dhcp client.
+                    DhcpEthernet ethernetPacketAck = processDhcpPacketFromServer(packet);
+                    if (ethernetPacketAck != null) {
+                        sendReply(ethernetPacketAck, dhcpPayload);
+                    }
+                    break;
+                default:
+                    break;
             }
         }
 
-        private Ethernet processDhcpPacketFromClient(PacketContext context,
-                                                     Ethernet ethernetPacket) {
+        private DhcpEthernet processDhcpPacketFromClient(PacketContext context,
+                                                         DhcpEthernet ethernetPacket) {
             log.info("Processing packet from client");
 
             MacAddress relayAgentMac = relayAgentMacAddress(context);
             if (relayAgentMac == null) {
                 log.warn("RelayAgent MAC not found ");
-
                 return null;
             }
 
-            Ethernet etherReply = ethernetPacket;
+            DhcpEthernet etherReply = ethernetPacket;
 
             IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
             UDP udpPacket = (UDP) ipv4Packet.getPayload();
             DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
 
-            etherReply.setSourceMACAddress(relayAgentMac);
-            etherReply.setDestinationMACAddress(dhcpConnectMac);
+            if (option82) {
+                SubscriberAndDeviceInformation entry = getSubscriber(context);
+                if (entry == null) {
+                    log.info("Dropping packet as subscriber entry is not available");
+                    return null;
+                }
+
+                DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry);
+                udpPacket.setPayload(dhcpPacketWithOption82);
+            }
+
+            ipv4Packet.setPayload(udpPacket);
+            etherReply.setPayload(ipv4Packet);
+            etherReply.setSourceMacAddress(relayAgentMac);
+            etherReply.setDestinationMacAddress(dhcpConnectMac);
 
             etherReply.setVlanID(cTag(context).toShort());
-            etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
-            etherReply.setQinQVID(sTag(context).toShort());
+            etherReply.setQinQtpid(DhcpEthernet.TYPE_QINQ);
+            etherReply.setQinQVid(sTag(context).toShort());
 
-            log.info("Finished processing");
+            log.info("Finished processing packet");
             return etherReply;
         }
 
         //build the DHCP offer/ack with proper client port.
-        private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
-            log.warn("Processing DHCP packet from server");
+        private DhcpEthernet processDhcpPacketFromServer(DhcpEthernet ethernetPacket) {
+            log.info("Processing DHCP packet from server");
             // get dhcp header.
-            Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+            DhcpEthernet etherReply = (DhcpEthernet) ethernetPacket.clone();
             IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
             UDP udpPacket = (UDP) ipv4Packet.getPayload();
             DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
@@ -450,8 +480,8 @@
             Set<Host> hosts = hostService.getHostsByMac(dstMac);
             if (hosts == null || hosts.isEmpty()) {
                 log.warn("Cannot determine host for DHCP client: {}. Aborting "
-                        + "relay for dhcp packet from server {}",
-                         dstMac, ethernetPacket);
+                                + "relay for dhcp packet from server {}",
+                        dstMac, ethernetPacket);
                 return null;
             } else if (hosts.size() > 1) {
                 // XXX  redo to send reply to all hosts found
@@ -460,8 +490,8 @@
             }
             Host host = hosts.iterator().next();
 
-            etherReply.setDestinationMACAddress(dstMac);
-            etherReply.setQinQVID(Ethernet.VLAN_UNTAGGED);
+            etherReply.setDestinationMacAddress(dstMac);
+            etherReply.setQinQVid(Ethernet.VLAN_UNTAGGED);
             etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
             etherReply.setVlanID((short) 0);
 
@@ -473,7 +503,7 @@
                             host.location().port()));
             if (relayAgentIP == null) {
                 log.warn("Cannot determine relay agent Ipv4 addr for host {}. "
-                        + "Aborting relay for dhcp packet from server {}",
+                                + "Aborting relay for dhcp packet from server {}",
                         host, ethernetPacket);
                 return null;
             }
@@ -482,7 +512,11 @@
             ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
 
             udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
-            udpPacket.setPayload(dhcpPayload);
+            if (option82) {
+                udpPacket.setPayload(removeOption82(dhcpPayload));
+            } else {
+                udpPacket.setPayload(dhcpPayload);
+            }
             ipv4Packet.setPayload(udpPacket);
             etherReply.setPayload(ipv4Packet);
 
@@ -491,7 +525,7 @@
         }
 
         //send the response to the requester host.
-        private void sendReply(Ethernet ethPacket, DHCP dhcpPayload) {
+        private void sendReply(DhcpEthernet ethPacket, DHCP dhcpPayload) {
             MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
             Host host = hostService.getHostsByMac(descMac).stream().findFirst().orElse(null);
 
@@ -506,13 +540,39 @@
                     log.trace("Relaying packet to dhcp client {}", ethPacket);
                 }
                 packetService.emit(o);
-                log.error("DHCP Packet sent to {}", host.location());
+                log.info("DHCP Packet sent to {}", host.location());
             } else {
-                log.info("Dropping DHCP packet because can't find host for {}", descMac);
+                log.error("Dropping DHCP packet because can't find host for {}", descMac);
             }
         }
     }
 
+    private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry) {
+        log.debug("option82data {} ", entry);
+
+        List<DHCPOption> options = Lists.newArrayList(dhcpPacket.getOptions());
+        DhcpOption82 option82 = new DhcpOption82();
+        option82.setAgentCircuitId(entry.circuitId());
+        option82.setAgentRemoteId(entry.remoteId());
+        DHCPOption option = new DHCPOption()
+                                .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
+                                .setData(option82.toByteArray())
+                                .setLength(option82.length());
+
+        options.add(options.size() - 1, option);
+        dhcpPacket.setOptions(options);
+        return dhcpPacket;
+
+    }
+
+    private DHCP removeOption82(DHCP dhcpPacket) {
+        List<DHCPOption> options = dhcpPacket.getOptions();
+        List<DHCPOption> newoptions = options.stream()
+                .filter(option -> option.getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
+                .collect(Collectors.toList());
+
+        return dhcpPacket.setOptions(newoptions);
+    }
     /**
      * Listener for network config events.
      */
@@ -529,4 +589,7 @@
             }
         }
     }
+
+
+
 }
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
index 4816285..c263149 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Laboratory
+ * Copyright 2017-present Open Networking Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/opencord/dhcpl2relay/package-info.java b/src/main/java/org/opencord/dhcpl2relay/package-info.java
index 9774697..5acda83 100755
--- a/src/main/java/org/opencord/dhcpl2relay/package-info.java
+++ b/src/main/java/org/opencord/dhcpl2relay/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Laboratory
+ * Copyright 2017-present Open Networking Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/src/main/java/org/opencord/dhcpl2relay/packet/DhcpEthernet.java b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpEthernet.java
new file mode 100644
index 0000000..45468e3
--- /dev/null
+++ b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpEthernet.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.dhcpl2relay.packet;
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.LLC;
+import org.onlab.packet.LLDP;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TCP;
+import org.onlab.packet.UDP;
+import org.onlab.packet.ndp.NeighborAdvertisement;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onlab.packet.ndp.Redirect;
+import org.onlab.packet.ndp.RouterAdvertisement;
+import org.onlab.packet.ndp.RouterSolicitation;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Ethernet Packet.
+ */
+public class DhcpEthernet extends BasePacket {
+    private static final String HEXES = "0123456789ABCDEF";
+    private static final String HEX_PROTO = "0x%s";
+
+    public static final short TYPE_ARP  = EthType.EtherType.ARP.ethType().toShort();
+    public static final short TYPE_RARP = EthType.EtherType.RARP.ethType().toShort();
+    public static final short TYPE_IPV4 = EthType.EtherType.IPV4.ethType().toShort();
+    public static final short TYPE_IPV6 = EthType.EtherType.IPV6.ethType().toShort();
+    public static final short TYPE_LLDP = EthType.EtherType.LLDP.ethType().toShort();
+    public static final short TYPE_VLAN = EthType.EtherType.VLAN.ethType().toShort();
+    public static final short TYPE_QINQ = EthType.EtherType.QINQ.ethType().toShort();
+    public static final short TYPE_BSN  = EthType.EtherType.BDDP.ethType().toShort();
+
+    public static final short MPLS_UNICAST = EthType.EtherType.MPLS_UNICAST.ethType().toShort();
+    public static final short MPLS_MULTICAST = EthType.EtherType.MPLS_MULTICAST.ethType().toShort();
+
+
+    public static final short VLAN_UNTAGGED = (short) 0xffff;
+
+    public static final short ETHERNET_HEADER_LENGTH = 14; // bytes
+    public static final short VLAN_HEADER_LENGTH = 4; // bytes
+
+    public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes
+
+    private static final Map<Short, Deserializer<? extends IPacket>> ETHERTYPE_DESERIALIZER_MAP =
+            new HashMap<>();
+
+    static {
+        for (EthType.EtherType ethType : EthType.EtherType.values()) {
+            if (ethType.deserializer() != null) {
+                ETHERTYPE_DESERIALIZER_MAP.put(ethType.ethType().toShort(), ethType.deserializer());
+            }
+        }
+    }
+
+    protected MacAddress destinationMacAddress;
+    protected MacAddress sourceMacAddress;
+    protected byte priorityCode;
+    protected byte qInQPriorityCode;
+    protected short vlanID;
+    protected short qinqVid;
+    protected short qinqTPid;
+    protected short etherType;
+    protected boolean pad = false;
+
+    /**
+     * By default, set Ethernet to untagged.
+     */
+    public DhcpEthernet() {
+        super();
+        this.vlanID = org.onlab.packet.Ethernet.VLAN_UNTAGGED;
+        this.qinqVid = org.onlab.packet.Ethernet.VLAN_UNTAGGED;
+        this.qinqTPid = TYPE_QINQ;
+    }
+
+    public DhcpEthernet(org.onlab.packet.Ethernet eth) {
+        super();
+        this.destinationMacAddress = eth.getDestinationMAC();
+        this.sourceMacAddress = eth.getSourceMAC();
+        this.priorityCode = eth.getPriorityCode();
+        this.vlanID = eth.getVlanID();
+        this.etherType = eth.getEtherType();
+        this.pad = eth.isPad();
+        this.payload = eth.getPayload();
+        this.payload.setParent(this);
+        this.qinqVid = Ethernet.VLAN_UNTAGGED;
+        this.qinqTPid = TYPE_QINQ;
+        //this.parent = eth.getParent();
+    }
+
+
+    /**
+     * Gets the destination MAC address.
+     *
+     * @return the destination MAC as a byte array
+     */
+    public byte[] getDestinationMacAddress() {
+        return this.destinationMacAddress.toBytes();
+    }
+
+    /**
+     * Gets the destination MAC address.
+     *
+     * @return the destination MAC
+     */
+    public MacAddress getDestinationMac() {
+        return this.destinationMacAddress;
+    }
+
+    /**
+     * Sets the destination MAC address.
+     *
+     * @param destMac the destination MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setDestinationMacAddress(final MacAddress destMac) {
+        this.destinationMacAddress = checkNotNull(destMac);
+        return this;
+    }
+
+    /**
+     * Sets the destination MAC address.
+     *
+     * @param destMac the destination MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setDestinationMacAddress(final byte[] destMac) {
+        this.destinationMacAddress = MacAddress.valueOf(destMac);
+        return this;
+    }
+
+    /**
+     * Sets the destination MAC address.
+     *
+     * @param destMac the destination MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setDestinationMacAddress(final String destMac) {
+        this.destinationMacAddress = MacAddress.valueOf(destMac);
+        return this;
+    }
+
+    /**
+     * Gets the source MAC address.
+     *
+     * @return the source MACAddress as a byte array
+     */
+    public byte[] getSourceMacAddress() {
+        return this.sourceMacAddress.toBytes();
+    }
+
+    /**
+     * Gets the source MAC address.
+     *
+     * @return the source MACAddress
+     */
+    public MacAddress getSourceMac() {
+        return this.sourceMacAddress;
+    }
+
+    /**
+     * Sets the source MAC address.
+     *
+     * @param sourceMac the source MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setSourceMacAddress(final MacAddress sourceMac) {
+        this.sourceMacAddress = checkNotNull(sourceMac);
+        return this;
+    }
+
+    /**
+     * Sets the source MAC address.
+     *
+     * @param sourceMac the source MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setSourceMacAddress(final byte[] sourceMac) {
+        this.sourceMacAddress = MacAddress.valueOf(sourceMac);
+        return this;
+    }
+
+    /**
+     * Sets the source MAC address.
+     *
+     * @param sourceMac the source MAC to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setSourceMacAddress(final String sourceMac) {
+        this.sourceMacAddress = MacAddress.valueOf(sourceMac);
+        return this;
+    }
+
+    /**
+     * Gets the priority code.
+     *
+     * @return the priorityCode
+     */
+    public byte getPriorityCode() {
+        return this.priorityCode;
+    }
+
+    /**
+     * Sets the priority code.
+     *
+     * @param priority the priorityCode to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setPriorityCode(final byte priority) {
+        this.priorityCode = priority;
+        return this;
+    }
+
+    /**
+     * Gets the QinQ priority code.
+     *
+     * @return the qInQPriorityCode
+     */
+    public byte getQinQPriorityCode() {
+        return this.qInQPriorityCode;
+    }
+
+    /**
+     * Sets the QinQ priority code.
+     *
+     * @param priority the priorityCode to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setQinQPriorityCode(final byte priority) {
+        this.qInQPriorityCode = priority;
+        return this;
+    }
+
+    /**
+     * Gets the VLAN ID.
+     *
+     * @return the vlanID
+     */
+    public short getVlanID() {
+        return this.vlanID;
+    }
+
+    /**
+     * Sets the VLAN ID.
+     *
+     * @param vlan the vlanID to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setVlanID(final short vlan) {
+        this.vlanID = vlan;
+        return this;
+    }
+
+    /**
+     * Gets the QinQ VLAN ID.
+     *
+     * @return the QinQ vlanID
+     */
+    public short getQinQVid() {
+        return this.qinqVid;
+    }
+
+    /**
+     * Sets the QinQ VLAN ID.
+     *
+     * @param vlan the vlanID to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setQinQVid(final short vlan) {
+        this.qinqVid = vlan;
+        return this;
+    }
+    /**
+     * Gets the QinQ TPID.
+     *
+     * @return the QinQ TPID
+     */
+    public short getQinQTPid() {
+        return this.qinqTPid;
+    }
+
+    /**
+     * Sets the QinQ TPID.
+     *
+     * @param tpId the TPID to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setQinQtpid(final short tpId) {
+        if (tpId != TYPE_VLAN && tpId != TYPE_QINQ) {
+            return null;
+        }
+        this.qinqTPid = tpId;
+        return this;
+    }
+    /**
+     * Gets the Ethernet type.
+     *
+     * @return the etherType
+     */
+    public short getEtherType() {
+        return this.etherType;
+    }
+
+    /**
+     * Sets the Ethernet type.
+     *
+     * @param ethType the etherType to set
+     * @return the Ethernet frame
+     */
+    public DhcpEthernet setEtherType(final short ethType) {
+        this.etherType = ethType;
+        return this;
+    }
+
+    /**
+     * @return True if the Ethernet frame is broadcast, false otherwise
+     */
+    public boolean isBroadcast() {
+        assert this.destinationMacAddress.length() == 6;
+        return this.destinationMacAddress.isBroadcast();
+    }
+
+    /**
+     * @return True is the Ethernet frame is multicast, False otherwise
+     */
+    public boolean isMulticast() {
+        return this.destinationMacAddress.isMulticast();
+    }
+
+    /**
+     * Pad this packet to 60 bytes minimum, filling with zeros?
+     *
+     * @return the pad
+     */
+    public boolean isPad() {
+        return this.pad;
+    }
+
+    /**
+     * Pad this packet to 60 bytes minimum, filling with zeros?
+     *
+     * @param pd
+     *            the pad to set
+     * @return this
+     */
+    public DhcpEthernet setPad(final boolean pd) {
+        this.pad = pd;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+        }
+        int length = 14 + (this.vlanID == org.onlab.packet.Ethernet.VLAN_UNTAGGED ? 0 : 4)
+                + (this.qinqVid == org.onlab.packet.Ethernet.VLAN_UNTAGGED ? 0 : 4)
+                + (payloadData == null ? 0 : payloadData.length);
+        if (this.pad && length < 60) {
+            length = 60;
+        }
+        final byte[] data = new byte[length];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(this.destinationMacAddress.toBytes());
+        bb.put(this.sourceMacAddress.toBytes());
+        if (this.qinqVid != org.onlab.packet.Ethernet.VLAN_UNTAGGED) {
+            bb.putShort(this.qinqTPid);
+            bb.putShort((short) (this.qInQPriorityCode << 13 | this.qinqVid & 0x0fff));
+        }
+        if (this.vlanID != org.onlab.packet.Ethernet.VLAN_UNTAGGED) {
+            bb.putShort(TYPE_VLAN);
+            bb.putShort((short) (this.priorityCode << 13 | this.vlanID & 0x0fff));
+        }
+        bb.putShort(this.etherType);
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+        if (this.pad) {
+            Arrays.fill(data, bb.position(), data.length, (byte) 0x0);
+        }
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(final byte[] data, final int offset,
+                               final int length) {
+        if (length <= 0) {
+            return null;
+        }
+        final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+        if (this.destinationMacAddress == null) {
+            this.destinationMacAddress = MacAddress.valueOf(new byte[6]);
+        }
+        final byte[] dstAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+        bb.get(dstAddr);
+        this.destinationMacAddress = MacAddress.valueOf(dstAddr);
+
+        if (this.sourceMacAddress == null) {
+            this.sourceMacAddress = MacAddress.valueOf(new byte[6]);
+        }
+        final byte[] srcAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+        bb.get(srcAddr);
+        this.sourceMacAddress = MacAddress.valueOf(srcAddr);
+
+        short ethType = bb.getShort();
+        if (ethType == TYPE_QINQ) {
+            final short tci = bb.getShort();
+            this.qInQPriorityCode = (byte) (tci >> 13 & 0x07);
+            this.qinqVid = (short) (tci & 0x0fff);
+            this.qinqTPid = TYPE_QINQ;
+            ethType = bb.getShort();
+        }
+
+        if (ethType == TYPE_VLAN) {
+            final short tci = bb.getShort();
+            this.priorityCode = (byte) (tci >> 13 & 0x07);
+            this.vlanID = (short) (tci & 0x0fff);
+            ethType = bb.getShort();
+
+            // there might be one more tag with 1q TPID
+            if (ethType == TYPE_VLAN) {
+                // packet is double tagged with 1q TPIDs
+                // We handle only double tagged packets here and assume that in this case
+                // TYPE_QINQ above was not hit
+                // We put the values retrieved above with TYPE_VLAN in
+                // qInQ fields
+                this.qInQPriorityCode = this.priorityCode;
+                this.qinqVid = this.vlanID;
+                this.qinqTPid = TYPE_VLAN;
+
+                final short innerTci = bb.getShort();
+                this.priorityCode = (byte) (innerTci >> 13 & 0x07);
+                this.vlanID = (short) (innerTci & 0x0fff);
+                ethType = bb.getShort();
+            }
+        } else {
+            this.vlanID = org.onlab.packet.Ethernet.VLAN_UNTAGGED;
+        }
+        this.etherType = ethType;
+
+        IPacket payload;
+        Deserializer<? extends IPacket> deserializer;
+        if (DhcpEthernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) {
+            deserializer = ETHERTYPE_DESERIALIZER_MAP.get(ethType);
+        } else {
+            deserializer = Data.deserializer();
+        }
+        try {
+            this.payload = deserializer.deserialize(data, bb.position(),
+                    bb.limit() - bb.position());
+            this.payload.setParent(this);
+        } catch (DeserializationException e) {
+            return this;
+        }
+        return this;
+    }
+
+    /**
+     * Checks to see if a string is a valid MAC address.
+     *
+     * @param macAddress string to test if it is a valid MAC
+     * @return True if macAddress is a valid MAC, False otherwise
+     */
+    public static boolean isMacAddress(final String macAddress) {
+        final String[] macBytes = macAddress.split(":");
+        if (macBytes.length != 6) {
+            return false;
+        }
+        for (int i = 0; i < 6; ++i) {
+            if (DhcpEthernet.HEXES.indexOf(macBytes[i].toUpperCase().charAt(0)) == -1
+                    || HEXES.indexOf(macBytes[i].toUpperCase().charAt(
+                    1)) == -1) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Accepts a MAC address of the form 00:aa:11:bb:22:cc, case does not
+     * matter, and returns a corresponding byte[].
+     *
+     * @param macAddress
+     *            The MAC address to convert into a byte array
+     * @return The macAddress as a byte array
+     */
+    public static byte[] toMacAddress(final String macAddress) {
+        return MacAddress.valueOf(macAddress).toBytes();
+    }
+
+    /**
+     * Accepts a MAC address and returns the corresponding long, where the MAC
+     * bytes are set on the lower order bytes of the long.
+     *
+     * @param macAddress MAC address as a byte array
+     * @return a long containing the mac address bytes
+     */
+    public static long toLong(final byte[] macAddress) {
+        return MacAddress.valueOf(macAddress).toLong();
+    }
+
+    /**
+     * Converts a long MAC address to a byte array.
+     *
+     * @param macAddress MAC address set on the lower order bytes of the long
+     * @return the bytes of the mac address
+     */
+    public static byte[] toByteArray(final long macAddress) {
+        return MacAddress.valueOf(macAddress).toBytes();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 7867;
+        int result = super.hashCode();
+        result = prime * result + this.destinationMacAddress.hashCode();
+        result = prime * result + this.etherType;
+        result = prime * result + this.qinqVid;
+        result = prime * result + this.qInQPriorityCode;
+        result = prime * result + this.vlanID;
+        result = prime * result + this.priorityCode;
+        result = prime * result + (this.pad ? 1231 : 1237);
+        result = prime * result + this.sourceMacAddress.hashCode();
+        return result;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        if (!(obj instanceof DhcpEthernet)) {
+            return false;
+        }
+        final DhcpEthernet other = (DhcpEthernet) obj;
+        if (!this.destinationMacAddress.equals(other.destinationMacAddress)) {
+            return false;
+        }
+        if (this.qInQPriorityCode != other.qInQPriorityCode) {
+            return false;
+        }
+        if (this.qinqVid != other.qinqVid) {
+            return false;
+        }
+        if (this.priorityCode != other.priorityCode) {
+            return false;
+        }
+        if (this.vlanID != other.vlanID) {
+            return false;
+        }
+        if (this.etherType != other.etherType) {
+            return false;
+        }
+        if (this.pad != other.pad) {
+            return false;
+        }
+        if (!this.sourceMacAddress.equals(other.sourceMacAddress)) {
+            return false;
+        }
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#toString(java.lang.Object)
+     */
+    @Override
+    public String toString() {
+
+        final StringBuffer sb = new StringBuffer("\n");
+
+        final IPacket pkt = this.getPayload();
+
+        if (pkt instanceof ARP) {
+            sb.append("arp");
+        } else if (pkt instanceof LLDP) {
+            sb.append("lldp");
+        } else if (pkt instanceof ICMP) {
+            sb.append("icmp");
+        } else if (pkt instanceof IPv4) {
+            sb.append("ip");
+        } else if (pkt instanceof DHCP) {
+            sb.append("dhcp");
+        } else {
+            /*
+             * When we don't know the protocol, we print using
+             * the well known hex format instead of a decimal
+             * value.
+             */
+            sb.append(String.format(HEX_PROTO,
+                    Integer.toHexString(this.getEtherType() & 0xffff)));
+        }
+
+        if (this.getQinQVid() != org.onlab.packet.Ethernet.VLAN_UNTAGGED) {
+            sb.append("\ndl_qinqVlan: ");
+            sb.append(this.getQinQVid());
+            sb.append("\ndl_qinqVlan_pcp: ");
+            sb.append(this.getQinQPriorityCode());
+        }
+
+        sb.append("\ndl_vlan: ");
+        if (this.getVlanID() == org.onlab.packet.Ethernet.VLAN_UNTAGGED) {
+            sb.append("untagged");
+        } else {
+            sb.append(this.getVlanID());
+        }
+        sb.append("\ndl_vlan_pcp: ");
+        sb.append(this.getPriorityCode());
+        sb.append("\ndl_src: ");
+        sb.append(bytesToHex(this.getSourceMacAddress()));
+        sb.append("\ndl_dst: ");
+        sb.append(bytesToHex(this.getDestinationMacAddress()));
+
+        if (pkt instanceof ARP) {
+            final ARP p = (ARP) pkt;
+            sb.append("\nnw_src: ");
+            sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p
+                    .getSenderProtocolAddress())));
+            sb.append("\nnw_dst: ");
+            sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p
+                    .getTargetProtocolAddress())));
+        } else if (pkt instanceof LLDP) {
+            sb.append("lldp packet");
+        } else if (pkt instanceof ICMP) {
+            final ICMP icmp = (ICMP) pkt;
+            sb.append("\nicmp_type: ");
+            sb.append(icmp.getIcmpType());
+            sb.append("\nicmp_code: ");
+            sb.append(icmp.getIcmpCode());
+        } else if (pkt instanceof IPv4) {
+            final IPv4 p = (IPv4) pkt;
+            sb.append("\nnw_src: ");
+            sb.append(IPv4.fromIPv4Address(p.getSourceAddress()));
+            sb.append("\nnw_dst: ");
+            sb.append(IPv4.fromIPv4Address(p.getDestinationAddress()));
+            sb.append("\nnw_tos: ");
+            sb.append(p.getDiffServ());
+            sb.append("\nnw_proto: ");
+            sb.append(p.getProtocol());
+
+            IPacket payload = pkt.getPayload();
+            if (payload != null) {
+                if (payload instanceof TCP) {
+                    sb.append("\ntp_src: ");
+                    sb.append(((TCP) payload).getSourcePort());
+                    sb.append("\ntp_dst: ");
+                    sb.append(((TCP) payload).getDestinationPort());
+
+                } else if (payload instanceof UDP) {
+                    sb.append("\ntp_src: ");
+                    sb.append(((UDP) payload).getSourcePort());
+                    sb.append("\ntp_dst: ");
+                    sb.append(((UDP) payload).getDestinationPort());
+                } else if (payload instanceof ICMP) {
+                    final ICMP icmp = (ICMP) payload;
+                    sb.append("\nicmp_type: ");
+                    sb.append(icmp.getIcmpType());
+                    sb.append("\nicmp_code: ");
+                    sb.append(icmp.getIcmpCode());
+                }
+            }
+        } else if (pkt instanceof IPv6) {
+            final IPv6 ipv6 = (IPv6) pkt;
+            sb.append("\nipv6_src: ");
+            sb.append(Ip6Address.valueOf(ipv6.getSourceAddress()).toString());
+            sb.append("\nipv6_dst: ");
+            sb.append(Ip6Address.valueOf(ipv6.getDestinationAddress()).toString());
+            sb.append("\nipv6_proto: ");
+            sb.append(ipv6.getNextHeader());
+
+            IPacket payload = pkt.getPayload();
+            if (payload != null && payload instanceof ICMP6) {
+                final ICMP6 icmp6 = (ICMP6) payload;
+                sb.append("\nicmp6_type: ");
+                sb.append(icmp6.getIcmpType());
+                sb.append("\nicmp6_code: ");
+                sb.append(icmp6.getIcmpCode());
+
+                payload = payload.getPayload();
+                if (payload != null) {
+                    if (payload instanceof NeighborSolicitation) {
+                        final NeighborSolicitation ns = (NeighborSolicitation) payload;
+                        sb.append("\nns_target_addr: ");
+                        sb.append(Ip6Address.valueOf(ns.getTargetAddress()).toString());
+                        ns.getOptions().forEach(option -> {
+                            sb.append("\noption_type: ");
+                            sb.append(option.type());
+                            sb.append("\noption_data: ");
+                            sb.append(bytesToHex(option.data()));
+                        });
+                    } else if (payload instanceof NeighborAdvertisement) {
+                        final NeighborAdvertisement na = (NeighborAdvertisement) payload;
+                        sb.append("\nna_target_addr: ");
+                        sb.append(Ip6Address.valueOf(na.getTargetAddress()).toString());
+                        sb.append("\nna_solicited_flag: ");
+                        sb.append(na.getSolicitedFlag());
+                        sb.append("\nna_router_flag: ");
+                        sb.append(na.getRouterFlag());
+                        sb.append("\nna_override_flag: ");
+                        sb.append(na.getOverrideFlag());
+                        na.getOptions().forEach(option -> {
+                            sb.append("\noption_type: ");
+                            sb.append(option.type());
+                            sb.append("\noption_data: ");
+                            sb.append(bytesToHex(option.data()));
+                        });
+                    } else if (payload instanceof RouterSolicitation) {
+                        final RouterSolicitation rs = (RouterSolicitation) payload;
+                        sb.append("\nrs");
+                        rs.getOptions().forEach(option -> {
+                            sb.append("\noption_type: ");
+                            sb.append(option.type());
+                            sb.append("\noption_data: ");
+                            sb.append(bytesToHex(option.data()));
+                        });
+                    } else if (payload instanceof RouterAdvertisement) {
+                        final RouterAdvertisement ra = (RouterAdvertisement) payload;
+                        sb.append("\nra_hop_limit: ");
+                        sb.append(ra.getCurrentHopLimit());
+                        sb.append("\nra_mflag: ");
+                        sb.append(ra.getMFlag());
+                        sb.append("\nra_oflag: ");
+                        sb.append(ra.getOFlag());
+                        sb.append("\nra_reachable_time: ");
+                        sb.append(ra.getReachableTime());
+                        sb.append("\nra_retransmit_time: ");
+                        sb.append(ra.getRetransmitTimer());
+                        sb.append("\nra_router_lifetime: ");
+                        sb.append(ra.getRouterLifetime());
+                        ra.getOptions().forEach(option -> {
+                            sb.append("\noption_type: ");
+                            sb.append(option.type());
+                            sb.append("\noption_data: ");
+                            sb.append(bytesToHex(option.data()));
+                        });
+                    } else if (payload instanceof Redirect) {
+                        final Redirect rd = (Redirect) payload;
+                        sb.append("\nrd_target_addr: ");
+                        sb.append(Ip6Address.valueOf(rd.getTargetAddress()).toString());
+                        rd.getOptions().forEach(option -> {
+                            sb.append("\noption_type: ");
+                            sb.append(option.type());
+                            sb.append("\noption_data: ");
+                            sb.append(bytesToHex(option.data()));
+                        });
+                    }
+                }
+            }
+        } else if (pkt instanceof DHCP) {
+            sb.append("\ndhcp packet");
+        } else if (pkt instanceof Data) {
+            sb.append("\ndata packet");
+        } else if (pkt instanceof LLC) {
+            sb.append("\nllc packet");
+        } else {
+            sb.append("\nunknown packet");
+        }
+
+        return sb.toString();
+    }
+
+    public static String bytesToHex(byte[] in) {
+        final StringBuilder builder = new StringBuilder();
+        for (byte b : in) {
+            builder.append(String.format("%02x", b));
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Deserializer function for Ethernet packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<DhcpEthernet> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, ETHERNET_HEADER_LENGTH);
+
+            byte[] addressBuffer = new byte[DATALAYER_ADDRESS_LENGTH];
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            DhcpEthernet eth = new DhcpEthernet();
+            // Read destination MAC address into buffer
+            bb.get(addressBuffer);
+            eth.setDestinationMacAddress(addressBuffer);
+
+            // Read source MAC address into buffer
+            bb.get(addressBuffer);
+            eth.setSourceMacAddress(addressBuffer);
+
+            short ethType = bb.getShort();
+            if (ethType == TYPE_QINQ) {
+                // in this case we excpect 2 VLAN headers
+                checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                final short tci = bb.getShort();
+                eth.setQinQPriorityCode((byte) (tci >> 13 & 0x07));
+                eth.setQinQVid((short) (tci & 0x0fff));
+                eth.setQinQtpid(TYPE_QINQ);
+                ethType = bb.getShort();
+            }
+            if (ethType == TYPE_VLAN) {
+                checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                final short tci = bb.getShort();
+                eth.setPriorityCode((byte) (tci >> 13 & 0x07));
+                eth.setVlanID((short) (tci & 0x0fff));
+                ethType = bb.getShort();
+
+                if (ethType == TYPE_VLAN) {
+                    // We handle only double tagged packets here and assume that in this case
+                    // TYPE_QINQ above was not hit
+                    // We put the values retrieved above with TYPE_VLAN in
+                    // qInQ fields
+                    checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                    eth.setQinQPriorityCode(eth.getPriorityCode());
+                    eth.setQinQVid(eth.getVlanID());
+                    eth.setQinQtpid(TYPE_VLAN);
+
+                    final short innerTci = bb.getShort();
+                    eth.setPriorityCode((byte) (innerTci >> 13 & 0x07));
+                    eth.setVlanID((short) (innerTci & 0x0fff));
+                    ethType = bb.getShort();
+                }
+            } else {
+                eth.setVlanID(org.onlab.packet.Ethernet.VLAN_UNTAGGED);
+            }
+            eth.setEtherType(ethType);
+
+            IPacket payload;
+            Deserializer<? extends IPacket> deserializer;
+            if (DhcpEthernet.ETHERTYPE_DESERIALIZER_MAP.containsKey(ethType)) {
+                deserializer = DhcpEthernet.ETHERTYPE_DESERIALIZER_MAP.get(ethType);
+            } else {
+                deserializer = Data.deserializer();
+            }
+            payload = deserializer.deserialize(data, bb.position(),
+                    bb.limit() - bb.position());
+            payload.setParent(eth);
+            eth.setPayload(payload);
+
+            return eth;
+        };
+    }
+}
diff --git a/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java
new file mode 100644
index 0000000..b673f94
--- /dev/null
+++ b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.dhcpl2relay.packet;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Represents the DHCP Option 82 information. Currently only supports
+ * sub option 1 (agent-circuit-id) and 2 (agent=-relay-id).
+ */
+public class DhcpOption82 {
+
+    private String agentCircuitId = null;
+    private String agentRemoteId = null;
+
+    public DhcpOption82() {
+
+    }
+
+    public void setAgentCircuitId(String value) {
+        this.agentCircuitId = value;
+    }
+
+    /**
+     *
+     * @return agentCircuitId
+     */
+    public String getAgentCircuitId() {
+        return this.agentCircuitId;
+    }
+
+    /**
+     * sets AgentRemoteId.
+     * @param value
+     */
+    public void setAgentRemoteId(String value) {
+        this.agentRemoteId = value;
+    }
+
+    /**
+     *
+     * @return agentRemoteId
+     */
+    public String getAgentRemoteId() {
+        return this.agentRemoteId;
+    }
+
+    /**
+     *
+     * @return length of option 82.
+     */
+    public byte length() {
+        int length = 0;
+
+        // +2 below for sub option ID and length of sub option
+        if (agentCircuitId != null) {
+            length += agentCircuitId.length() + 2;
+        }
+        if (agentRemoteId != null) {
+            length += agentRemoteId.length() + 2;
+        }
+        return (byte) length;
+    }
+
+    /**
+     * Returns the representation of the option 82 specification as a byte
+     * array.
+     * @return returns byte array
+     */
+    public byte[] toByteArray() {
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        // Add sub option if set
+        if (agentCircuitId != null) {
+            buf.write((byte) 1);
+            buf.write((byte) agentCircuitId.length());
+            byte[] bytes = agentCircuitId.getBytes(StandardCharsets.UTF_8);
+            buf.write(bytes, 0, bytes.length);
+        }
+
+        // Add sub option if set
+        if (agentRemoteId != null) {
+            buf.write((byte) 2);
+            buf.write((byte) agentRemoteId.length());
+            byte[] bytes = agentRemoteId.getBytes(StandardCharsets.UTF_8);
+            buf.write(bytes, 0, bytes.length);
+        }
+
+        return buf.toByteArray();
+    }
+
+}
diff --git a/src/main/java/org/opencord/dhcpl2relay/packet/package-info.java b/src/main/java/org/opencord/dhcpl2relay/packet/package-info.java
new file mode 100644
index 0000000..40880f8
--- /dev/null
+++ b/src/main/java/org/opencord/dhcpl2relay/packet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable lacd ..
+ * w 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.
+ */
+/**
+ * Utilities for decoding and encoding DHCP options.
+ */
+package org.opencord.dhcpl2relay.packet;