VOL-144: Relay DHCP from subscriber on a configurable VLAN
Patchset contains:
1. Bug fix
2. Documentation - README.md
3. Unit tests
4. Changed - DHCP Relay app sends double tagged packets to voltha instead of only priority tagged packets

Change-Id: I2b046068c42421bffd3cb84b68b1edc26e8d9148
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5a1676c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# ONOS DHCP L2 RELAY Application
+
+The ONOS dhcpl2relay application is a DHCP Relay Agent which does Layer 2 relay.
+
+The DHCP packets sent towards the DHCP Server (DHCP DISCOVER and DHCP REQUEST) are double tagged by this app. It retrieves the tag values to be used from the `Sadis` Service. Similarly it replaces the tags on the packets received from the server (DHCP OFFER and DHCP ACK) with priority tags.
+
+DHCP Option 82 with CircuitId and RemoteId are added to packets sent to the DHCP server and  Option 82 received from the server are removed before relaying back to the client. The CircuitId and Remote Id are retrieved from `Sadis` Service.
+
+# Configuration
+```sh
+"org.opencord.dhcpl2relay" : {
+      "dhcpl2relay" : {
+        "dhcpserverConnectPoints" : [ "of:00000000000000b2/2" ]
+      }
+    }
+ ```
+ ### Configuration Parameters
+##### dhcpServerConnectPoints
+Port on the switch through which the DHCP Server is reachable
+
+# Example configuration of Sadis
+```sh
+   "org.opencord.sadis" : {
+      "sadis" : {
+        "integration" : {
+          "cache" : {
+            "enabled" : true,
+            "maxsize" : 50,
+            "ttl" : "PT1m"
+          }
+        },
+        "entries" : [ {
+          "id" : "uni-128", # (This is an entry for a subscriber) Same as the portName of the Port as seen in onos ports command
+          "cTag" : 2, # C-tag of the subscriber
+          "sTag" : 2, # S-tag of the subscriber
+          "nasPortId" : "uni-128"  # NAS Port Id of the subscriber, could be different from the id above
+        }, {
+          "id" : "1d3eafb52e4e44b08818ab9ebaf7c0d4", # (This is an entry for an OLT device) Same as the serial of the OLT logical device as seen in the onos devices command
+          "hardwareIdentifier" : "00:1b:22:00:b1:78", # MAC address to be used for this OLT
+          "ipAddress" : "192.168.1.252", # IP address to be used for this OLT
+          "nasId" : "B100-NASID" # NAS ID to be used for this OLT
+        } ]
+      }
+    }
+ ```
+
diff --git a/pom.xml b/pom.xml
index b3bb997..9a2fa76 100755
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,18 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${onos.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
index cdffe4e..a3c8e4b 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
@@ -17,6 +17,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -31,11 +32,15 @@
 import org.onlab.packet.DHCPPacketType;
 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.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
 import org.opencord.dhcpl2relay.packet.DhcpEthernet;
 import org.opencord.dhcpl2relay.packet.DhcpOption82;
 import org.onlab.util.Tools;
@@ -72,8 +77,8 @@
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
 import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
@@ -123,6 +128,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
     @Property(name = "option82", boolValue = true,
             label = "Add option 82 to relayed packets")
     protected boolean option82 = true;
@@ -130,8 +138,12 @@
     private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
             new DhcpRelayPacketProcessor();
 
+    private InnerMastershipListener changeListener = new InnerMastershipListener();
+    private InnerDeviceListener deviceListener = new InnerDeviceListener();
 
-    private ConnectPoint dhcpServerConnectPoint = null;
+    // connect points to the DHCP server
+    Set<ConnectPoint> dhcpConnectPoints;
+    private AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
     private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
     private ApplicationId appId;
 
@@ -142,6 +154,9 @@
         componentConfigService.registerProperties(getClass());
 
         cfgService.addListener(cfgListener);
+        mastershipService.addListener(changeListener);
+        deviceService.addListener(deviceListener);
+
         factories.forEach(cfgService::registerConfigFactory);
         //update the dhcp server configuration.
         updateConfig();
@@ -185,23 +200,49 @@
      * @return true if all information we need have been initialized
      */
     private boolean configured() {
-        return dhcpServerConnectPoint != null;
+        return dhcpServerConnectPoint.get() != null;
     }
 
+    /**
+     * Selects a connect point through an available device for which it is the master.
+     */
+    private void selectServerConnectPoint() {
+        synchronized (this) {
+            dhcpServerConnectPoint.set(null);
+            if (dhcpConnectPoints != null) {
+                // find a connect point through a device for which we are master
+                for (ConnectPoint cp: dhcpConnectPoints) {
+                    if (mastershipService.isLocalMaster(cp.deviceId())) {
+                        if (deviceService.isAvailable(cp.deviceId())) {
+                            dhcpServerConnectPoint.set(cp);
+                        }
+                        log.info("DHCP connectPoint selected is {}", cp);
+                        break;
+                    }
+                }
+            }
+
+            log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
+
+            if (dhcpServerConnectPoint.get() == null) {
+                log.error("Master of none, can't relay DHCP Message to server");
+            }
+        }
+    }
+
+    /**
+     * Updates the network configuration.
+     */
     private void updateConfig() {
         DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
         if (cfg == null) {
             log.warn("Dhcp Server info not available");
             return;
         }
-        if (dhcpServerConnectPoint == null) {
-            dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
-            requestDhcpPackets();
-        } else {
-            cancelDhcpPackets();
-            dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
-            requestDhcpPackets();
-        }
+
+        dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
+
+        selectServerConnectPoint();
 
         log.info("dhcp server connect point: " + dhcpServerConnectPoint);
     }
@@ -210,44 +251,39 @@
      * Request DHCP packet in via PacketService.
      */
     private void requestDhcpPackets() {
-        if (dhcpServerConnectPoint != null) {
-            TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-            packetService.requestPackets(selectorServer.build(),
-                    PacketPriority.CONTROL, appId,
-                    Optional.of(dhcpServerConnectPoint.deviceId()));
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.requestPackets(selectorServer.build(),
+                PacketPriority.CONTROL, appId);
 
-            TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-            packetService.requestPackets(selectorClient.build(),
-                    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.requestPackets(selectorClient.build(),
+                PacketPriority.CONTROL, appId);
+
     }
 
     /**
      * Cancel requested DHCP packets in via packet service.
      */
     private void cancelDhcpPackets() {
-        if (dhcpServerConnectPoint != null) {
-            TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-            packetService.cancelPackets(selectorServer.build(),
-                    PacketPriority.CONTROL, appId, Optional.of(dhcpServerConnectPoint.deviceId()));
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.cancelPackets(selectorServer.build(),
+                PacketPriority.CONTROL, appId);
 
-            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()));
-        }
+        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);
     }
 
     private SubscriberAndDeviceInformation getDevice(PacketContext context) {
@@ -263,16 +299,6 @@
 
         return subsService.get(serialNo);
     }
