[CORD-2318] Allow v6 alternative dhcp server for default and indirect dhcp server

Change-Id: Ief4027a7528db7b4e3058fce369b3e1ddc214e92
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
index 1472082..654c79d 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  *
  */
-
 package org.onosproject.dhcprelay;
 
 import com.google.common.collect.HashMultimap;
@@ -42,7 +41,6 @@
 import org.onlab.packet.VlanId;
 import org.onlab.packet.dhcp.Dhcp6RelayOption;
 import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
-import org.onlab.packet.dhcp.Dhcp6Option;
 import org.onlab.packet.dhcp.Dhcp6IaNaOption;
 import org.onlab.packet.dhcp.Dhcp6IaTaOption;
 import org.onlab.packet.dhcp.Dhcp6IaPdOption;
@@ -50,6 +48,7 @@
 import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
 import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
 import org.onlab.packet.dhcp.Dhcp6Duid;
+import org.onlab.packet.DHCP6.MsgType;
 import org.onlab.util.HexString;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
@@ -100,7 +99,7 @@
 import org.slf4j.LoggerFactory;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
-
+import org.onosproject.dhcprelay.Dhcp6HandlerUtil.InternalPacket;
 
 import java.nio.ByteBuffer;
 import java.util.List;
@@ -108,9 +107,8 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.ArrayList;
+
 import java.util.concurrent.atomic.AtomicInteger;
-
-
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
@@ -181,8 +179,20 @@
 
     private Boolean dhcpFpmEnabled = false;
 
+    private Dhcp6HandlerUtil dhcp6HandlerUtil = new Dhcp6HandlerUtil();
+
     private List<DhcpServerInfo> defaultServerInfoList = Lists.newArrayList();
     private List<DhcpServerInfo> indirectServerInfoList = Lists.newArrayList();
+    private class IpAddressInfo {
+        Ip6Address ip6Address;
+        long    prefTime;
+    }
+    private class PdPrefixInfo {
+        IpPrefix pdPrefix;
+        long    prefTime;
+    }
+    protected int dhcp6PollInterval = 24 * 3600; // 24 hr period
+
 
 
     // CLIENT message types
@@ -278,16 +288,19 @@
         Ethernet receivedPacket = context.inPacket().parsed();
 
         if (!configured()) {
-            log.warn("Missing DHCP6 relay server config. Abort packet processing");
-            log.trace("dhcp6 payload {}", dhcp6Payload);
-
+            log.warn("Missing DHCP6 relay server config. Abort packet processing dhcp6 payload {}", dhcp6Payload);
             return;
         }
+        byte msgTypeVal = dhcp6Payload.getMsgType();
+        MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
+        log.debug("msgType is {}", msgType);
 
-        byte msgType = dhcp6Payload.getMsgType();
 
         ConnectPoint inPort = context.inPacket().receivedFrom();
 
+        if (inPort == null) {
+            log.warn("incomming ConnectPoint is null");
+        }
         Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
         //ignore the packets if dhcp client interface is not configured on onos.
         if (receivingInterfaces.isEmpty()) {
@@ -295,27 +308,25 @@
             return;
         }
 
+        if (MSG_TYPE_FROM_CLIENT.contains(msgTypeVal)) {
 
-        if (MSG_TYPE_FROM_CLIENT.contains(msgType)) {
-            InternalPacket ethernetClientPacket =
+            List<InternalPacket> ethernetClientPacket =
                     processDhcp6PacketFromClient(context, receivedPacket, receivingInterfaces);
-            if (ethernetClientPacket != null) {
-                forwardPacket(ethernetClientPacket);
+            for (InternalPacket internalPacket : ethernetClientPacket) {
+                forwardPacket(internalPacket);
             }
-
-        } else if (MSG_TYPE_FROM_SERVER.contains(msgType)) {
-            log.debug("calling processDhcp6PacketFromServer with RELAY_REPL", msgType);
+        } else if (MSG_TYPE_FROM_SERVER.contains(msgTypeVal)) {
+            log.debug("calling processDhcp6PacketFromServer with RELAY_REPL {}", msgTypeVal);
             InternalPacket ethernetPacketReply =
                     processDhcp6PacketFromServer(context, receivedPacket, receivingInterfaces);
             if (ethernetPacketReply != null) {
                 forwardPacket(ethernetPacketReply);
             }
         } else {
-            log.warn("DHCP type {} not supported yet", msgType);
+            log.warn("Not so fast, packet type {} not supported yet", msgTypeVal);
         }
     }
 
-
     /**
      * Checks if this app has been configured.
      *
@@ -335,19 +346,7 @@
         // Do nothing here
     }
 
-    // the new class the contains Ethernet packet and destination port, kind of like adding
-    // internal header to the packet
-    private class InternalPacket {
-        Ethernet packet;
-        ConnectPoint destLocation;
-        public InternalPacket(Ethernet newPacket, ConnectPoint newLocation) {
-            packet = newPacket;
-            destLocation = newLocation;
-        }
-        void setLocation(ConnectPoint newLocation) {
-            destLocation = newLocation;
-        }
-    }
+
 
     //forward the packet to ConnectPoint where the DHCP server is attached.
     private void forwardPacket(InternalPacket packet) {
@@ -364,168 +363,17 @@
         } // if
     }
 
-    /**
-     * Check if the host is directly connected to the network or not.
-     *
-     * @param dhcp6Payload the dhcp6 payload
-     * @return true if the host is directly connected to the network; false otherwise
-     */
-    private boolean directlyConnected(DHCP6 dhcp6Payload) {
-        log.debug("directlyConnected enters");
-
-        if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
-                dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
-            log.debug("directlyConnected true. MsgType {}", dhcp6Payload.getMsgType());
-
-            return true;
-        }
-
-        // Regardless of relay-forward or relay-replay, check if we see another relay message
-        DHCP6 dhcp6Payload2 = dhcp6PacketFromRelayPacket(dhcp6Payload);
-        if (dhcp6Payload2 != null) {
-            if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELAY_FORW.value()) {
-                log.debug("directlyConnected  false. 1st realy-foward, 2nd MsgType {}", dhcp6Payload2.getMsgType());
-                return false;
-            } else {
-                // relay-reply
-                if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
-                    log.debug("directlyConnected  true. 2nd MsgType {}", dhcp6Payload2.getMsgType());
-                    return true;  // must be directly connected
-                } else {
-                    log.debug("directlyConnected  false. 1st relay-reply, 2nd relay-reply MsgType {}",
-                               dhcp6Payload2.getMsgType());
-                    return false;  // must be indirectly connected
-                }
-            }
-        } else {
-            log.debug("directlyConnected  true.");
-            return true;
-        }
-    }
-
-    /**
-     * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply.
-     *
-     * @param dhcp6 dhcp6 relay-reply or relay-foward
-     * @return dhcp6Packet dhcp6 packet extracted from relay-message
-     */
-    private DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) {
-        log.debug("dhcp6PacketFromRelayPacket  enters. dhcp6 {}", dhcp6);
-
-        // extract the relay message if exist
-        DHCP6 dhcp6Payload = dhcp6.getOptions().stream()
-                    .filter(opt -> opt instanceof Dhcp6RelayOption)
-                    .map(BasePacket::getPayload)
-                    .map(pld -> (DHCP6) pld)
-                    .findFirst()
-                    .orElse(null);
 
 
-        if (dhcp6Payload == null) {
-            // Can't find dhcp payload
-            log.debug("Can't find dhcp6 payload from relay message");
-        } else {
-            log.debug("dhcp6 payload found from relay message {}", dhcp6Payload);
-        }
-
-        return dhcp6Payload;
-    }
-
-    /**
-     * find the leaf DHCP6 packet from multi-level relay packet.
-     *
-     * @param relayPacket dhcp6 relay packet
-     * @return leafPacket non-relay dhcp6 packet
-     */
-    private DHCP6 getDhcp6Leaf(DHCP6 relayPacket) {
-        DHCP6 dhcp6Parent = relayPacket;
-        DHCP6 dhcp6Child = null;
-
-        log.debug("getDhcp6Leaf entered.");
-        while (dhcp6Parent != null) {
-            dhcp6Child = dhcp6PacketFromRelayPacket(dhcp6Parent);
-
-            if (dhcp6Child != null) {
-                if (dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
-                        dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
-                    log.debug("leaf dhcp6 packet found.");
-                    break;
-                } else {
-                    // found another relay
-                    // go for another loop
-                    dhcp6Parent = dhcp6Child;
-                }
-            } else {
-                log.warn("Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
-                break;
-            }
-        }
-        return dhcp6Child;
-    }
-
-    /**
-     * check if DHCP6 relay-reply is reply.
-     *
-     * @param relayPacket dhcp6 relay-reply
-     * @return boolean relay-reply contains ack
-     */
-    private boolean isDhcp6Reply(DHCP6 relayPacket) {
-        log.debug("isDhcp6Reply  entered.");
-
-        DHCP6 leafDhcp6 = getDhcp6Leaf(relayPacket);
-
-        if (leafDhcp6 != null) {
-            if (leafDhcp6.getMsgType() == DHCP6.MsgType.REPLY.value()) {
-                log.debug("isDhcp6Reply  true.");
-                return true;  // must be directly connected
-            } else {
-                log.debug("isDhcp6Reply false. leaf dhcp6 is not replay. MsgType {}", leafDhcp6.getMsgType());
-            }
-        } else {
-            log.debug("isDhcp6Reply false. Expected dhcp6 within relay pkt but not found.");
-        }
-        log.debug("isDhcp6Reply  false.");
-        return false;
-    }
-
-    /**
-     * check if DHCP6 is release or relay-forward contains release.
-     *
-     * @param dhcp6Payload dhcp6 packet
-     * @return boolean dhcp6 contains release
-     */
-    private boolean isDhcp6Release(DHCP6 dhcp6Payload) {
-
-        log.debug("isDhcp6Release  entered.");
-
-        if (dhcp6Payload.getMsgType() ==  DHCP6.MsgType.RELEASE.value()) {
-            log.debug("isDhcp6Release  true.");
-            return true;  // must be directly connected
-        } else {
-            DHCP6 dhcp6Leaf = getDhcp6Leaf(dhcp6Payload);
-            if (dhcp6Leaf != null) {
-                if (dhcp6Leaf.getMsgType() ==  DHCP6.MsgType.RELEASE.value()) {
-                    log.debug("isDhcp6Release  true. indirectlry connected");
-                    return true;
-                } else {
-                    log.debug("leaf dhcp6 is not release. MsgType {}",  dhcp6Leaf.getMsgType());
-                    return false;
-                }
-            } else {
-                log.debug("isDhcp6Release  false. dhcp6 is niether relay nor release.");
-                return false;
-            }
-        }
-    }
 
     /**
      * extract from dhcp6 packet client ipv6 address of given by dhcp server.
      *
      * @param dhcp6 the dhcp6 packet
-     * @return Ip6Address  Ip6Address given by dhcp server, or null if not exists
+     * @return IpAddressInfo  IpAddressInfo given by dhcp server, or null if not exists
      */
-    private Ip6Address extractIpAddress(DHCP6 dhcp6) {
-        Ip6Address ip = null;
+    private IpAddressInfo extractIpAddress(DHCP6 dhcp6) {
+        IpAddressInfo ipInfo = new IpAddressInfo();
 
         log.debug("extractIpAddress  enters dhcp6 {}.", dhcp6);
         // Extract IPv6 address from IA NA ot IA TA option
@@ -555,18 +403,18 @@
                     .map(opt -> (Dhcp6IaAddressOption) opt)
                     .findFirst();
         } else {
+            log.info("No IPv6 address found from iaTaOption {}", iaTaOption);
             iaAddressOption = Optional.empty();
         }
         if (iaAddressOption.isPresent()) {
-            ip = iaAddressOption.get().getIp6Address();
+            ipInfo.ip6Address = iaAddressOption.get().getIp6Address();
+            ipInfo.prefTime = iaAddressOption.get().getPreferredLifetime();
             log.debug("Found IPv6 address from iaAddressOption {}", iaAddressOption);
-
-
         } else {
             log.debug("Can't find IPv6 address from DHCPv6 {}", dhcp6);
+            return null;
         }
-
-        return ip;
+        return ipInfo;
     }
     /**
      * extract from dhcp6 packet Prefix prefix provided by dhcp server.
@@ -574,11 +422,11 @@
      * @param dhcp6 the dhcp6 payload
      * @return IpPrefix Prefix Delegation prefix, or null if not exists.
      */
-    private IpPrefix extractPrefix(DHCP6 dhcp6) {
-        log.trace("extractPrefix  enters {}", dhcp6);
+    private PdPrefixInfo extractPrefix(DHCP6 dhcp6) {
+        log.debug("extractPrefix  enters {}", dhcp6);
 
         // extract prefix
-        IpPrefix  prefixPrefix = null;
+        PdPrefixInfo  pdPrefixInfo = new PdPrefixInfo();
 
         Ip6Address prefixAddress = null;
 
@@ -591,29 +439,30 @@
 
         Optional<Dhcp6IaPrefixOption> iaPrefixOption;
         if (iaPdOption.isPresent()) {
-            log.trace("IA_PD option found {}", iaPdOption);
+            log.debug("IA_PD option found {}", iaPdOption);
 
             iaPrefixOption = iaPdOption.get().getOptions().stream()
                     .filter(opt -> opt instanceof Dhcp6IaPrefixOption)
                     .map(opt -> (Dhcp6IaPrefixOption) opt)
                     .findFirst();
         } else {
-            log.trace("IA_PD option NOT found");
+            log.debug("IA_PD option NOT found");
 
             iaPrefixOption = Optional.empty();
         }
         if (iaPrefixOption.isPresent()) {
-            log.trace("IAPrefix Option within IA_PD option found {}", iaPrefixOption);
+            log.debug("IAPrefix Option within IA_PD option found {}", iaPrefixOption);
 
             prefixAddress = iaPrefixOption.get().getIp6Prefix();
             int prefixLen = (int) iaPrefixOption.get().getPrefixLength();
-            log.trace("Prefix length is  {} bits", prefixLen);
-            prefixPrefix = IpPrefix.valueOf(prefixAddress, prefixLen);
+            log.debug("Prefix length is  {} bits", prefixLen);
+            pdPrefixInfo.pdPrefix = IpPrefix.valueOf(prefixAddress, prefixLen);
+            pdPrefixInfo.prefTime = iaPrefixOption.get().getPreferredLifetime();
         } else {
-            log.trace("Can't find IPv6 prefix from DHCPv6 {}", dhcp6);
+            log.debug("Can't find IPv6 prefix from DHCPv6 {}", dhcp6);
+            return null;
         }
-
-        return prefixPrefix;
+        return pdPrefixInfo;
     }
 
     /**
@@ -634,7 +483,7 @@
                     .findFirst()
                     .orElse(null);
         } else {
-            DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Payload);
+            DHCP6 leafDhcp = dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Payload);
             clientIdOption = leafDhcp.getOptions()
                     .stream()
                     .filter(opt -> opt instanceof Dhcp6ClientIdOption)
@@ -646,9 +495,10 @@
         return clientIdOption;
     }
     /**
-     * remove host or route.
+     * remove host or route and update dhcp relay record attributes.
      *
      * @param directConnFlag  flag to show that packet is from directly connected client
+     * @param location  client side connect point
      * @param dhcp6Packet the dhcp6 payload
      * @param clientPacket client's ethernet packet
      * @param clientIpv6 client's Ipv6 packet
@@ -658,78 +508,15 @@
                                    DHCP6 dhcp6Packet,
                                    Ethernet clientPacket, IPv6 clientIpv6,
                                    Interface clientInterface) {
-        log.debug("extractPrefix  enters {}", dhcp6Packet);
+        log.debug("removeHostOrRoute  enters {}", dhcp6Packet);
         VlanId vlanId = clientInterface.vlan();
-        MacAddress gwMac = clientPacket.getSourceMAC();  // could be gw or host
+        MacAddress srcMac = clientPacket.getSourceMAC();  // could be gw or host
         MacAddress leafClientMac;
         byte leafMsgType;
-        log.debug("client mac {} client vlan {}", HexString.toHexString(gwMac.toBytes(), ":"), vlanId);
-
-        // add host or route
-        boolean isMsgRelease = isDhcp6Release(dhcp6Packet);
-        IpAddress ip;
-        IpPrefix ipPrefix = null;
-        if (directConnFlag) {
-            // Add to host store if it is connected to network directly
-            ip = extractIpAddress(dhcp6Packet);
-            if (ip != null) {
-                if (isMsgRelease) {
-                    HostId hostId = HostId.hostId(gwMac, vlanId);
-                    log.debug("remove Host {} ip for directly connected.", hostId.toString());
-                    // Remove host's ip of  when dhcp release msg is received
-                    providerService.removeIpFromHost(hostId, ip);
-                }
-            } else {
-                log.debug("ipAddress not found. Do not remove Host {} for directly connected.",
-                        HostId.hostId(gwMac, vlanId).toString());
-            }
-            leafMsgType = dhcp6Packet.getMsgType();
-        } else {
-            // Remove from route store if it is not connected to network directly
-            // pick out the first link-local ip address
-            IpAddress nextHopIp = getFirstIpByHost(gwMac, vlanId);
-            if (nextHopIp == null) {
-                log.warn("Can't find link-local IP address of gateway mac {} vlanId {}",
-                        gwMac, vlanId);
-                return;
-            }
-
-            DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
-            ip = extractIpAddress(leafDhcp);
-            if (ip == null) {
-                log.debug("ip is null");
-            } else {
-                if (isMsgRelease) {
-                    Route routeForIP = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
-                    log.debug("removing route of 128 address for indirectly connected.");
-                    log.debug("128 ip {}, nexthop {}", HexString.toHexString(ip.toOctets(), ":"),
-                            HexString.toHexString(nextHopIp.toOctets(), ":"));
-                    routeStore.removeRoute(routeForIP);
-                }
-            }
-
-            ipPrefix = extractPrefix(leafDhcp);
-            if (ipPrefix == null) {
-                log.debug("ipPrefix is null ");
-            } else {
-                if (isMsgRelease) {
-                    Route routeForPrefix = new Route(Route.Source.STATIC, ipPrefix, nextHopIp);
-                    log.debug("removing route of PD for indirectly connected.");
-                    log.debug("pd ip {}, nexthop {}", HexString.toHexString(ipPrefix.address().toOctets(), ":"),
-                            HexString.toHexString(nextHopIp.toOctets(), ":"));
-
-                    routeStore.removeRoute(routeForPrefix);
-                    if (this.dhcpFpmEnabled) {
-                        dhcpFpmPrefixStore.removeFpmRecord(ipPrefix);
-                    }
-                }
-            }
-            leafMsgType = leafDhcp.getMsgType();
-        }
+        log.debug("client mac {} client vlan {}", HexString.toHexString(srcMac.toBytes(), ":"), vlanId);
 
         Dhcp6ClientIdOption clientIdOption = extractClinedId(directConnFlag, dhcp6Packet);
         if (clientIdOption != null) {
-            log.warn("CLIENTID option found {}", clientIdOption);
             if ((clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LLT) ||
                     (clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LL)) {
                 leafClientMac = MacAddress.valueOf(clientIdOption.getDuid().getLinkLayerAddress());
@@ -737,151 +524,126 @@
                 log.warn("Link-Layer Address not supported in CLIENTID option. No DhcpRelay Record created.");
                 return;
             }
-
         } else {
-            log.warn("CLIENTID option NOT found. No DhcpRelay Record created.");
+            log.warn("CLIENTID option NOT found. Don't create DhcpRelay Record.");
             return;
         }
 
-        HostId hostId = HostId.hostId(leafClientMac, vlanId);
-        DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
-
-        if (leafMsgType == DHCP6.MsgType.RELEASE.value()) {
-            log.debug("DHCP6 RELEASE msg.");
-            if (record != null) {
-                if (ip != null) {
-                    log.warn("DhcpRelay Record ip6Address is set to null.");
-                    record.ip6Address(null);
-                }
-                if (ipPrefix != null) {
-                    log.warn("DhcpRelay Record pdPrefix is set to null.");
-                    record.pdPrefix(null);
-                }
-                log.debug("ip {} pd {}", record.ip6Address(), record.pdPrefix());
-
-                if (!record.ip6Address().isPresent() && !record.pdPrefix().isPresent()) {
-                    log.warn("IP6 address and IP6 PD both are null. Remove record.");
-                    dhcpRelayStore.removeDhcpRecord(HostId.hostId(leafClientMac, vlanId));
-                }
-            }
-            return;
-        }
+        HostId leafHostId = HostId.hostId(leafClientMac, vlanId);
+        DhcpRecord record = dhcpRelayStore.getDhcpRecord(leafHostId).orElse(null);
         if (record == null) {
-            record = new DhcpRecord(HostId.hostId(leafClientMac, vlanId));
-        } else {
-            record = record.clone();
+            record = new DhcpRecord(leafHostId);
         }
-        record.addLocation(new HostLocation(location, System.currentTimeMillis()));
-        record.ip6Status(DHCP6.MsgType.getType(dhcp6Packet.getMsgType()));
-        record.setDirectlyConnected(directConnFlag);
-        if (!directConnFlag) {
-            // Update gateway mac address if the host is not directly connected
-            record.nextHop(gwMac);
-        }
-        record.updateLastSeen();
-        dhcpRelayStore.updateDhcpRecord(HostId.hostId(leafClientMac, vlanId), record);
 
-
-    }
-
-    /**
-     * add host or route.
-     *
-     * @param directConnFlag  flag to show that packet is from directly connected client
-     * @param location  client side connect point
-     * @param dhcp6Relay the dhcp6 payload
-     * @param embeddedDhcp6 the dhcp6 payload within relay
-     * @param gwMac client gw/host macAddress
-     * @param clientInterface client interface
-     */
-    private void addHostOrRoute(boolean directConnFlag,
-                                ConnectPoint location,
-                                DHCP6 dhcp6Relay,
-                                DHCP6 embeddedDhcp6,
-                                MacAddress gwMac,
-                                Interface clientInterface) {
-        log.debug("addHostOrRoute entered.");
-        VlanId vlanId = clientInterface.vlan();
-        Boolean isMsgReply = isDhcp6Reply(dhcp6Relay);
-        MacAddress leafClientMac;
-        Byte leafMsgType;
-
-        // add host or route
-        IpAddress ip;
-        IpPrefix ipPrefix = null;
+        Boolean isMsgRelease = dhcp6HandlerUtil.isDhcp6Release(dhcp6Packet);
+        IpAddressInfo ipInfo;
+        PdPrefixInfo pdInfo = null;
         if (directConnFlag) {
-            // Add to host store if it connect to network directly
-            ip = extractIpAddress(embeddedDhcp6);
-            if (ip != null) {
-                if (isMsgReply) {
-                    Set<IpAddress> ips = Sets.newHashSet(ip);
-
-                    HostId hostId = HostId.hostId(gwMac, vlanId);
-                    Host host = hostService.getHost(hostId);
-                    HostLocation hostLocation = new HostLocation(clientInterface.connectPoint(),
-                            System.currentTimeMillis());
-                    Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
-
-                    if (host != null) {
-                        // Dual homing support:
-                        // if host exists, use old locations and new location
-                        hostLocations.addAll(host.locations());
-                    }
-                    HostDescription desc = new DefaultHostDescription(gwMac, vlanId,
-                            hostLocations, ips,
-                            false);
-                    log.debug("adding Host for directly connected.");
-                    log.debug("client mac {} client vlan {} hostlocation {}",
-                            HexString.toHexString(gwMac.toBytes(), ":"),
-                            vlanId, hostLocation.toString());
-
-                    // Replace the ip when dhcp server give the host new ip address
-                    providerService.hostDetected(hostId, desc, false);
+            // Add to host store if it is connected to network directly
+            ipInfo = extractIpAddress(dhcp6Packet);
+            if (ipInfo != null) {
+                if (isMsgRelease) {
+                    HostId hostId = HostId.hostId(srcMac, vlanId);
+                    log.debug("remove Host {} ip for directly connected.", hostId.toString());
+                    providerService.removeIpFromHost(hostId, ipInfo.ip6Address);
                 }
             } else {
-                log.warn("ipAddress not found. Do not add Host {} for directly connected.",
-                        HostId.hostId(gwMac, vlanId).toString());
+                log.debug("ipAddress not found. Do not remove Host {} for directly connected.",
+                        HostId.hostId(srcMac, vlanId).toString());
             }
-            leafMsgType = embeddedDhcp6.getMsgType();
+            leafMsgType = dhcp6Packet.getMsgType();
         } else {
-            // Add to route store if it does not connect to network directly
+            // Remove from route store if it is not connected to network directly
             // pick out the first link-local ip address
-            IpAddress nextHopIp = getFirstIpByHost(gwMac, vlanId);
+            IpAddress nextHopIp = getFirstIpByHost(directConnFlag, srcMac, vlanId);
             if (nextHopIp == null) {
-                log.warn("Can't find link-local IP address of gateway mac {} vlanId {}",
-                        gwMac, vlanId);
+                log.warn("Can't find link-local IP address of gateway mac {} vlanId {}", srcMac, vlanId);
                 return;
             }
 
-            DHCP6 leafDhcp = getDhcp6Leaf(embeddedDhcp6);
-            ip = extractIpAddress(leafDhcp);
-            if (ip == null) {
-                    log.debug("ip is null");
+            DHCP6 leafDhcp = dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Packet);
+            ipInfo = extractIpAddress(leafDhcp);
+            if (ipInfo == null) {
+                log.debug("ip is null");
             } else {
-                if (isMsgReply) {
-                    Route routeForIP = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
-                    log.debug("adding Route of 128 address for indirectly connected.");
-                    routeStore.updateRoute(routeForIP);
+                if (isMsgRelease) {
+                    Route routeForIP = new Route(Route.Source.STATIC, ipInfo.ip6Address.toIpPrefix(), nextHopIp);
+                    log.debug("removing route of 128 address for indirectly connected.");
+                    log.debug("128 ip {}, nexthop {}",
+                            HexString.toHexString(ipInfo.ip6Address.toOctets(), ":"),
+                            HexString.toHexString(nextHopIp.toOctets(), ":"));
+                    routeStore.removeRoute(routeForIP);
                 }
             }
 
-            ipPrefix = extractPrefix(leafDhcp);
-            if (ipPrefix == null) {
-                    log.debug("ipPrefix is null ");
+            pdInfo = extractPrefix(leafDhcp);
+            if (pdInfo == null) {
+                log.debug("ipPrefix is null ");
             } else {
-                if (isMsgReply) {
-                    Route routeForPrefix = new Route(Route.Source.STATIC, ipPrefix, nextHopIp);
-                    log.debug("adding Route of PD for indirectly connected.");
-                    routeStore.updateRoute(routeForPrefix);
+                if (isMsgRelease) {
+                    Route routeForPrefix = new Route(Route.Source.STATIC, pdInfo.pdPrefix, nextHopIp);
+                    log.debug("removing route of PD for indirectly connected.");
+                    log.debug("pd ip {}, nexthop {}",
+                            HexString.toHexString(pdInfo.pdPrefix.address().toOctets(), ":"),
+                            HexString.toHexString(nextHopIp.toOctets(), ":"));
+
+                    routeStore.removeRoute(routeForPrefix);
                     if (this.dhcpFpmEnabled) {
-                        FpmRecord record = new FpmRecord(ipPrefix, nextHopIp, FpmRecord.Type.DHCP_RELAY);
-                        dhcpFpmPrefixStore.addFpmRecord(ipPrefix, record);
+                        dhcpFpmPrefixStore.removeFpmRecord(pdInfo.pdPrefix);
                     }
                 }
             }
             leafMsgType = leafDhcp.getMsgType();
         }
 
+        if (isMsgRelease) {
+            log.debug("DHCP6 RELEASE msg.");
+            if (record != null) {
+                if (ipInfo != null) {
+                    log.debug("DhcpRelay Record ip6Address is set to null.");
+                    record.ip6Address(null);
+                }
+                if (pdInfo != null) {
+                    log.debug("DhcpRelay Record pdPrefix is set to null.");
+                }
+
+                if (!record.ip6Address().isPresent() && !record.pdPrefix().isPresent()) {
+                    log.warn("IP6 address and IP6 PD both are null. Remove record.");
+                    // do not remove a record. Let timer task handler it.
+                    //dhcpRelayStore.removeDhcpRecord(HostId.hostId(leafClientMac, vlanId));
+                }
+            }
+        }
+
+        record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+        record.ip6Status(DHCP6.MsgType.getType(leafMsgType));
+        record.setDirectlyConnected(directConnFlag);
+        if (!directConnFlag) {
+            // Update gateway mac address if the host is not directly connected
+            record.nextHop(srcMac);
+        }
+        record.updateLastSeen();
+        dhcpRelayStore.updateDhcpRecord(leafHostId, record);
+    }
+
+    /**
+     * add host or route and update dhcp relay record.
+     *
+     * @param directConnFlag  flag to show that packet is from directly connected client
+     * @param location  client side connect point
+     * @param dhcp6Relay the dhcp6 payload
+     * @param embeddedDhcp6 the dhcp6 payload within relay
+     * @param srcMac client gw/host macAddress
+     * @param clientInterface client interface
+     */
+    private void addHostOrRoute(boolean directConnFlag, ConnectPoint location, DHCP6 dhcp6Relay,
+                                DHCP6 embeddedDhcp6, MacAddress srcMac, Interface clientInterface) {
+        log.debug("addHostOrRoute entered.");
+        VlanId vlanId = clientInterface.vlan();
+        Boolean isMsgReply = dhcp6HandlerUtil.isDhcp6Reply(dhcp6Relay);
+        MacAddress leafClientMac;
+        Byte leafMsgType;
+
         Dhcp6ClientIdOption clientIdOption = extractClinedId(directConnFlag, embeddedDhcp6);
         if (clientIdOption != null) {
             log.debug("CLIENTID option found {}", clientIdOption);
@@ -892,328 +654,223 @@
                 log.warn("Link-Layer Address not supported in CLIENTID option. No DhcpRelay Record created.");
                 return;
             }
-
         } else {
             log.warn("CLIENTID option NOT found. No DhcpRelay Record created.");
             return;
         }
-
-        if (leafMsgType == DHCP6.MsgType.RELEASE.value() ||
-                (leafMsgType == DHCP6.MsgType.REPLY.value()) && ip == null) {
-            log.warn("DHCP6 RELEASE/REPLY(null ip) from Server. MsgType {}", DHCP6.MsgType.getType(leafMsgType));
-            return;
-        }
-
-        HostId hostId = HostId.hostId(leafClientMac, vlanId);
-        DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+        HostId leafHostId = HostId.hostId(leafClientMac, vlanId);
+        DhcpRecord record = dhcpRelayStore.getDhcpRecord(leafHostId).orElse(null);
         if (record == null) {
             record = new DhcpRecord(HostId.hostId(leafClientMac, vlanId));
-        } else {
-            record = record.clone();
         }
+
+        IpAddressInfo ipInfo;
+        PdPrefixInfo pdInfo = null;
+        if (directConnFlag) {
+            // Add to host store if it connect to network directly
+            ipInfo = extractIpAddress(embeddedDhcp6);
+            if (ipInfo != null) {
+                if (isMsgReply) {
+                    Set<IpAddress> ips = Sets.newHashSet(ipInfo.ip6Address);
+                    HostId hostId = HostId.hostId(srcMac, vlanId);
+                    Host host = hostService.getHost(hostId);
+                    HostLocation hostLocation = new HostLocation(clientInterface.connectPoint(),
+                            System.currentTimeMillis());
+                    Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
+                    if (host != null) {
+                        // Dual homing support:
+                        // if host exists, use old locations and new location
+                        hostLocations.addAll(host.locations());
+                    }
+                    HostDescription desc = new DefaultHostDescription(srcMac, vlanId, hostLocations, ips,
+                            false);
+                    log.debug("adding Host for directly connected.");
+                    log.debug("client mac {} client vlan {} hostlocation {}",
+                            HexString.toHexString(srcMac.toBytes(), ":"), vlanId, hostLocation.toString());
+                    // Replace the ip when dhcp server give the host new ip address
+                    providerService.hostDetected(hostId, desc, false);
+                }
+            } else {
+                log.warn("ipAddress not found. Do not add Host {} for directly connected.",
+                        HostId.hostId(srcMac, vlanId).toString());
+            }
+            leafMsgType = embeddedDhcp6.getMsgType();
+        } else {
+            // Add to route store if it does not connect to network directly
+            // pick out the first link-local ip address
+            IpAddress nextHopIp = getFirstIpByHost(directConnFlag, srcMac, vlanId);
+            if (nextHopIp == null) {
+                log.warn("Can't find link-local IP address of gateway mac {} vlanId {}", srcMac, vlanId);
+                return;
+            }
+
+            DHCP6 leafDhcp = dhcp6HandlerUtil.getDhcp6Leaf(embeddedDhcp6);
+            ipInfo = extractIpAddress(leafDhcp);
+            if (ipInfo == null) {
+                log.debug("ip is null");
+            } else {
+                if (isMsgReply) {
+                    Route routeForIP = new Route(Route.Source.STATIC, ipInfo.ip6Address.toIpPrefix(), nextHopIp);
+                    log.debug("adding Route of 128 address for indirectly connected.");
+                    routeStore.updateRoute(routeForIP);
+                }
+            }
+
+            pdInfo = extractPrefix(leafDhcp);
+            if (pdInfo == null) {
+                log.debug("ipPrefix is null ");
+            } else {
+                if (isMsgReply) {
+                    Route routeForPrefix = new Route(Route.Source.STATIC, pdInfo.pdPrefix, nextHopIp);
+                    log.debug("adding Route of PD for indirectly connected.");
+                    routeStore.updateRoute(routeForPrefix);
+                    if (this.dhcpFpmEnabled) {
+                        FpmRecord fpmRecord = new FpmRecord(pdInfo.pdPrefix, nextHopIp, FpmRecord.Type.DHCP_RELAY);
+                        dhcpFpmPrefixStore.addFpmRecord(pdInfo.pdPrefix, fpmRecord);
+                    }
+                }
+            }
+            leafMsgType = leafDhcp.getMsgType();
+        }
+        if (leafMsgType == DHCP6.MsgType.RELEASE.value() ||
+                (leafMsgType == DHCP6.MsgType.REPLY.value()) && ipInfo == null) {
+            log.warn("DHCP6 RELEASE/REPLY(null ip) from Server. MsgType {}", leafMsgType);
+            //return;
+        }
+
         record.addLocation(new HostLocation(location, System.currentTimeMillis()));
         if (leafMsgType == DHCP6.MsgType.REPLY.value()) {
-            if (ip != null) {
+            if (ipInfo != null) {
                 log.debug("IP6 address is being stored into dhcp-relay store.");
-                log.debug("IP6 address {}", HexString.toHexString(ip.toOctets(), ":"));
-                record.ip6Address(ip.getIp6Address());
+                log.debug("IP6 address {}", HexString.toHexString(ipInfo.ip6Address.toOctets(), ":"));
+                record.ip6Address(ipInfo.ip6Address);
             } else {
                 log.debug("IP6 address is not returned from server. Maybe only PD is returned.");
             }
-            if (ipPrefix != null) {
-                log.debug("IP6 PD address is being stored into dhcp-relay store.");
-                log.debug("IP6 PD address {}", HexString.toHexString(ipPrefix.address().toOctets(), ":"));
-                record.pdPrefix(ipPrefix);
+            if (pdInfo != null) {
+                log.debug("IP6 PD address {}",
+                        HexString.toHexString(pdInfo.pdPrefix.address().toOctets(), ":"));
             } else {
                 log.debug("IP6 PD address is not returned from server. Maybe only IPAddress is returned.");
             }
         }
-        record.ip6Status(DHCP6.MsgType.getType(dhcp6Relay.getMsgType()));
+        record.ip6Status(DHCP6.MsgType.getType(leafMsgType));
         record.setDirectlyConnected(directConnFlag);
         record.updateLastSeen();
-        dhcpRelayStore.updateDhcpRecord(HostId.hostId(leafClientMac, vlanId), record);
-
+        dhcpRelayStore.updateDhcpRecord(leafHostId, record);
     }
 
     /**
-     * Build the DHCP6 solicit/request packet with gatewayip.
-     * TODO: method too long, need to be refactored.
+     * build the DHCP6 solicit/request packet with gatewayip.
      *
      * @param context packet context
      * @param clientPacket client ethernet packet
      * @param clientInterfaces set of client side interfaces
      */
-    private InternalPacket processDhcp6PacketFromClient(PacketContext context,
-                                                        Ethernet clientPacket, Set<Interface> clientInterfaces) {
+    private List<InternalPacket> processDhcp6PacketFromClient(PacketContext context,
+                                                              Ethernet clientPacket,
+                                                              Set<Interface> clientInterfaces) {
         ConnectPoint receivedFrom = context.inPacket().receivedFrom();
         DeviceId receivedFromDevice = receivedFrom.deviceId();
-        DhcpServerInfo serverInfo;
-        Ip6Address dhcpServerIp = null;
-        ConnectPoint dhcpServerConnectPoint = null;
-        MacAddress dhcpConnectMac = null;
-        VlanId dhcpConnectVlan = null;
-        Ip6Address dhcpGatewayIp = null;
-        Ip6Address indirectDhcpServerIp = null;
-        ConnectPoint indirectDhcpServerConnectPoint = null;
-        MacAddress indirectDhcpConnectMac = null;
-        VlanId indirectDhcpConnectVlan = null;
-        Ip6Address indirectDhcpGatewayIp = null;
-        Ip6Address indirectRelayAgentIpFromCfg = null;
-        if (!defaultServerInfoList.isEmpty()) {
-            serverInfo = defaultServerInfoList.get(0);
-            dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
-            dhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
-            dhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
-            dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
-        }
-        if (!indirectServerInfoList.isEmpty()) {
-            serverInfo = indirectServerInfoList.get(0);
-            indirectDhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
-            indirectDhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
-            indirectDhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
-            indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
-            indirectRelayAgentIpFromCfg = serverInfo.getRelayAgentIp6(receivedFromDevice).orElse(null);
-        }
-        Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces);
+        Ip6Address relayAgentIp = dhcp6HandlerUtil.getRelayAgentIPv6Address(clientInterfaces);
         MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
         if (relayAgentIp == null || relayAgentMac == null) {
             log.warn("Missing DHCP relay agent interface Ipv6 addr config for "
-                             + "packet from client on port: {}. Aborting packet processing",
-                     clientInterfaces.iterator().next().connectPoint());
-            return null;
-        }
-        // get dhcp6 header.
-        IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
-        UDP clientUdp = (UDP) clientIpv6.getPayload();
-        DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
-        boolean directConnFlag = directlyConnected(clientDhcp6);
-        Interface serverInterface;
-        if (directConnFlag) {
-            serverInterface = getServerInterface();
-        } else {
-            serverInterface = getIndirectServerInterface();
-            if (serverInterface == null) {
-                // Indirect server interface not found, use default server interface
-                serverInterface = getServerInterface();
-            }
-        }
-        if (serverInterface == null) {
-            log.warn("Can't get {} server interface, ignore", directConnFlag ? "direct" : "indirect");
-            return null;
-        }
-        Ip6Address ipFacingServer = getFirstIpFromInterface(serverInterface);
-        MacAddress macFacingServer = serverInterface.mac();
-        if (ipFacingServer == null || macFacingServer == null) {
-            log.warn("No IP v6 address for server Interface {}", serverInterface);
-            return null;
-        }
-        Ethernet etherReply = clientPacket.duplicate();
-        etherReply.setSourceMACAddress(macFacingServer);
-        if ((directConnFlag && dhcpConnectMac == null)  ||
-                !directConnFlag && indirectDhcpConnectMac == null && dhcpConnectMac == null)   {
-            log.trace("Packet received from {} connected client.", directConnFlag ? "directly" : "indirectly");
-            log.debug("DHCP6 {} not yet resolved .. Aborting DHCP packet processing from client on port: {}",
-                     (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
-                             : "gateway IP " + dhcpGatewayIp,
-                     clientInterfaces.iterator().next().connectPoint());
-            return null;
-        }
-        if (dhcpServerConnectPoint == null) {
-            log.warn("DHCP6 server connection point direct {} directConn {} indirectConn {} is not set up yet",
-                     directConnFlag, dhcpServerConnectPoint, indirectDhcpServerConnectPoint);
+                      + "packet from client on port: {}. Aborting packet processing",
+                      clientInterfaces.iterator().next().connectPoint());
             return null;
         }
 
-        etherReply.setDestinationMACAddress(dhcpConnectMac);
-        etherReply.setVlanID(dhcpConnectVlan.toShort());
-        IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
-        byte[] peerAddress = clientIpv6.getSourceAddress();
-        ipv6Packet.setSourceAddress(ipFacingServer.toOctets());
-        ipv6Packet.setDestinationAddress(dhcpServerIp.toOctets());
-        UDP udpPacket = (UDP) ipv6Packet.getPayload();
-        udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
-        DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
-        byte[] dhcp6PacketByte = dhcp6Packet.serialize();
+        IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
+        UDP clientUdp = (UDP) clientIpv6.getPayload();
+        DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
+
+        boolean directConnFlag = dhcp6HandlerUtil.directlyConnected(clientDhcp6);
 
         ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
         VlanId vlanIdInUse = VlanId.vlanId(clientPacket.getVlanID());
         Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
-                .stream().filter(iface -> interfaceContainsVlan(iface, vlanIdInUse))
-                .findFirst().orElse(null);
+                .stream().filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
+                .findFirst()
+                .orElse(null);
 
-        removeHostOrRoute(directConnFlag, clientConnectionPoint, dhcp6Packet, clientPacket,
-                          clientIpv6, clientInterface);
+        List<InternalPacket> internalPackets = new ArrayList<>();
+        List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+        List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
 
-        DHCP6 dhcp6Relay = new DHCP6();
-        dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
-        if (directConnFlag) {
-            dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
-            log.debug("direct connection: relayAgentIp obtained dynamically {}",
-                      HexString.toHexString(relayAgentIp.toOctets(), ":"));
+        for (DhcpServerInfo serverInfo : copyServerInfoList) {
+            if (!dhcp6HandlerUtil.checkDhcpServerConnPt(directConnFlag, serverInfo)) {
+                log.warn("Can't get server connect point, ignore");
+                continue;
+            }
+            DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
+            if (newServerInfo == null) {
+                log.warn("Can't get server interface with host info resolved, ignore");
+                continue;
+            }
 
-        } else {
-            if (indirectDhcpServerIp == null) {
-                log.debug("indirect DhcpServerIp not available, use default DhcpServerIp {}",
-                         HexString.toHexString(dhcpServerIp.toOctets()));
-             } else {
-                 // Indirect case, replace destination to indirect dhcp server if exist
-                 // Check if mac is obtained for valid server ip
-                 if (indirectDhcpConnectMac == null) {
-                     log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
-                                      + "packet processing from client on port: {}",
-                              (indirectDhcpGatewayIp == null) ? "server IP " + indirectDhcpServerIp
-                                      : "gateway IP " + indirectDhcpGatewayIp,
-                              clientInterfaces.iterator().next().connectPoint());
-                     return null;
-                 }
-                 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
-                 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
-                 ipv6Packet.setDestinationAddress(indirectDhcpServerIp.toOctets());
+            Interface serverInterface = getServerInterface(newServerInfo);
+            if (serverInterface == null) {
+                log.warn("Can't get server interface, ignore");
+                continue;
+            }
 
+            Ethernet etherReply = dhcp6HandlerUtil.buildDhcp6PacketFromClient(context, clientPacket,
+                                                              clientInterfaces, newServerInfo, serverInterface);
+            removeHostOrRoute(directConnFlag, clientConnectionPoint, clientDhcp6, clientPacket,
+                    clientIpv6, clientInterface);
 
-             }
-             if (indirectRelayAgentIpFromCfg == null) {
-                 dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
-                 log.trace("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}",
-                          HexString.toHexString(relayAgentIp.toOctets(), ":"));
-             } else {
-                 dhcp6Relay.setLinkAddress(indirectRelayAgentIpFromCfg.toOctets());
-                 log.trace("indirect connection: relayAgentIp from config file is available! {}",
-                           HexString.toHexString(indirectRelayAgentIpFromCfg.toOctets(), ":"));
-             }
-         }
+            InternalPacket internalPacket = new Dhcp6HandlerUtil().new InternalPacket(etherReply,
+                    serverInfo.getDhcpServerConnectPoint().get());
+            internalPackets.add(internalPacket);
+        }
+        log.debug("num of client packets to send is{}", internalPackets.size());
 
-         // peer address: address of the client or relay agent from which
-         // the message to be relayed was received.
-         dhcp6Relay.setPeerAddress(peerAddress);
-         List<Dhcp6Option> options = new ArrayList<>();
-         // directly connected case, hop count is zero; otherwise, hop count + 1
-         if (directConnFlag) {
-             dhcp6Relay.setHopCount((byte) 0);
-         } else {
-             dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
-         }
-         // create relay message option
-         Dhcp6Option relayMessage = new Dhcp6Option();
-         relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value());
-         relayMessage.setLength((short) dhcp6PacketByte.length);
-         relayMessage.setData(dhcp6PacketByte);
-         options.add(relayMessage);
-         // create interfaceId option
-         String inPortString = "-" + context.inPacket().receivedFrom().toString() + ":";
-         Dhcp6Option interfaceId = new Dhcp6Option();
-         interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
-         byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress();
-         byte[] inPortStringBytes = inPortString.getBytes();
-         byte[] vlanIdBytes = new byte[2];
-         vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff);
-         vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff);
-         byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length +
-                 inPortStringBytes.length + vlanIdBytes.length];
-         log.trace("Length: interfaceIdBytes  {} clientSoureMacBytes {} inPortStringBytes {} vlan {}",
-                   interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length,
-                   vlanIdBytes.length);
-         System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
-         System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length, inPortStringBytes.length);
-         System.arraycopy(vlanIdBytes, 0, interfaceIdBytes, clientSoureMacBytes.length + inPortStringBytes.length,
-                          vlanIdBytes.length);
-         interfaceId.setData(interfaceIdBytes);
-         interfaceId.setLength((short) interfaceIdBytes.length);
-         options.add(interfaceId);
-         log.debug("interfaceId write srcMac {} portString {}",
-                   HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
-         dhcp6Relay.setOptions(options);
-         udpPacket.setPayload(dhcp6Relay);
-         udpPacket.resetChecksum();
-         ipv6Packet.setPayload(udpPacket);
-         ipv6Packet.setHopLimit((byte) 64);
-         etherReply.setPayload(ipv6Packet);
-         if (directConnFlag || indirectDhcpServerIp == null) {
-             return new InternalPacket(etherReply, dhcpServerConnectPoint);
-         } else {
-             return new InternalPacket(etherReply, indirectDhcpServerConnectPoint);
-         }
-     }
+        return internalPackets;
+    }
 
     /**
-     *
      * process the DHCP6 relay-reply packet from dhcp server.
      *
      * @param context packet context
      * @param receivedPacket server ethernet packet
      * @param recevingInterfaces set of server side interfaces
+     * @return internalPacket toward client
      */
     private InternalPacket processDhcp6PacketFromServer(PacketContext context,
                                                         Ethernet receivedPacket, Set<Interface> recevingInterfaces) {
-
-        ConnectPoint receivedFrom = context.inPacket().receivedFrom();
-        DeviceId receivedFromDevice = receivedFrom.deviceId();
-
-        // TODO: refactor
-        DhcpServerInfo serverInfo;
-        Ip6Address dhcpServerIp = null;
-        ConnectPoint dhcpServerConnectPoint = null;
-        MacAddress dhcpConnectMac = null;
-        VlanId dhcpConnectVlan = null;
-        Ip6Address dhcpGatewayIp = null;
-
-        Ip6Address indirectDhcpServerIp = null;
-        ConnectPoint indirectDhcpServerConnectPoint = null;
-        MacAddress indirectDhcpConnectMac = null;
-        VlanId indirectDhcpConnectVlan = null;
-        Ip6Address indirectDhcpGatewayIp = null;
-        Ip6Address indirectRelayAgentIpFromCfg = null;
-
-        if (!defaultServerInfoList.isEmpty()) {
-            serverInfo = defaultServerInfoList.get(0);
-            dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
-            dhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
-            dhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
-            dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
-        }
-
-        if (!indirectServerInfoList.isEmpty()) {
-            serverInfo = indirectServerInfoList.get(0);
-            indirectDhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
-            indirectDhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null);
-            indirectDhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null);
-            indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
-            indirectRelayAgentIpFromCfg = serverInfo.getRelayAgentIp6(receivedFromDevice).orElse(null);
-        }
-
         // get dhcp6 header.
         Ethernet etherReply = receivedPacket.duplicate();
         IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
         UDP udpPacket = (UDP) ipv6Packet.getPayload();
         DHCP6 dhcp6Relay = (DHCP6) udpPacket.getPayload();
+        Boolean directConnFlag = dhcp6HandlerUtil.directlyConnected(dhcp6Relay);
 
-        Boolean directConnFlag = directlyConnected(dhcp6Relay);
+        DHCP6 embeddedDhcp6 = dhcp6Relay.getOptions().stream()
+                .filter(opt -> opt instanceof Dhcp6RelayOption)
+                .map(BasePacket::getPayload)
+                .map(pld -> (DHCP6) pld)
+                .findFirst()
+                .orElse(null);
+
         ConnectPoint inPort = context.inPacket().receivedFrom();
-        if ((directConnFlag || indirectDhcpServerIp == null)
-             && !inPort.equals(dhcpServerConnectPoint)) {
-            log.warn("Receiving port {} is not the same as server connect point {} for direct or indirect-null",
-                    inPort, dhcpServerConnectPoint);
-            return null;
-        }
+        DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
 
-        if (!directConnFlag && indirectDhcpServerIp != null &&
-                                !inPort.equals(indirectDhcpServerConnectPoint)) {
-            log.warn("Receiving port {} is not the same as server connect point {} for indirect",
-                    inPort, indirectDhcpServerConnectPoint);
+        if (foundServerInfo == null) {
+            log.warn("Cannot find server info");
             return null;
+        } else {
+            if (dhcp6HandlerUtil.isServerIpEmpty(foundServerInfo)) {
+                log.warn("Cannot find server info's ipaddress");
+                return null;
+            }
         }
 
-
         Dhcp6InterfaceIdOption interfaceIdOption = dhcp6Relay.getOptions().stream()
                 .filter(opt -> opt instanceof Dhcp6InterfaceIdOption)
                 .map(opt -> (Dhcp6InterfaceIdOption) opt)
                 .findFirst()
                 .orElse(null);
-
         if (interfaceIdOption == null) {
             log.warn("Interface Id option is not present, abort packet...");
             return null;
@@ -1221,14 +878,11 @@
 
         MacAddress peerMac = interfaceIdOption.getMacAddress();
         String clientConnectionPointStr = new String(interfaceIdOption.getInPort());
-
         ConnectPoint clientConnectionPoint = ConnectPoint.deviceConnectPoint(clientConnectionPointStr);
         VlanId vlanIdInUse = VlanId.vlanId(interfaceIdOption.getVlanId());
         Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
-                .stream()
-                .filter(iface -> interfaceContainsVlan(iface, vlanIdInUse))
-                .findFirst()
-                .orElse(null);
+                .stream().filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
+                .findFirst().orElse(null);
         if (clientInterface == null) {
             log.warn("Cannot get client interface for from packet, abort... vlan {}", vlanIdInUse.toString());
             return null;
@@ -1256,10 +910,8 @@
                 return null;
             }
             log.trace("Client mac address found from getHostByIp");
-
         }
         etherReply.setDestinationMACAddress(clientMac);
-
         // ip header
         ipv6Packet.setSourceAddress(dhcp6Relay.getLinkAddress());
         ipv6Packet.setDestinationAddress(dhcp6Relay.getPeerAddress());
@@ -1270,15 +922,6 @@
         } else {
             udpPacket.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
         }
-
-        DHCP6 embeddedDhcp6 = dhcp6Relay.getOptions().stream()
-                    .filter(opt -> opt instanceof Dhcp6RelayOption)
-                    .map(BasePacket::getPayload)
-                    .map(pld -> (DHCP6) pld)
-                    .findFirst()
-                    .orElse(null);
-
-
         // add host or route
         addHostOrRoute(directConnFlag, clientConnectionPoint, dhcp6Relay, embeddedDhcp6, clientMac, clientInterface);
 
@@ -1286,23 +929,9 @@
         udpPacket.resetChecksum();
         ipv6Packet.setPayload(udpPacket);
         etherReply.setPayload(ipv6Packet);
-
-        return new InternalPacket(etherReply, clientConnectionPoint);
+        return new Dhcp6HandlerUtil().new InternalPacket(etherReply, clientConnectionPoint);
     }
 