-    private Ip4Address relayAgentIPv4Address(ConnectPoint cp) {
-
-        SubscriberAndDeviceInformation device = getDevice(cp);
-        if (device == null) {
-            log.warn("Device not found for {}", cp);
-            return null;
-        }
-
-        return device.ipAddress();
-    }
 
     private MacAddress relayAgentMacAddress(PacketContext context) {
 
@@ -287,7 +313,11 @@
     }
 
     private String nasPortId(PacketContext context) {
-        Port p = deviceService.getPort(context.inPacket().receivedFrom());
+        return nasPortId(context.inPacket().receivedFrom());
+    }
+
+    private String nasPortId(ConnectPoint cp) {
+        Port p = deviceService.getPort(cp);
         return p.annotations().value(AnnotationKeys.PORT_NAME);
     }
 
@@ -305,6 +335,26 @@
         return sub.cTag();
     }
 
+    private VlanId cTag(ConnectPoint cp) {
+        String portId = nasPortId(cp);
+        SubscriberAndDeviceInformation sub = subsService.get(portId);
+        if (sub == null) {
+            log.warn("Subscriber info not found for {} looking for C-TAG", cp);
+            return VlanId.NONE;
+        }
+        return sub.cTag();
+    }
+
+    private VlanId sTag(ConnectPoint cp) {
+        String portId = nasPortId(cp);
+        SubscriberAndDeviceInformation sub = subsService.get(portId);
+        if (sub == null) {
+            log.warn("Subscriber info not found for {} looking for S-TAG", cp);
+            return VlanId.NONE;
+        }
+        return sub.sTag();
+    }
+
     private VlanId sTag(PacketContext context) {
         SubscriberAndDeviceInformation sub = getSubscriber(context);
         if (sub == null) {
@@ -358,15 +408,15 @@
         //forward the packet to ConnectPoint where the DHCP server is attached.
         private void forwardPacket(DhcpEthernet packet) {
 
-            if (dhcpServerConnectPoint != null) {
+            if (dhcpServerConnectPoint.get() != null) {
                 TrafficTreatment t = DefaultTrafficTreatment.builder()
-                        .setOutput(dhcpServerConnectPoint.port()).build();
+                        .setOutput(dhcpServerConnectPoint.get().port()).build();
                 OutboundPacket o = new DefaultOutboundPacket(
-                        dhcpServerConnectPoint.deviceId(), t,
+                        dhcpServerConnectPoint.get().deviceId(), t,
                         ByteBuffer.wrap(packet.serialize()));
                 if (log.isTraceEnabled()) {
                 log.trace("Relaying packet to dhcp server {} at {}",
-                        packet, dhcpServerConnectPoint);
+                        packet, dhcpServerConnectPoint.get());
                 }
                 packetService.emit(o);
             } else {
@@ -458,11 +508,12 @@
             etherReply.setSourceMacAddress(relayAgentMac);
             etherReply.setDestinationMacAddress(dhcpConnectMac);
 
+            etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
             etherReply.setVlanID(cTag(context).toShort());
             etherReply.setQinQtpid(DhcpEthernet.TYPE_QINQ);
             etherReply.setQinQVid(sTag(context).toShort());
 
-            log.info("Finished processing packet");
+            log.info("Finished processing packet -- sending packet {}", etherReply);
             return etherReply;
         }
 
@@ -475,7 +526,6 @@
             UDP udpPacket = (UDP) ipv4Packet.getPayload();
             DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
 
-
             MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
             Set<Host> hosts = hostService.getHostsByMac(dstMac);
             if (hosts == null || hosts.isEmpty()) {
@@ -490,28 +540,15 @@
             }
             Host host = hosts.iterator().next();
 
-            etherReply.setDestinationMacAddress(dstMac);
-            etherReply.setQinQVid(Ethernet.VLAN_UNTAGGED);
-            etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
-            etherReply.setVlanID((short) 0);
+            ConnectPoint subsCp = new ConnectPoint(host.location().deviceId(),
+                    host.location().port());
 
             // we leave the srcMac from the original packet
+            etherReply.setDestinationMacAddress(dstMac);
+            etherReply.setQinQVid(sTag(subsCp).toShort());
+            etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
+            etherReply.setVlanID((cTag(subsCp).toShort()));
 
-            // figure out the relay agent IP corresponding to the original request
-            Ip4Address relayAgentIP = relayAgentIPv4Address(
-                    new ConnectPoint(host.location().deviceId(),
-                            host.location().port()));
-            if (relayAgentIP == null) {
-                log.warn("Cannot determine relay agent Ipv4 addr for host {}. "
-                                + "Aborting relay for dhcp packet from server {}",
-                        host, ethernetPacket);
-                return null;
-            }
-
-            ipv4Packet.setSourceAddress(relayAgentIP.toInt());
-            ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
-
-            udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
             if (option82) {
                 udpPacket.setPayload(removeOption82(dhcpPayload));
             } else {
@@ -561,6 +598,7 @@
 
         options.add(options.size() - 1, option);
         dhcpPacket.setOptions(options);
+
         return dhcpPacket;
 
     }
@@ -590,6 +628,58 @@
         }
     }
 
+    /**
+     * Handles Mastership changes for the devices which connect
+     * to the DHCP server.
+     */
+    private class InnerMastershipListener implements MastershipListener {
+        @Override
+        public void event(MastershipEvent event) {
+            if (dhcpServerConnectPoint.get() != null &&
+                    dhcpServerConnectPoint.get().deviceId().
+                            equals(event.subject())) {
+                log.trace("Mastership Event recevived for {}", event.subject());
+                // mastership of the device for our connect point has changed
+                // reselect
+                selectServerConnectPoint();
+            }
+        }
+    }
 
-
+    /**
+     * Handles Device status change for the devices which connect
+     * to the DHCP server.
+     */
+    private class InnerDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
+            if (dhcpServerConnectPoint.get() == null) {
+                switch (event.type()) {
+                    case DEVICE_ADDED:
+                    case DEVICE_AVAILABILITY_CHANGED:
+                        // some device is available check if we can get one
+                        selectServerConnectPoint();
+                        break;
+                    default:
+                        break;
+                }
+                return;
+            }
+            if (dhcpServerConnectPoint.get().deviceId().
+                    equals(event.subject().id())) {
+                switch (event.type()) {
+                    case DEVICE_AVAILABILITY_CHANGED:
+                    case DEVICE_REMOVED:
+                    case DEVICE_SUSPENDED:
+                        // state of our device has changed, check if we need
+                        // to re-select
+                        selectServerConnectPoint();
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
index c263149..eb60c7f 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
@@ -15,32 +15,59 @@
  */
 package org.opencord.dhcpl2relay;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import com.google.common.collect.ImmutableSet;
+
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.config.Config;
 
-import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
+import java.util.HashSet;
+import java.util.Set;
+
 
 /**
  * DHCP Relay Config class.
  */
 public class DhcpL2RelayConfig extends Config<ApplicationId> {
 
-    private static final String DHCP_CONNECT_POINT = "dhcpserverConnectPoint";
+    private static final String DHCP_CONNECT_POINTS = "dhcpServerConnectPoints";
 
     @Override
     public boolean isValid() {
 
-        return hasOnlyFields(DHCP_CONNECT_POINT) &&
-                isConnectPoint(DHCP_CONNECT_POINT, MANDATORY);
+        return hasOnlyFields(DHCP_CONNECT_POINTS);
     }
 
     /**
-     * Returns the dhcp server connect point.
+     * Returns the dhcp server connect points.
      *
-     * @return dhcp server connect point
+     * @return dhcp server connect points
      */
-    public ConnectPoint getDhcpServerConnectPoint() {
-        return ConnectPoint.deviceConnectPoint(object.path(DHCP_CONNECT_POINT).asText());
+    public Set<ConnectPoint> getDhcpServerConnectPoint() {
+        if (object == null) {
+            return new HashSet<ConnectPoint>();
+        }
+
+        if (!object.has(DHCP_CONNECT_POINTS)) {
+            return ImmutableSet.of();
+        }
+
+        ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(DHCP_CONNECT_POINTS);
+        for (JsonNode jsonNode : arrayNode) {
+            String portName = jsonNode.asText(null);
+            if (portName == null) {
+                return null;
+            }
+            try {
+                builder.add(ConnectPoint.deviceConnectPoint(portName));
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+        return builder.build();
     }
 }
diff --git a/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java
index b673f94..7f39258 100644
--- a/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java
+++ b/src/main/java/org/opencord/dhcpl2relay/packet/DhcpOption82.java
@@ -45,7 +45,7 @@
 
     /**
      * sets AgentRemoteId.
-     * @param value
+     * @param value   Value to be set
      */
     public void setAgentRemoteId(String value) {
         this.agentRemoteId = value;
diff --git a/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java
new file mode 100755
index 0000000..e91fddd
--- /dev/null
+++ b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java
@@ -0,0 +1,417 @@
+/*
+ * 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;
+
+//import com.fasterxml.jackson.databind.util.Annotations;
+
+import org.easymock.EasyMock;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.onlab.osgi.ComponentContextAdapter;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCPOption;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.PortNumber;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Device;
+import org.onosproject.net.Element;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.Port;
+
+import org.opencord.dhcpl2relay.packet.DhcpEthernet;
+import org.opencord.dhcpl2relay.packet.DhcpOption82;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.opencord.sadis.SubscriberAndDeviceInformationService;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertEquals;
+
+public class DhcpL2RelayTest extends DhcpL2RelayTestBase {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private DhcpL2Relay dhcpL2Relay;
+
+    private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final VlanId CLIENT_C_TAG = VlanId.vlanId((short) 999);
+    private static final VlanId CLIENT_S_TAG = VlanId.vlanId((short) 111);
+    private static final String CLIENT_NAS_PORT_ID = "PON 1/1";
+    private static final String CLIENT_CIRCUIT_ID = "CIR-PON 1/1";
+
+    private static final String OLT_DEV_ID = "of:00000000000000aa";
+    private static final MacAddress OLT_MAC_ADDRESS = MacAddress.valueOf("01:02:03:04:05:06");
+    private static final DeviceId DEVICE_ID_1 = DeviceId.deviceId(OLT_DEV_ID);
+
+    private static final ConnectPoint SERVER_CONNECT_POINT =
+            ConnectPoint.deviceConnectPoint("of:0000000000000001/5");
+
+    private static final String SCHEME_NAME = "dhcpl2relay";
+    private static final DefaultAnnotations DEVICE_ANNOTATIONS = DefaultAnnotations.builder()
+            .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase()).build();
+
+    ComponentConfigService mockConfigService =
+            EasyMock.createMock(ComponentConfigService.class);
+
+    /**
+     * Sets up the services required by the dhcpl2relay app.
+     */
+    @Before
+    public void setUp() {
+        dhcpL2Relay = new DhcpL2Relay();
+        dhcpL2Relay.cfgService = new TestNetworkConfigRegistry();
+        dhcpL2Relay.coreService = new CoreServiceAdapter();
+        dhcpL2Relay.packetService = new MockPacketService();
+        dhcpL2Relay.componentConfigService = mockConfigService;
+        dhcpL2Relay.deviceService = new MockDeviceService();
+        dhcpL2Relay.subsService = new MockSubService();
+        dhcpL2Relay.hostService = new MockHostService();
+        dhcpL2Relay.mastershipService = new MockMastershipService();
+        dhcpL2Relay.activate(new ComponentContextAdapter());
+    }
+
+    /**
+     * Tears down the dhcpL2Relay application.
+     */
+    @After
+    public void tearDown() {
+        dhcpL2Relay.deactivate();
+    }
+
+    /**
+     * Tests the DHCP relay app by sending DHCP discovery Packet.
+     *
+     * @throws Exception when an unhandled error occurs
+     */
+    @Test
+    public void testDhcpDiscover()  throws Exception {
+        //  (1) Sending DHCP discover packet
+        System.out.println("Sending pakcet");
+        DhcpEthernet discoverPacket = constructDhcpDiscoverPacket(CLIENT_MAC);
+
+        sendPacket(discoverPacket, ConnectPoint.deviceConnectPoint(OLT_DEV_ID + "/" + 1));
+
+        DhcpEthernet discoverRelayed = (DhcpEthernet) getPacket();
+        compareClientPackets(discoverPacket, discoverRelayed);
+    }
+
+    /**
+     * Tests the DHCP relay app by sending DHCP Request Packet.
+     *
+     * @throws Exception when an unhandled error occurs
+     */
+    @Test
+    public void testDhcpRequest()  throws Exception {
+        //  (1) Sending DHCP discover packet
+        System.out.println("Sending pakcet");
+        DhcpEthernet requestPacket = constructDhcpRequestPacket(CLIENT_MAC);
+
+        sendPacket(requestPacket, ConnectPoint.deviceConnectPoint(OLT_DEV_ID + "/" + 1));
+
+        DhcpEthernet requestRelayed = (DhcpEthernet) getPacket();
+        compareClientPackets(requestPacket, requestRelayed);
+    }
+
+    /**
+     * Tests the DHCP relay app by sending DHCP Offer Packet.
+     *
+     * @throws Exception when an unhandled error occurs
+     */
+    @Test
+    public void testDhcpOffer() {
+        //  (1) Sending DHCP discover packet
+        System.out.println("Sending pakcet");
+        DhcpEthernet offerPacket = constructDhcpOfferPacket(MacAddress.valueOf("bb:bb:bb:bb:bb:bb"),
+                CLIENT_MAC, "1.1.1.1", "2.2.2.2");
+
+        sendPacket(offerPacket, ConnectPoint.deviceConnectPoint(OLT_DEV_ID + "/" + 1));
+
+        DhcpEthernet offerRelayed = (DhcpEthernet) getPacket();
+        compareServerPackets(offerPacket, offerRelayed);
+    }
+
+    /**
+     * Tests the DHCP relay app by sending DHCP Ack Packet.
+     *
+     * @throws Exception when an unhandled error occurs
+     */
+    @Test
+    public void testDhcpAck() {
+
+        DhcpEthernet ackPacket = constructDhcpAckPacket(MacAddress.valueOf("bb:bb:bb:bb:bb:bb"),
+                CLIENT_MAC, "1.1.1.1", "2.2.2.2");
+
+        sendPacket(ackPacket, ConnectPoint.deviceConnectPoint(OLT_DEV_ID + "/" + 1));
+
+        DhcpEthernet ackRelayed = (DhcpEthernet) getPacket();
+        compareServerPackets(ackPacket, ackRelayed);
+    }
+
+    public void compareClientPackets(DhcpEthernet sent, DhcpEthernet relayed) {
+        sent.setSourceMacAddress(OLT_MAC_ADDRESS);
+        sent.setQinQVid(CLIENT_S_TAG.toShort());
+        sent.setVlanID(CLIENT_C_TAG.toShort());
+
+        IPv4 ipv4Packet = (IPv4) sent.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        List<DHCPOption> options = Lists.newArrayList(dhcpPacket.getOptions());
+        DhcpOption82 option82 = new DhcpOption82();
+        option82.setAgentCircuitId(CLIENT_CIRCUIT_ID);
+
+        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);
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(sent.serialize());
+        DhcpEthernet expectedPacket = null;
+        try {
+            expectedPacket = DhcpEthernet.deserializer().deserialize(byteBuffer.array(),
+                    0, byteBuffer.array().length);
+        } catch (Exception e) {
+        }
+        assertEquals(expectedPacket, relayed);
+
+    }
+
+    public void compareServerPackets(DhcpEthernet sent, DhcpEthernet relayed) {
+        sent.setDestinationMacAddress(CLIENT_MAC);
+        sent.setQinQVid(CLIENT_S_TAG.toShort());
+        sent.setVlanID(CLIENT_C_TAG.toShort());
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(sent.serialize());
+        DhcpEthernet expectedPacket = null;
+        try {
+            expectedPacket = DhcpEthernet.deserializer().deserialize(byteBuffer.array(),
+                    0, byteBuffer.array().length);
+        } catch (Exception e) {
+        }
+        assertEquals(expectedPacket, relayed);
+
+    }
+
+    private class MockDevice extends DefaultDevice {
+
+        public MockDevice(ProviderId providerId, DeviceId id, Type type,
+                          String manufacturer, String hwVersion, String swVersion,
+                          String serialNumber, ChassisId chassisId, Annotations... annotations) {
+            super(providerId, id, type, manufacturer, hwVersion, swVersion, serialNumber,
+                    chassisId, annotations);
+        }
+    }
+
+    private class MockDeviceService extends DeviceServiceAdapter {
+
+        private ProviderId providerId = new ProviderId("of", "foo");
+        private final Device device1 = new MockDevice(providerId, DEVICE_ID_1, Device.Type.SWITCH,
+                "foo.inc", "0", "0", OLT_DEV_ID, new ChassisId(),
+                DEVICE_ANNOTATIONS);
+
+        @Override
+        public Device getDevice(DeviceId devId) {
+            return device1;
+
+        }
+
+        @Override
+        public Port getPort(ConnectPoint cp) {
+            return new MockPort();
+        }
+
+        @Override
+        public boolean isAvailable(DeviceId d) {
+            return true;
+        }
+    }
+
+    private class  MockPort implements Port {
+
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+        public long portSpeed() {
+            return 1000;
+        }
+        public Element element() {
+            return null;
+        }
+        public PortNumber number() {
+            return null;
+        }
+        public Annotations annotations() {
+            return new MockAnnotations();
+        }
+        public Type type() {
+            return Port.Type.FIBER;
+        }
+
+        private class MockAnnotations implements Annotations {
+
+            @Override
+            public String value(String val) {
+                return "PON 1/1";
+            }
+            public Set<String> keys() {
+                return null;
+            }
+        }
+    }
+
+    private class MockSubService implements SubscriberAndDeviceInformationService {
+        MockSubscriberAndDeviceInformation device =
+                new MockSubscriberAndDeviceInformation(OLT_DEV_ID, VlanId.NONE, VlanId.NONE, null, null,
+                        OLT_MAC_ADDRESS, Ip4Address.valueOf("10.10.10.10"));
+        MockSubscriberAndDeviceInformation sub =
+                new MockSubscriberAndDeviceInformation(CLIENT_NAS_PORT_ID, CLIENT_C_TAG,
+                        CLIENT_S_TAG, CLIENT_NAS_PORT_ID, CLIENT_CIRCUIT_ID, null, null);
+        @Override
+        public SubscriberAndDeviceInformation get(String id) {
+            if (id.equals(OLT_DEV_ID)) {
+                return device;
+            } else {
+                return  sub;
+            }
+        }
+
+        @Override
+        public void invalidateAll() {}
+        public void invalidateId(String id) {}
+        public SubscriberAndDeviceInformation getfromCache(String id) {
+            return null;
+        }
+    }
+
+    private class MockMastershipService extends MastershipServiceAdapter {
+        @Override
+        public boolean isLocalMaster(DeviceId d) {
+            return true;
+        }
+    }
+
+    private class MockSubscriberAndDeviceInformation extends SubscriberAndDeviceInformation {
+
+        MockSubscriberAndDeviceInformation(String id, VlanId ctag,
+                                           VlanId stag, String nasPortId,
+                                           String circuitId, MacAddress hardId,
+                                           Ip4Address ipAddress) {
+            this.setCTag(ctag);
+            this.setHardwareIdentifier(hardId);
+            this.setId(id);
+            this.setIPAddress(ipAddress);
+            this.setSTag(stag);
+            this.setNasPortId(nasPortId);
+            this.setCircuitId(circuitId);
+        }
+    }
+
+    private class MockHostService extends HostServiceAdapter {
+
+        @Override
+        public Set<Host> getHostsByMac(MacAddress mac) {
+
+            HostLocation loc = new HostLocation(DEVICE_ID_1, PortNumber.portNumber(22), 0);
+
+            IpAddress ip = IpAddress.valueOf("10.100.200.10");
+
+            Host h = new DefaultHost(ProviderId.NONE, HostId.hostId(mac, VlanId.NONE),
+                    mac, VlanId.NONE, loc, ImmutableSet.of(ip));
+
+            return ImmutableSet.of(h);
+        }
+    }
+
+
+    /**
+     * Mocks the AAAConfig class to force usage of an unroutable address for the
+     * RADIUS server.
+     */
+    static class MockDhcpL2RealyConfig extends DhcpL2RelayConfig {
+        @Override
+        public Set<ConnectPoint> getDhcpServerConnectPoint() {
+            return ImmutableSet.of(SERVER_CONNECT_POINT);
+        }
+    }
+
+    /**
+     * Mocks the network config registry.
+     */
+    @SuppressWarnings("unchecked")
+    private static final class TestNetworkConfigRegistry
+            extends NetworkConfigRegistryAdapter {
+        @Override
+        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+            DhcpL2RelayConfig dhcpConfig = new MockDhcpL2RealyConfig();
+            return (C) dhcpConfig;
+        }
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTestBase.java b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTestBase.java
new file mode 100755
index 0000000..a80c0f8
--- /dev/null
+++ b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTestBase.java
@@ -0,0 +1,301 @@
+/*
+ * 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;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCPOption;
+import org.onlab.packet.DHCPPacketType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.DefaultPacketContext;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+
+import org.opencord.dhcpl2relay.packet.DhcpEthernet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * Common methods for AAA app testing.
+ */
+public class DhcpL2RelayTestBase {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final int TRANSACTION_ID = 1000;
+
+    private static final String EXPECTED_IP = "10.2.0.2";
+
+    List<BasePacket> savedPackets = new LinkedList<>();
+    PacketProcessor packetProcessor;
+
+
+    /**
+     * Saves the given packet onto the saved packets list.
+     *
+     * @param packet packet to save
+     */
+    void savePacket(BasePacket packet) {
+        savedPackets.add(packet);
+    }
+
+    BasePacket getPacket() {
+        return savedPackets.remove(0);
+    }
+
+    /**
+     * Keeps a reference to the PacketProcessor and saves the OutboundPackets.
+     */
+    class MockPacketService extends PacketServiceAdapter {
+
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+            packetProcessor = processor;
+        }
+
+        @Override
+        public void emit(OutboundPacket packet) {
+            try {
+                DhcpEthernet eth = DhcpEthernet.deserializer().deserialize(packet.data().array(),
+                        0, packet.data().array().length);
+                savePacket(eth);
+            } catch (Exception e) {
+                fail(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Mocks the DefaultPacketContext.
+     */
+    final class TestPacketContext extends DefaultPacketContext {
+
+        private TestPacketContext(long time, InboundPacket inPkt,
+                                  OutboundPacket outPkt, boolean block) {
+            super(time, inPkt, outPkt, block);
+        }
+
+        @Override
+        public void send() {
+            // We don't send anything out.
+        }
+    }
+
+    /**
+     * Sends an Ethernet packet to the process method of the Packet Processor.
+     *
+     * @param pkt Ethernet packet
+     */
+    void sendPacket(DhcpEthernet pkt, ConnectPoint cp) {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(pkt.serialize());
+        InboundPacket inPacket = new DefaultInboundPacket(cp, null, byteBuffer);
+
+        PacketContext context = new TestPacketContext(127L, inPacket, null, false);
+        packetProcessor.process(context);
+    }
+
+    /**
+     * Constructs an Ethernet packet with IP/UDP/DHCP payload.
+     *
+     * @return Ethernet packet
+     */
+    private DhcpEthernet construcEthernetPacket(MacAddress srcMac, MacAddress dstMac,
+                                                String dstIp, byte dhcpReqRsp,
+                                                MacAddress clientHwAddress,
+                                                Ip4Address dhcpClientIpAddress) {
+        // Ethernet Frame.
+        DhcpEthernet ethPkt = new DhcpEthernet();
+        ethPkt.setSourceMacAddress(srcMac);
+        ethPkt.setDestinationMacAddress(dstMac);
+        ethPkt.setEtherType(Ethernet.TYPE_IPV4);
+        ethPkt.setVlanID((short) 2);
+        ethPkt.setPriorityCode((byte) 6);
+
+        // IP Packet
+        IPv4 ipv4Reply = new IPv4();
+        ipv4Reply.setSourceAddress(0);
+        ipv4Reply.setDestinationAddress(dstIp);
+
+        ipv4Reply.setTtl((byte) 127);
+
+        // UDP Datagram.
+        UDP udpReply = new UDP();
+        udpReply.setSourcePort((byte) UDP.DHCP_CLIENT_PORT);
+        udpReply.setDestinationPort((byte) UDP.DHCP_SERVER_PORT);
+
+        // DHCP Payload.
+        DHCP dhcpReply = new DHCP();
+        dhcpReply.setOpCode(dhcpReqRsp);
+
+        dhcpReply.setYourIPAddress(dhcpClientIpAddress.toInt());
+        dhcpReply.setServerIPAddress(0);
+
+        final byte[] serverNameBytes = new byte[64];
+        String result = new String(serverNameBytes, StandardCharsets.US_ASCII).trim();
+        dhcpReply.setServerName(result);
+
+        final byte[] bootFileBytes = new byte[128];
+        String result1 = new String(bootFileBytes, StandardCharsets.US_ASCII).trim();
+        dhcpReply.setBootFileName(result1);
+
+        dhcpReply.setTransactionId(TRANSACTION_ID);
+        dhcpReply.setClientHardwareAddress(clientHwAddress.toBytes());
+        dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
+        dhcpReply.setHardwareAddressLength((byte) 6);
+
+        udpReply.setPayload(dhcpReply);
+        ipv4Reply.setPayload(udpReply);
+        ethPkt.setPayload(ipv4Reply);
+
+        return ethPkt;
+    }
+
+    /**
+     * Constructs DHCP Discover Packet.
+     *
+     * @return Ethernet packet
+     */
+    DhcpEthernet constructDhcpDiscoverPacket(MacAddress clientMac) {
+
+        DhcpEthernet pkt = construcEthernetPacket(clientMac, MacAddress.BROADCAST,
+                "255.255.255.255", DHCP.OPCODE_REQUEST, MacAddress.NONE,
+                Ip4Address.valueOf("0.0.0.0"));
+
+        IPv4 ipv4Packet = (IPv4) pkt.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        dhcpPacket.setOptions(constructDhcpOptions(DHCPPacketType.DHCPDISCOVER));
+
+        return pkt;
+    }
+
+    /**
+     * Constructs DHCP Request Packet.
+     *
+     * @return Ethernet packet
+     */
+    DhcpEthernet constructDhcpRequestPacket(MacAddress clientMac) {
+
+        DhcpEthernet pkt = construcEthernetPacket(clientMac, MacAddress.BROADCAST,
+                "255.255.255.255", DHCP.OPCODE_REQUEST, MacAddress.NONE,
+                Ip4Address.valueOf("0.0.0.0"));
+
+        IPv4 ipv4Packet = (IPv4) pkt.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        dhcpPacket.setOptions(constructDhcpOptions(DHCPPacketType.DHCPREQUEST));
+
+        return pkt;
+    }
+
+    /**
+     * Constructs DHCP Offer Packet.
+     *
+     * @return Ethernet packet
+     */
+    DhcpEthernet constructDhcpOfferPacket(MacAddress servMac, MacAddress clientMac,
+                                           String ipAddress, String dhcpClientIpAddress) {
+
+        DhcpEthernet pkt = construcEthernetPacket(servMac, clientMac, ipAddress, DHCP.OPCODE_REPLY,
+                clientMac, Ip4Address.valueOf(dhcpClientIpAddress));
+
+        IPv4 ipv4Packet = (IPv4) pkt.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        dhcpPacket.setOptions(constructDhcpOptions(DHCPPacketType.DHCPOFFER));
+
+        return pkt;
+    }
+
+    /**
+     * Constructs DHCP Ack Packet.
+     *
+     * @return Ethernet packet
+     */
+    DhcpEthernet constructDhcpAckPacket(MacAddress servMac, MacAddress clientMac,
+                                           String ipAddress, String dhcpClientIpAddress) {
+
+        DhcpEthernet pkt = construcEthernetPacket(servMac, clientMac, ipAddress, DHCP.OPCODE_REPLY,
+                clientMac, Ip4Address.valueOf(dhcpClientIpAddress));
+
+        IPv4 ipv4Packet = (IPv4) pkt.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        dhcpPacket.setOptions(constructDhcpOptions(DHCPPacketType.DHCPACK));
+
+        return pkt;
+    }
+
+    /**
+     * Constructs DHCP Discover Options.
+     *
+     * @return Ethernet packet
+     */
+    private List<DHCPOption> constructDhcpOptions(DHCPPacketType packetType) {
+
+        // DHCP Options.
+        DHCPOption option = new DHCPOption();
+        List<DHCPOption> optionList = new ArrayList<>();
+
+
+        // DHCP Message Type.
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+        option.setLength((byte) 1);
+        byte[] optionData = {(byte) packetType.getValue()};
+        option.setData(optionData);
+        optionList.add(option);
+
+        // DHCP Requested IP.
+        option = new DHCPOption();
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue());
+        option.setLength((byte) 4);
+        optionData = Ip4Address.valueOf(EXPECTED_IP).toOctets();
+        option.setData(optionData);
+        optionList.add(option);
+
+        // End Option.
+        option = new DHCPOption();
+        option.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+        option.setLength((byte) 1);
+        optionList.add(option);
+
+        return optionList;
+    }
+}