-    // Returns the first v6 interface ip out of a set of interfaces or null.
-    // Checks all interfaces, and ignores v6 interface ips
-    private Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) {
-        for (Interface intf : intfs) {
-            for (InterfaceIpAddress ip : intf.ipAddressesList()) {
-                Ip6Address relayAgentIp = ip.ipAddress().getIp6Address();
-                if (relayAgentIp != null) {
-                    return relayAgentIp;
-                }
-            }
-        }
-        return null;
-    }
 
     @Override
     public void setDhcpFpmEnabled(Boolean enabled) {
@@ -1311,33 +940,39 @@
 
     @Override
     public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+        log.debug("setDefaultDhcpServerConfigs is called.");
+
         setDhcpServerConfigs(configs, defaultServerInfoList);
     }
 
     @Override
     public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+        log.debug("setIndirectDhcpServerConfigs is called.");
+
         setDhcpServerConfigs(configs, indirectServerInfoList);
     }
 
     public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
+        log.debug("config size {}.", configs.size());
+
         if (configs.size() == 0) {
             // no config to update
             return;
         }
-
         // TODO: currently we pick up first DHCP server config.
         // Will use other server configs in the future for HA.
-        DhcpServerConfig serverConfig = configs.iterator().next();
-
-        if (!serverConfig.getDhcpServerIp6().isPresent()) {
-            // not a DHCPv6 config
-            return;
+        Boolean isConfigValid = false;
+        for (DhcpServerConfig serverConfig : configs) {
+            if (serverConfig.getDhcpServerIp6().isPresent()) {
+                isConfigValid = true;
+                break;
+            }
         }
-
-        if (!serverInfoList.isEmpty()) {
-            // remove old server info
-            DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
-
+        if (!isConfigValid) {
+            log.warn("No IP V6 server address found.");
+            return;  // No IP V6 address found
+        }
+        for (DhcpServerInfo oldServerInfo : serverInfoList) {
             // stop monitoring gateway or server
             oldServerInfo.getDhcpGatewayIp6().ifPresent(gatewayIp -> {
                 hostService.stopMonitoringIp(gatewayIp);
@@ -1347,43 +982,50 @@
                 cancelDhcpPacket(serverIp);
             });
         }
+        serverInfoList.clear();
+        for (DhcpServerConfig serverConfig : configs) {
+            // Create new server info according to the config
+            DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
+                    DhcpServerInfo.Version.DHCP_V6);
+            checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
+                    "Connect point not exists");
+            checkState(newServerInfo.getDhcpServerIp6().isPresent(),
+                    "IP of DHCP server not exists");
 
-        // Create new server info according to the config
-        DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
-                                                          DhcpServerInfo.Version.DHCP_V6);
-        checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
-                   "Connect point not exists");
-        checkState(newServerInfo.getDhcpServerIp6().isPresent(),
-                   "IP of DHCP server not exists");
+            log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
+            log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp6().orElse(null));
 
-        log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
-        log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp6().orElse(null));
+            Ip6Address serverIp = newServerInfo.getDhcpServerIp6().get();
+            Ip6Address ipToProbe;
+            if (newServerInfo.getDhcpGatewayIp6().isPresent()) {
+                ipToProbe = newServerInfo.getDhcpGatewayIp6().get();
+            } else {
+                ipToProbe = newServerInfo.getDhcpServerIp6().orElse(null);
+            }
+            String hostToProbe = newServerInfo.getDhcpGatewayIp6()
+                    .map(ip -> "gateway").orElse("server");
 
-        Ip6Address serverIp = newServerInfo.getDhcpServerIp6().get();
-        Ip6Address ipToProbe;
-        if (newServerInfo.getDhcpGatewayIp6().isPresent()) {
-            ipToProbe = newServerInfo.getDhcpGatewayIp6().get();
-        } else {
-            ipToProbe = newServerInfo.getDhcpServerIp6().orElse(null);
+            log.warn("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+            hostService.startMonitoringIp(ipToProbe);
+
+            Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+            if (!hosts.isEmpty()) {
+                Host host = hosts.iterator().next();
+                newServerInfo.setDhcpConnectVlan(host.vlan());
+                newServerInfo.setDhcpConnectMac(host.mac());
+                log.warn("Host found host {}", host);
+
+            } else {
+                log.warn("No host found host ip {}", ipToProbe);
+            }
+            // Add new server info
+            synchronized (this) {
+                serverInfoList.add(newServerInfo);
+            }
+            if (!hosts.isEmpty()) {
+                requestDhcpPacket(serverIp);
+            }
         }
-        String hostToProbe = newServerInfo.getDhcpGatewayIp6()
-                .map(ip -> "gateway").orElse("server");
-
-        log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
-        hostService.startMonitoringIp(ipToProbe);
-
-        Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
-        if (!hosts.isEmpty()) {
-            Host host = hosts.iterator().next();
-            newServerInfo.setDhcpConnectVlan(host.vlan());
-            newServerInfo.setDhcpConnectMac(host.mac());
-        }
-        // Add new server info
-        synchronized (this) {
-            serverInfoList.clear();
-            serverInfoList.add(0, newServerInfo);
-        }
-        requestDhcpPacket(serverIp);
     }
 
     class InternalHostListener implements HostListener {
@@ -1425,7 +1067,6 @@
             if (targetIp == null) {
                 targetIp = serverIp;
             }
-
             if (targetIp != null) {
                 if (host.ipAddresses().contains(targetIp)) {
                     serverInfo.setDhcpConnectMac(host.mac());
@@ -1459,7 +1100,6 @@
             if (targetIp == null) {
                 targetIp = serverIp;
             }
-
             if (targetIp != null) {
                 if (host.ipAddresses().contains(targetIp)) {
                     serverInfo.setDhcpConnectVlan(null);
@@ -1488,79 +1128,80 @@
     }
 
     /**
+     * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
+     *
+     * @param serverInfo server information
+     * @return newServerInfo if host info can be either found or filled in.
+     */
+    private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
+        DhcpServerInfo newServerInfo = null;
+        MacAddress  dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
+        VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+        ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+
+        if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
+            newServerInfo = serverInfo;
+            log.info("DHCP server {} host info found. ConnectPt{}  Mac {} vlan {}", serverInfo.getDhcpServerIp6(),
+                    dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+        } else {
+            log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp6(),
+                    dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+
+            Ip6Address ipToProbe;
+            if (serverInfo.getDhcpGatewayIp6().isPresent()) {
+                ipToProbe = serverInfo.getDhcpGatewayIp6().get();
+            } else {
+                ipToProbe = serverInfo.getDhcpServerIp6().orElse(null);
+            }
+            String hostToProbe = serverInfo.getDhcpGatewayIp6()
+                    .map(ip -> "gateway").orElse("server");
+
+            log.info("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
+            hostService.startMonitoringIp(ipToProbe);
+
+            Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+            if (!hosts.isEmpty()) {
+                int serverInfoIndex = sererInfoList.indexOf(serverInfo);
+                Host host = hosts.iterator().next();
+                serverInfo.setDhcpConnectVlan(host.vlan());
+                serverInfo.setDhcpConnectMac(host.mac());
+                // replace the serverInfo in the list
+                sererInfoList.set(serverInfoIndex, serverInfo);
+                newServerInfo = serverInfo;
+                log.warn("Dynamically host found host {}", host);
+            } else {
+                log.warn("No host found host ip {} dynamically", ipToProbe);
+            }
+        }
+        return newServerInfo;
+    }
+
+    /**
      * Gets Interface facing to the server for default host.
      *
+     * @param serverInfo server information
      * @return the Interface facing to the server; null if not found
      */
-    private Interface getServerInterface() {
-        DhcpServerInfo serverInfo;
-        ConnectPoint dhcpServerConnectPoint;
-        VlanId dhcpConnectVlan;
+    private Interface getServerInterface(DhcpServerInfo serverInfo) {
+        Interface serverInterface = null;
 
-        if (!defaultServerInfoList.isEmpty()) {
-            serverInfo = defaultServerInfoList.get(0);
-            dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+        ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+        VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+
+        if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
+        serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
+                    .stream()
+                    .filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
+                    .findFirst()
+                    .orElse(null);
         } else {
-            return null;
+            log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
+                    dhcpServerConnectPoint, dhcpConnectVlan);
         }
-        if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
-            log.info("Default DHCP server {} not resolve yet", serverInfo.getDhcpGatewayIp6());
-            return null;
-        }
-        return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
-                .stream()
-                .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
-                .findFirst()
-                .orElse(null);
+
+        return serverInterface;
     }
 
-    /**
-     * Gets Interface facing to the server for indirect hosts.
-     * Use default server Interface if indirect server not configured.
-     *
-     * @return the Interface facing to the server; null if not found
-     */
-    private Interface getIndirectServerInterface() {
-        DhcpServerInfo serverInfo;
-
-        ConnectPoint indirectDhcpServerConnectPoint;
-        VlanId indirectDhcpConnectVlan;
-
-        if (!indirectServerInfoList.isEmpty()) {
-            serverInfo = indirectServerInfoList.get(0);
-            indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
-            indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
-        } else {
-            return getServerInterface();
-        }
-        if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) {
-            log.info("Indirect DHCP server {} not resolve yet", serverInfo.getDhcpGatewayIp6());
-            return null;
-        }
-        return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint)
-                .stream()
-                .filter(iface -> interfaceContainsVlan(iface, indirectDhcpConnectVlan))
-                .findFirst()
-                .orElse(null);
-    }
-
-    /**
-     * Determind if an Interface contains a vlan id.
-     *
-     * @param iface the Interface
-     * @param vlanId the vlan id
-     * @return true if the Interface contains the vlan id
-     */
-    private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
-        if (vlanId.equals(VlanId.NONE)) {
-            // untagged packet, check if vlan untagged or vlan native is not NONE
-            return !iface.vlanUntagged().equals(VlanId.NONE) ||
-                    !iface.vlanNative().equals(VlanId.NONE);
-        }
-        // tagged packet, check if the interface contains the vlan
-        return iface.vlanTagged().contains(vlanId);
-    }
 
     private void requestDhcpPacket(Ip6Address serverIp) {
         requestServerDhcpPacket(serverIp);
@@ -1724,7 +1365,6 @@
             flowObjectiveService.apply(deviceId, fwd);
         });
     }
-
     /**
      * Find first ipaddress for a given Host info i.e.  mac and vlan.
      *
@@ -1732,7 +1372,7 @@
      * @param vlanId  packet's vlan
      * @return next-hop link-local ipaddress for a given host
      */
-    private IpAddress getFirstIpByHost(MacAddress clientMac, VlanId vlanId) {
+    private IpAddress getFirstIpByHost(Boolean directConnFlag, MacAddress clientMac, VlanId vlanId) {
         IpAddress nextHopIp;
         // pick out the first link-local ip address
         HostId gwHostId = HostId.hostId(clientMac, vlanId);
@@ -1741,14 +1381,67 @@
             log.warn("Can't find gateway host for hostId {}", gwHostId);
             return null;
         }
-        nextHopIp = gwHost.ipAddresses()
-                .stream()
-                .filter(IpAddress::isIp6)
-                .filter(IpAddress::isLinkLocal)
-                .map(IpAddress::getIp6Address)
-                .findFirst()
-                .orElse(null);
+        if (directConnFlag) {
+            nextHopIp = gwHost.ipAddresses()
+                    .stream()
+                    .filter(IpAddress::isIp6)
+                    .map(IpAddress::getIp6Address)
+                    .findFirst()
+                    .orElse(null);
+        } else {
+            nextHopIp = gwHost.ipAddresses()
+                    .stream()
+                    .filter(IpAddress::isIp6)
+                    .filter(ip6 -> ip6.isLinkLocal())
+                    .map(IpAddress::getIp6Address)
+                    .findFirst()
+                    .orElse(null);
+        }
         return nextHopIp;
     }
 
+    private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
+        List<DhcpServerInfo> validServerInfo;
+
+        if (directConnFlag || indirectServerInfoList.isEmpty()) {
+            validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
+        } else {
+            validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
+        }
+        return validServerInfo;
+    }
+
+    private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
+        List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
+        DhcpServerInfo  foundServerInfo = null;
+        for (DhcpServerInfo serverInfo : validServerInfoList) {
+            if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
+                foundServerInfo = serverInfo;
+                log.warn("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
+                        inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
+                break;
+            } else {
+                log.warn("Rcv port {} not the same as Server Connect Point {} for {}",
+                        inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
+            }
+        }
+        return foundServerInfo;
+    }
+    /**
+     * Set the dhcp6 lease expiry poll interval value.
+     *
+     * @param val poll interval value in seconds
+     */
+    public void setDhcp6PollInterval(int val) {
+        dhcp6PollInterval = val;
+    }
+
+    /**
+     * Get the dhcp6 lease expiry poll interval value.
+     *
+     * @return poll interval value in seconds
+     */
+    public int getDhcp6PollInterval() {
+        return dhcp6PollInterval;
+    }
 }
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java
new file mode 100644
index 0000000..5eb71b6
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java
@@ -0,0 +1,510 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DHCP6.MsgType;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6Option;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+
+import org.onlab.util.HexString;
+import org.onosproject.dhcprelay.api.DhcpServerInfo;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.DeviceId;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+
+
+public class Dhcp6HandlerUtil {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    // Returns the first v6 interface ip out of a set of interfaces or null.
+    // Checks all interfaces, and ignores v6 interface ips
+    public Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) {
+        for (Interface intf : intfs) {
+            for (InterfaceIpAddress ip : intf.ipAddressesList()) {
+                Ip6Address relayAgentIp = ip.ipAddress().getIp6Address();
+                if (relayAgentIp != null) {
+                    return relayAgentIp;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first interface ip from interface.
+     *
+     * @param iface interface of one connect point
+     * @return the first interface IP; null if not exists an IP address in
+     *         these interfaces
+     */
+    public Ip6Address getFirstIpFromInterface(Interface iface) {
+        checkNotNull(iface, "Interface can't be null");
+        return iface.ipAddressesList().stream()
+                .map(InterfaceIpAddress::ipAddress)
+                .filter(IpAddress::isIp6)
+                .map(IpAddress::getIp6Address)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply.
+     *
+     * @param dhcp6 dhcp6 relay-reply or relay-foward
+     * @return dhcp6Packet dhcp6 packet extracted from relay-message
+     */
+    public DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) {
+
+        // extract the relay message if exist
+        DHCP6 dhcp6Payload = dhcp6.getOptions().stream()
+                .filter(opt -> opt instanceof Dhcp6RelayOption)
+                .map(BasePacket::getPayload)
+                .map(pld -> (DHCP6) pld)
+                .findFirst()
+                .orElse(null);
+        if (dhcp6Payload == null) {
+            // Can't find dhcp payload
+            log.debug("Can't find dhcp6 payload from relay message");
+        } else {
+            log.debug("dhcp6 payload found from relay message {}", dhcp6Payload);
+        }
+        return dhcp6Payload;
+    }
+
+    /**
+     * find the leaf DHCP6 packet from multi-level relay packet.
+     *
+     * @param relayPacket dhcp6 relay packet
+     * @return leafPacket non-relay dhcp6 packet
+     */
+    public DHCP6 getDhcp6Leaf(DHCP6 relayPacket) {
+        DHCP6 dhcp6Parent = relayPacket;
+        DHCP6 dhcp6Child = null;
+
+        log.debug("getDhcp6Leaf entered.");
+        while (dhcp6Parent != null) {
+            dhcp6Child = dhcp6PacketFromRelayPacket(dhcp6Parent);
+            if (dhcp6Child != null) {
+                if (dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
+                        dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+                    log.debug("leaf dhcp6 packet found.");
+                    break;
+                } else {
+                    // found another relay, go for another loop
+                    dhcp6Parent = dhcp6Child;
+                }
+            } else {
+                log.debug("Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
+                break;
+            }
+        }
+        return dhcp6Child;
+    }
+
+    /**
+     * check if DHCP6 relay-reply is reply.
+     *
+     * @param relayPacket dhcp6 relay-reply
+     * @return boolean relay-reply contains ack
+     */
+    public boolean isDhcp6Reply(DHCP6 relayPacket) {
+        DHCP6 leafDhcp6 = getDhcp6Leaf(relayPacket);
+        if (leafDhcp6 != null) {
+            if (leafDhcp6.getMsgType() == DHCP6.MsgType.REPLY.value()) {
+                log.debug("isDhcp6Reply  true.");
+                return true;  // must be directly connected
+            } else {
+                log.debug("isDhcp6Reply false. leaf dhcp6 is not replay. MsgType {}", leafDhcp6.getMsgType());
+            }
+        } else {
+            log.debug("isDhcp6Reply false. Expected dhcp6 within relay pkt but not found.");
+        }
+        log.debug("isDhcp6Reply  false.");
+        return false;
+    }
+
+    /**
+     * check if DHCP6 is release or relay-forward contains release.
+     *
+     * @param dhcp6Payload dhcp6 packet
+     * @return boolean dhcp6 contains release
+     */
+    public boolean isDhcp6Release(DHCP6 dhcp6Payload) {
+        if (dhcp6Payload.getMsgType() ==  DHCP6.MsgType.RELEASE.value()) {
+            log.debug("isDhcp6Release  true.");
+            return true;  // must be directly connected
+        } else {
+            DHCP6 dhcp6Leaf = getDhcp6Leaf(dhcp6Payload);
+            if (dhcp6Leaf != null) {
+                if (dhcp6Leaf.getMsgType() ==  DHCP6.MsgType.RELEASE.value()) {
+                    log.debug("isDhcp6Release  true. indirectlry connected");
+                    return true;
+                } else {
+                    log.debug("leaf dhcp6 is not release. MsgType {}",  dhcp6Leaf.getMsgType());
+                    return false;
+                }
+            } else {
+                log.debug("isDhcp6Release  false. dhcp6 is niether relay nor release.");
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * convert dhcp6 msgType to String.
+     *
+     * @param msgTypeVal msgType byte of dhcp6 packet
+     * @return String string value of dhcp6 msg type
+     */
+    public String getMsgTypeStr(byte msgTypeVal) {
+        MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
+        return DHCP6.MsgType.getMsgTypeStr(msgType);
+    }
+
+    /**
+     * find the string of dhcp6 leaf packets's msg type.
+     *
+     * @param directConnFlag boolean value indicating direct/indirect connection
+     * @param dhcp6Packet dhcp6 packet
+     * @return String string value of dhcp6 leaf packet msg type
+     */
+    public String findLeafMsgType(boolean directConnFlag, DHCP6  dhcp6Packet) {
+        if (directConnFlag) {
+            return getMsgTypeStr(dhcp6Packet.getMsgType());
+        } else {
+            DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
+            if (leafDhcp != null) {
+                return getMsgTypeStr(leafDhcp.getMsgType());
+            } else {
+                return "INVALID"; //DhcpRelayCounters.INVALID_PACKET;
+            }
+        }
+    }
+
+    /**
+     * find the string of dhcp6 leaf packets's msg type.
+     *
+     * @param fromClient indicate from what side a packet is received
+     * @param directConnFlag boolean value indicating direct/indirect connection
+     * @param dhcp6Packet dhcp6 packet
+     * @return String string value of dhcp6 leaf packet msg type
+     */
+    public String findMsgType(boolean fromClient, boolean directConnFlag, DHCP6  dhcp6Packet) {
+        if (fromClient) {
+            return findLeafMsgType(directConnFlag, dhcp6Packet);
+        } else {
+            DHCP6 embeddedDhcp6 = dhcp6Packet.getOptions().stream()
+                    .filter(opt -> opt instanceof Dhcp6RelayOption)
+                    .map(BasePacket::getPayload)
+                    .map(pld -> (DHCP6) pld)
+                    .findFirst()
+                    .orElse(null);
+            if (embeddedDhcp6 != null) {
+                return findLeafMsgType(directConnFlag, embeddedDhcp6);
+            } else {
+                return "INVALID"; //DhcpRelayCounters.INVALID_PACKET;
+            }
+        }
+    }
+
+    /**
+     * Determind if an Interface contains a vlan id.
+     *
+     * @param iface the Interface
+     * @param vlanId the vlan id
+     * @return true if the Interface contains the vlan id
+     */
+    public boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
+        if (vlanId.equals(VlanId.NONE)) {
+            // untagged packet, check if vlan untagged or vlan native is not NONE
+            return !iface.vlanUntagged().equals(VlanId.NONE) ||
+                    !iface.vlanNative().equals(VlanId.NONE);
+        }
+        // tagged packet, check if the interface contains the vlan
+        return iface.vlanTagged().contains(vlanId);
+    }
+
+    /**
+     * the new class the contains Ethernet packet and destination port.
+     */
+    public class InternalPacket {
+        Ethernet packet;
+        ConnectPoint destLocation;
+        public InternalPacket(Ethernet newPacket, ConnectPoint newLocation) {
+            packet = newPacket;
+            destLocation = newLocation;
+        }
+        void setLocation(ConnectPoint newLocation) {
+            destLocation = newLocation;
+        }
+    }
+    /**
+     * Check if the host is directly connected to the network or not.
+     *
+     * @param dhcp6Payload the dhcp6 payload
+     * @return true if the host is directly connected to the network; false otherwise
+     */
+    public boolean directlyConnected(DHCP6 dhcp6Payload) {
+        log.debug("directlyConnected enters");
+
+        if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
+                dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+            log.debug("directlyConnected true. MsgType {}", dhcp6Payload.getMsgType());
+
+            return true;
+        }
+        // Regardless of relay-forward or relay-replay, check if we see another relay message
+        DHCP6 dhcp6Payload2 = dhcp6PacketFromRelayPacket(dhcp6Payload);
+        if (dhcp6Payload2 != null) {
+            if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELAY_FORW.value()) {
+                log.debug("directlyConnected  false. 1st realy-foward, 2nd MsgType {}", dhcp6Payload2.getMsgType());
+                return false;
+            } else {
+                // relay-reply
+                if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+                    log.debug("directlyConnected  true. 2nd MsgType {}", dhcp6Payload2.getMsgType());
+                    return true;  // must be directly connected
+                } else {
+                    log.debug("directlyConnected  false. 1st relay-reply, 2nd relay-reply MsgType {}",
+                            dhcp6Payload2.getMsgType());
+                    return false;  // must be indirectly connected
+                }
+            }
+        } else {
+            log.debug("directlyConnected  true.");
+            return true;
+        }
+    }
+    /**
+     * Check if a given server info has v6 ipaddress.
+     *
+     * @param serverInfo server info to check
+     * @return true if server info has v6 ip address; false otherwise
+     */
+    public boolean isServerIpEmpty(DhcpServerInfo serverInfo) {
+        if (!serverInfo.getDhcpServerIp6().isPresent()) {
+            log.warn("DhcpServerIp not available, use default DhcpServerIp {}",
+                    HexString.toHexString(serverInfo.getDhcpServerIp6().get().toOctets()));
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isConnectMacEmpty(DhcpServerInfo serverInfo, Set<Interface> clientInterfaces) {
+        if (!serverInfo.getDhcpConnectMac().isPresent()) {
+            log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
+                            + "packet processing from client on port: {}",
+                    !serverInfo.getDhcpGatewayIp6().isPresent() ? "server IP " + serverInfo.getDhcpServerIp6()
+                            : "gateway IP " + serverInfo.getDhcpGatewayIp6(),
+                    clientInterfaces.iterator().next().connectPoint());
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isRelayAgentIpFromCfgEmpty(DhcpServerInfo serverInfo, DeviceId receivedFromDevice) {
+        if (!serverInfo.getRelayAgentIp6(receivedFromDevice).isPresent()) {
+            log.warn("indirect connection: relayAgentIp NOT availale from config file! Use dynamic.");
+            return true;
+        }
+        return false;
+    }
+
+    private Dhcp6Option getInterfaceIdIdOption(PacketContext context, Ethernet clientPacket) {
+        String inPortString = "-" + context.inPacket().receivedFrom().toString() + ":";
+        Dhcp6Option interfaceId = new Dhcp6Option();
+        interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
+        byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress();
+        byte[] inPortStringBytes = inPortString.getBytes();
+        byte[] vlanIdBytes = new byte[2];
+        vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff);
+        vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff);
+        byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length +
+                inPortStringBytes.length + vlanIdBytes.length];
+        log.debug("Length: interfaceIdBytes  {} clientSoureMacBytes {} inPortStringBytes {} vlan {}",
+                interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length,
+                vlanIdBytes.length);
+
+        System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
+        System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length,
+                inPortStringBytes.length);
+        System.arraycopy(vlanIdBytes, 0, interfaceIdBytes,
+                clientSoureMacBytes.length + inPortStringBytes.length,
+                vlanIdBytes.length);
+        interfaceId.setData(interfaceIdBytes);
+        interfaceId.setLength((short) interfaceIdBytes.length);
+        log.debug("interfaceId write srcMac {} portString {}",
+                HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
+        return interfaceId;
+    }
+
+    private void addDhcp6OptionsFromClient(List<Dhcp6Option> options, byte[] dhcp6PacketByte,
+                                           PacketContext context, Ethernet clientPacket) {
+        Dhcp6Option relayMessage = new Dhcp6Option();
+        relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value());
+        relayMessage.setLength((short) dhcp6PacketByte.length);
+        relayMessage.setData(dhcp6PacketByte);
+        options.add(relayMessage);
+        // create interfaceId option
+        Dhcp6Option interfaceId = getInterfaceIdIdOption(context, clientPacket);
+        options.add(interfaceId);
+    }
+
+    /**
+     * build the DHCP6 solicit/request packet with gatewayip.
+     *
+     * @param context packet context
+     * @param clientPacket client ethernet packet
+     * @param clientInterfaces set of client side interfaces
+     * @param serverInfo target server which a packet is generated for
+     * @param serverInterface target server interface
+     * @return ethernet packet with dhcp6 packet info
+     */
+    public Ethernet buildDhcp6PacketFromClient(PacketContext context, Ethernet clientPacket,
+                                               Set<Interface> clientInterfaces, DhcpServerInfo serverInfo,
+                                               Interface serverInterface) {
+        ConnectPoint receivedFrom = context.inPacket().receivedFrom();
+        DeviceId receivedFromDevice = receivedFrom.deviceId();
+
+        Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces);
+        MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+        if (relayAgentIp == null || relayAgentMac == null) {
+            log.warn("Missing DHCP relay agent interface Ipv6 addr config for "
+                            + "packet from client on port: {}. Aborting packet processing",
+                    clientInterfaces.iterator().next().connectPoint());
+            return null;
+        }
+        IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
+        UDP clientUdp = (UDP) clientIpv6.getPayload();
+        DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
+        boolean directConnFlag = directlyConnected(clientDhcp6);
+
+        Ip6Address serverIpFacing = getFirstIpFromInterface(serverInterface);
+        if (serverIpFacing == null || serverInterface.mac() == null) {
+            log.warn("No IP v6 address for server Interface {}", serverInterface);
+            return null;
+        }
+
+        Ethernet etherReply = clientPacket.duplicate();
+        etherReply.setSourceMACAddress(serverInterface.mac());
+
+        // set default info and replace with indirect if available later on.
+        if (serverInfo.getDhcpConnectMac().isPresent()) {
+            etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
+        }
+        if (serverInfo.getDhcpConnectVlan().isPresent()) {
+            etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
+        }
+        IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
+        byte[] peerAddress = clientIpv6.getSourceAddress();
+        ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
+        ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
+        UDP udpPacket = (UDP) ipv6Packet.getPayload();
+        udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+        DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
+        byte[] dhcp6PacketByte = dhcp6Packet.serialize();
+
+        DHCP6 dhcp6Relay = new DHCP6();
+
+        dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
+
+        if (directConnFlag) {
+            dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
+        } else {
+            if (isServerIpEmpty(serverInfo)) {
+                log.warn("indirect DhcpServerIp empty... use default server ");
+            } else {
+                // Indirect case, replace destination to indirect dhcp server if exist
+                // Check if mac is obtained for valid server ip
+                if (isConnectMacEmpty(serverInfo, clientInterfaces)) {
+                    log.warn("indirect Dhcp ConnectMac empty ...");
+                    return null;
+                }
+                etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
+                etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
+                ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
+            }
+            if (isRelayAgentIpFromCfgEmpty(serverInfo, receivedFromDevice)) {
+                dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
+                log.debug("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}",
+                        HexString.toHexString(relayAgentIp.toOctets(), ":"));
+            } else {
+                dhcp6Relay.setLinkAddress(serverInfo.getRelayAgentIp6(receivedFromDevice).get().toOctets());
+            }
+        }
+        // peer address: address of the client or relay agent from which the message to be relayed was received.
+        dhcp6Relay.setPeerAddress(peerAddress);
+        // directly connected case, hop count is zero; otherwise, hop count + 1
+        if (directConnFlag) {
+            dhcp6Relay.setHopCount((byte) 0);
+        } else {
+            dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
+        }
+
+        List<Dhcp6Option> options = new ArrayList<>();
+        addDhcp6OptionsFromClient(options, dhcp6PacketByte, context, clientPacket);
+        dhcp6Relay.setOptions(options);
+        udpPacket.setPayload(dhcp6Relay);
+        udpPacket.resetChecksum();
+        ipv6Packet.setPayload(udpPacket);
+        ipv6Packet.setHopLimit((byte) 64);
+        etherReply.setPayload(ipv6Packet);
+
+        return etherReply;
+    }
+
+    /**
+     * build the DHCP6 solicit/request packet with gatewayip.
+     *
+     * @param directConnFlag flag indicating if packet is from direct client or not
+     * @param serverInfo server to check its connect point
+     * @return boolean true if serverInfo is found; false otherwise
+     */
+    public boolean checkDhcpServerConnPt(boolean directConnFlag,
+                                          DhcpServerInfo serverInfo) {
+        if (serverInfo.getDhcpServerConnectPoint() == null) {
+            log.warn("DHCP6 server connect point for {} connPt {}",
+                    directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 74e1379..018c450 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -54,6 +54,8 @@
 import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
 import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
 import org.onlab.packet.dhcp.Dhcp6Option;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6Duid;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.TestApplicationId;
 import org.onosproject.cfg.ComponentConfigService;
@@ -358,6 +360,7 @@
         // properties
         Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
         expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();
+        expect(dictionary.get("dhcpPollInterval")).andReturn(120).anyTimes();
         ComponentContext context = createNiceMock(ComponentContext.class);
         expect(context.getProperties()).andReturn(dictionary).anyTimes();
 
@@ -1233,6 +1236,14 @@
         iaAddressOption.setValidLifetime(1200);
         iaAddressOption.setLength((short) Dhcp6IaAddressOption.DEFAULT_LEN);
 
+        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+        Dhcp6Duid dhcp6Duip = new Dhcp6Duid();
+        dhcp6Duip.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+        dhcp6Duip.setHardwareType((short) 0x01);   // Ethernet
+        dhcp6Duip.setDuidTime(1234);
+        dhcp6Duip.setLinkLayerAddress(CLIENT_MAC.toBytes());
+        clientIdOption.setDuid(dhcp6Duip);
+
         Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
         iaNaOption.setCode(DHCP6.OptionCode.IA_NA.value());
         iaNaOption.setIaId(0);
@@ -1265,6 +1276,7 @@
         dhcp6.setMsgType(msgType);
         List<Dhcp6Option> dhcp6Options = new ArrayList<Dhcp6Option>();
         dhcp6Options.add(iaNaOption);
+        dhcp6Options.add(clientIdOption);
         dhcp6Options.add(iaPdOption);
         dhcp6.setOptions(dhcp6Options);
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
index 25ef6a3..8dbd1dc 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -111,6 +111,38 @@
                     return null;
             }
         }
+        public static String  getMsgTypeStr(final MsgType msgType) {
+            switch (msgType) {
+                case SOLICIT:
+                    return "SOLICIT";
+                case ADVERTISE:
+                    return "ADVERTISE";
+                case REQUEST:
+                    return "REQUEST";
+                case CONFIRM:
+                    return "CONFIRM";
+                case RENEW:
+                    return "RENEW";
+                case REBIND:
+                    return "REBIND";
+                case REPLY:
+                    return "REPLY";
+                case RELEASE:
+                    return "RELEASE";
+                case DECLINE:
+                    return "DECLINE";
+                case RECONFIGURE:
+                    return "RECONFIGURE";
+                case INFORMATION_REQUEST:
+                    return "INFORMATION_REQUEST";
+                case RELAY_FORW:
+                    return "RELAY_FORW";
+                case RELAY_REPL:
+                    return "RELAY_REPL";
+                default:
+                    return "NULL";
+            }
+        }
     }
 
     /**