[CORD-1614] Refactor DHCP relay app

Change-Id: Id4a281526aa5469abe5732e5d2d42d34074907e9
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
new file mode 100644
index 0000000..9abe49e
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -0,0 +1,670 @@
+/*
+ * 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 com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.CircuitId;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+import org.onosproject.dhcprelay.api.DhcpHandler;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
+import static org.onlab.packet.MacAddress.valueOf;
+import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
+
+@Component
+@Service
+@Property(name = "version", value = "4")
+public class Dhcp4HandlerImpl implements DhcpHandler {
+    private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DhcpRelayStore dhcpRelayStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostStore hostStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RouteStore routeStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private Ip4Address dhcpServerIp = null;
+    // dhcp server may be connected directly to the SDN network or
+    // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
+    // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
+    // to the gateway.
+    private ConnectPoint dhcpServerConnectPoint = null;
+    private MacAddress dhcpConnectMac = null;
+    private VlanId dhcpConnectVlan = null;
+    private Ip4Address dhcpGatewayIp = null;
+
+    @Override
+    public void setDhcpServerIp(IpAddress dhcpServerIp) {
+        checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
+        checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
+        this.dhcpServerIp = dhcpServerIp.getIp4Address();
+    }
+
+    @Override
+    public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
+        checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
+        this.dhcpServerConnectPoint = dhcpServerConnectPoint;
+    }
+
+    @Override
+    public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
+        this.dhcpConnectMac = dhcpConnectMac;
+    }
+
+    @Override
+    public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
+        this.dhcpConnectVlan = dhcpConnectVlan;
+    }
+
+    @Override
+    public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
+        if (dhcpGatewayIp != null) {
+            checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
+            this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
+        } else {
+            // removes gateway config
+            this.dhcpGatewayIp = null;
+        }
+    }
+
+    @Override
+    public Optional<IpAddress> getDhcpServerIp() {
+        return Optional.ofNullable(dhcpServerIp);
+    }
+
+    @Override
+    public Optional<IpAddress> getDhcpGatewayIp() {
+        return Optional.ofNullable(dhcpGatewayIp);
+    }
+
+    @Override
+    public Optional<MacAddress> getDhcpConnectMac() {
+        return Optional.ofNullable(dhcpConnectMac);
+    }
+
+    @Override
+    public void processDhcpPacket(PacketContext context, BasePacket payload) {
+        checkNotNull(payload, "DHCP payload can't be null");
+        checkState(payload instanceof DHCP, "Payload is not a DHCP");
+        DHCP dhcpPayload = (DHCP) payload;
+        if (!configured()) {
+            log.warn("Missing DHCP relay server config. Abort packet processing");
+            return;
+        }
+
+        ConnectPoint inPort = context.inPacket().receivedFrom();
+        Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
+        // ignore the packets if dhcp client interface is not configured on onos.
+        if (clientServerInterfaces.isEmpty()) {
+            log.warn("Virtual interface is not configured on {}", inPort);
+            return;
+        }
+        checkNotNull(dhcpPayload, "Can't find DHCP payload");
+        Ethernet packet = context.inPacket().parsed();
+        DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
+                .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
+                .map(DhcpOption::getData)
+                .map(data -> DHCP.MsgType.getType(data[0]))
+                .findFirst()
+                .orElse(null);
+        checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
+        switch (incomingPacketType) {
+            case DHCPDISCOVER:
+                // add the gatewayip as virtual interface ip for server to understand
+                // the lease to be assigned and forward the packet to dhcp server.
+                Ethernet ethernetPacketDiscover =
+                        processDhcpPacketFromClient(context, packet, clientServerInterfaces);
+
+                if (ethernetPacketDiscover != null) {
+                    writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+                    handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
+                }
+                break;
+            case DHCPOFFER:
+                //reply to dhcp client.
+                Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
+                if (ethernetPacketOffer != null) {
+                    writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
+                    handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
+                }
+                break;
+            case DHCPREQUEST:
+                // add the gateway ip as virtual interface ip for server to understand
+                // the lease to be assigned and forward the packet to dhcp server.
+                Ethernet ethernetPacketRequest =
+                        processDhcpPacketFromClient(context, packet, clientServerInterfaces);
+                if (ethernetPacketRequest != null) {
+                    writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+                    handleDhcpDiscoverAndRequest(ethernetPacketRequest);
+                }
+                break;
+            case DHCPACK:
+                // reply to dhcp client.
+                Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
+                if (ethernetPacketAck != null) {
+                    writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
+                    handleDhcpAck(ethernetPacketAck, dhcpPayload);
+                }
+                break;
+            case DHCPRELEASE:
+                // TODO: release the ip address from client
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Checks if this app has been configured.
+     *
+     * @return true if all information we need have been initialized
+     */
+    public boolean configured() {
+        return dhcpServerConnectPoint != null && dhcpServerIp != null;
+    }
+
+    /**
+     * Returns the first interface ip out of a set of interfaces or null.
+     *
+     * @param intfs interfaces of one connect port
+     * @return the first interface IP; null if not exists an IP address in
+     *         these interfaces
+     */
+    private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
+        return intfs.stream()
+                .map(Interface::ipAddressesList)
+                .flatMap(List::stream)
+                .map(InterfaceIpAddress::ipAddress)
+                .filter(IpAddress::isIp4)
+                .map(IpAddress::getIp4Address)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * Build the DHCP discover/request packet with gateway IP(unicast packet).
+     *
+     * @param context the packet context
+     * @param ethernetPacket the ethernet payload to process
+     * @param clientInterfaces interfaces which belongs to input port
+     * @return processed packet
+     */
+    private Ethernet processDhcpPacketFromClient(PacketContext context,
+                                                 Ethernet ethernetPacket,
+                                                 Set<Interface> clientInterfaces) {
+        Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
+        MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+        if (relayAgentIp == null || relayAgentMac == null) {
+            log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
+                             + "packet from client on port: {}. Aborting packet processing",
+                     clientInterfaces.iterator().next().connectPoint());
+            return null;
+        }
+        if (dhcpConnectMac == null) {
+            log.warn("DHCP {} 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;
+        }
+        // get dhcp header.
+        Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+        etherReply.setSourceMACAddress(relayAgentMac);
+        etherReply.setDestinationMACAddress(dhcpConnectMac);
+        etherReply.setVlanID(dhcpConnectVlan.toShort());
+        IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+        ipv4Packet.setSourceAddress(relayAgentIp.toInt());
+        ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+        // If there is no relay agent option(option 82), add one to DHCP payload
+        boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
+                .map(DhcpOption::getCode)
+                .anyMatch(code -> code == OptionCode_CircuitID.getValue());
+
+        if (!containsRelayAgentOption) {
+            ConnectPoint inPort = context.inPacket().receivedFrom();
+            VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
+            // add connected in port and vlan
+            CircuitId cid = new CircuitId(inPort.toString(), vlanId);
+            byte[] circuitId = cid.serialize();
+            DhcpOption circuitIdSubOpt = new DhcpOption();
+            circuitIdSubOpt
+                    .setCode(CIRCUIT_ID.getValue())
+                    .setLength((byte) circuitId.length)
+                    .setData(circuitId);
+
+            DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
+            newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
+            newRelayAgentOpt.addSubOption(circuitIdSubOpt);
+
+            // Removes END option  first
+            List<DhcpOption> options = dhcpPacket.getOptions().stream()
+                    .filter(opt -> opt.getCode() != OptionCode_END.getValue())
+                    .collect(Collectors.toList());
+
+            // push relay agent option
+            options.add(newRelayAgentOpt);
+
+            // make sure option 255(End) is the last option
+            DhcpOption endOption = new DhcpOption();
+            endOption.setCode(OptionCode_END.getValue());
+            options.add(endOption);
+
+            dhcpPacket.setOptions(options);
+            dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+        }
+
+        udpPacket.setPayload(dhcpPacket);
+        udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
+        udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+        ipv4Packet.setPayload(udpPacket);
+        etherReply.setPayload(ipv4Packet);
+        return etherReply;
+    }
+
+    /**
+     * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
+     *
+     * @param location the location which DHCP packet comes from
+     * @param ethernet the DHCP packet
+     * @param dhcpPayload the DHCP payload
+     */
+    private void writeRequestDhcpRecord(ConnectPoint location,
+                                        Ethernet ethernet,
+                                        DHCP dhcpPayload) {
+        VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
+        MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+        HostId hostId = HostId.hostId(macAddress, vlanId);
+        DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+        if (record == null) {
+            record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+        } else {
+            record = record.clone();
+        }
+        record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+        record.ip4Status(dhcpPayload.getPacketType());
+        record.setDirectlyConnected(directlyConnected(dhcpPayload));
+        if (!directlyConnected(dhcpPayload)) {
+            // Update gateway mac address if the host is not directly connected
+            record.nextHop(ethernet.getSourceMAC());
+        }
+        record.updateLastSeen();
+        dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+    }
+
+    /**
+     * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
+     *
+     * @param ethernet the DHCP packet
+     * @param dhcpPayload the DHCP payload
+     */
+    private void writeResponseDhcpRecord(Ethernet ethernet,
+                                         DHCP dhcpPayload) {
+        Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
+        if (!outInterface.isPresent()) {
+            log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
+            return;
+        }
+
+        Interface outIface = outInterface.get();
+        ConnectPoint location = outIface.connectPoint();
+        VlanId vlanId = outIface.vlan();
+        MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+        HostId hostId = HostId.hostId(macAddress, vlanId);
+        DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+        if (record == null) {
+            record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+        } else {
+            record = record.clone();
+        }
+        record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+        if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
+            record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
+        }
+        record.ip4Status(dhcpPayload.getPacketType());
+        record.setDirectlyConnected(directlyConnected(dhcpPayload));
+        record.updateLastSeen();
+        dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+    }
+
+    /**
+     * Build the DHCP offer/ack with proper client port.
+     *
+     * @param ethernetPacket the original packet comes from server
+     * @return new packet which will send to the client
+     */
+    private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
+        // get dhcp header.
+        Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+        IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
+
+        // determine the vlanId of the client host - note that this vlan id
+        // could be different from the vlan in the packet from the server
+        Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
+
+        if (outInterface == null) {
+            log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
+            return null;
+        }
+
+        etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
+        etherReply.setVlanID(outInterface.vlan().toShort());
+        // we leave the srcMac from the original packet
+
+        // figure out the relay agent IP corresponding to the original request
+        Ip4Address relayAgentIP = getRelayAgentIPv4Address(
+                interfaceService.getInterfacesByPort(outInterface.connectPoint()));
+        if (relayAgentIP == null) {
+            log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
+                             + "Aborting relay for dhcp packet from server {}",
+                     etherReply.getDestinationMAC(), outInterface.vlan(),
+                     ethernetPacket);
+            return null;
+        }
+        // SRC_IP: relay agent IP
+        // DST_IP: offered IP
+        ipv4Packet.setSourceAddress(relayAgentIP.toInt());
+        ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
+        udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+        if (directlyConnected(dhcpPayload)) {
+            udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+        } else {
+            // forward to another dhcp relay
+            udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+        }
+
+        udpPacket.setPayload(dhcpPayload);
+        ipv4Packet.setPayload(udpPacket);
+        etherReply.setPayload(ipv4Packet);
+        return etherReply;
+    }
+
+
+    /**
+     * Check if the host is directly connected to the network or not.
+     *
+     * @param dhcpPayload the dhcp payload
+     * @return true if the host is directly connected to the network; false otherwise
+     */
+    private boolean directlyConnected(DHCP dhcpPayload) {
+        DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
+
+        // Doesn't contains relay option
+        if (relayAgentOption == null) {
+            return true;
+        }
+
+        IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
+        Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
+
+        // Contains relay option, and added by ONOS
+        if (!gatewayInterfaces.isEmpty()) {
+            return true;
+        }
+
+        // Relay option added by other relay agent
+        return false;
+    }
+
+
+    /**
+     * Send the DHCP ack to the requester host.
+     * Modify Host or Route store according to the type of DHCP.
+     *
+     * @param ethernetPacketAck the packet
+     * @param dhcpPayload the DHCP data
+     */
+    private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
+        Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
+        if (!outInterface.isPresent()) {
+            log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
+            return;
+        }
+
+        Interface outIface = outInterface.get();
+        HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
+        MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+        VlanId vlanId = outIface.vlan();
+        HostId hostId = HostId.hostId(macAddress, vlanId);
+        Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
+
+        if (directlyConnected(dhcpPayload)) {
+            // Add to host store if it connect to network directly
+            Set<IpAddress> ips = Sets.newHashSet(ip);
+            HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
+                                                              hostLocation, ips);
+
+            // Replace the ip when dhcp server give the host new ip address
+            hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
+        } else {
+            // Add to route store if it does not connect to network directly
+            // Get gateway host IP according to host mac address
+            DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+
+            if (record == null) {
+                log.warn("Can't find DHCP record of host {}", hostId);
+                return;
+            }
+
+            MacAddress gwMac = record.nextHop().orElse(null);
+            if (gwMac == null) {
+                log.warn("Can't find gateway mac address from record {}", record);
+                return;
+            }
+
+            HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
+            Host gwHost = hostService.getHost(gwHostId);
+
+            if (gwHost == null) {
+                log.warn("Can't find gateway host {}", gwHostId);
+                return;
+            }
+
+            Ip4Address nextHopIp = gwHost.ipAddresses()
+                    .stream()
+                    .filter(IpAddress::isIp4)
+                    .map(IpAddress::getIp4Address)
+                    .findFirst()
+                    .orElse(null);
+
+            if (nextHopIp == null) {
+                log.warn("Can't find IP address of gateway {}", gwHost);
+                return;
+            }
+
+            Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
+            routeStore.updateRoute(route);
+        }
+        sendResponseToClient(ethernetPacketAck, dhcpPayload);
+    }
+
+    /**
+     * forward the packet to ConnectPoint where the DHCP server is attached.
+     *
+     * @param packet the packet
+     */
+    private void handleDhcpDiscoverAndRequest(Ethernet packet) {
+        // send packet to dhcp server connect point.
+        if (dhcpServerConnectPoint != null) {
+            TrafficTreatment t = DefaultTrafficTreatment.builder()
+                    .setOutput(dhcpServerConnectPoint.port()).build();
+            OutboundPacket o = new DefaultOutboundPacket(
+                    dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+            if (log.isTraceEnabled()) {
+                log.trace("Relaying packet to dhcp server {}", packet);
+            }
+            packetService.emit(o);
+        } else {
+            log.warn("Can't find DHCP server connect point, abort.");
+        }
+    }
+
+
+    /**
+     * Gets output interface of a dhcp packet.
+     * If option 82 exists in the dhcp packet and the option was sent by
+     * ONOS (gateway address exists in ONOS interfaces), use the connect
+     * point and vlan id from circuit id; otherwise, find host by destination
+     * address and use vlan id from sender (dhcp server).
+     *
+     * @param ethPacket the ethernet packet
+     * @param dhcpPayload the dhcp packet
+     * @return an interface represent the output port and vlan; empty value
+     *         if the host or circuit id not found
+     */
+    private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
+        VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
+        IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
+        Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
+        DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
+
+        // Sent by ONOS, and contains circuit id
+        if (!gatewayInterfaces.isEmpty() && option != null) {
+            DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
+            try {
+                CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
+                ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
+                VlanId vlanId = circuitId.vlanId();
+                return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
+            } catch (IllegalArgumentException ex) {
+                // invalid circuit format, didn't sent by ONOS
+                log.debug("Invalid circuit {}, use information from dhcp payload",
+                          circuitIdSubOption.getData());
+            }
+        }
+
+        // Use Vlan Id from DHCP server if DHCP relay circuit id was not
+        // sent by ONOS or circuit Id can't be parsed
+        MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
+        Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
+        return dhcpRecord
+                .map(DhcpRecord::locations)
+                .orElse(Collections.emptySet())
+                .stream()
+                .reduce((hl1, hl2) -> {
+                    if (hl1 == null || hl2 == null) {
+                        return hl1 == null ? hl2 : hl1;
+                    }
+                    return hl1.time() > hl2.time() ? hl1 : hl2;
+                })
+                .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
+    }
+
+    /**
+     * Handles DHCP offer packet.
+     *
+     * @param ethPacket the packet
+     * @param dhcpPayload the DHCP data
+     */
+    private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
+        // TODO: removes option 82 if necessary
+        sendResponseToClient(ethPacket, dhcpPayload);
+    }
+
+    /**
+     * Send the response DHCP to the requester host.
+     *
+     * @param ethPacket the packet
+     * @param dhcpPayload the DHCP data
+     */
+    private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
+        Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
+        outInterface.ifPresent(theInterface -> {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(theInterface.connectPoint().port())
+                    .build();
+            OutboundPacket o = new DefaultOutboundPacket(
+                    theInterface.connectPoint().deviceId(),
+                    treatment,
+                    ByteBuffer.wrap(ethPacket.serialize()));
+            if (log.isTraceEnabled()) {
+                log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
+                          ethPacket,
+                          theInterface.connectPoint(),
+                          theInterface.vlan());
+            }
+            packetService.emit(o);
+        });
+    }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
new file mode 100644
index 0000000..f5375bf
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.dhcprelay.api.DhcpHandler;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.PacketContext;
+
+import java.util.Optional;
+
+@Component
+@Service
+@Property(name = "version", value = "6")
+public class Dhcp6HandlerImpl implements DhcpHandler {
+
+    @Override
+    public void processDhcpPacket(PacketContext context, BasePacket dhcp6Payload) {
+
+    }
+
+    @Override
+    public Optional<IpAddress> getDhcpServerIp() {
+        return null;
+    }
+
+    @Override
+    public Optional<IpAddress> getDhcpGatewayIp() {
+        return null;
+    }
+
+    @Override
+    public Optional<MacAddress> getDhcpConnectMac() {
+        return null;
+    }
+
+    @Override
+    public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
+
+    }
+
+    @Override
+    public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
+
+    }
+
+    @Override
+    public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
+
+    }
+
+    @Override
+    public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
+
+    }
+
+    @Override
+    public void setDhcpServerIp(IpAddress dhcpServerIp) {
+
+    }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayConfig.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayConfig.java
index 11a5d7a..0ed8411 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayConfig.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayConfig.java
@@ -33,7 +33,6 @@
 
     @Override
     public boolean isValid() {
-
         return hasOnlyFields(DHCP_CONNECT_POINT, DHCP_SERVER_IP, DHCP_GATEWAY_IP) &&
                 isConnectPoint(DHCP_CONNECT_POINT, MANDATORY) &&
                 isIpAddress(DHCP_SERVER_IP, MANDATORY) &&
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
index de682de..a310a96 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -17,15 +17,12 @@
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Dictionary;
-import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
-import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -39,31 +36,26 @@
 import org.onlab.packet.DHCP6;
 import org.onlab.packet.IPacket;
 import org.onlab.packet.IPv6;
-import org.onlab.packet.dhcp.DhcpOption;
-import org.onlab.packet.dhcp.CircuitId;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
-import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
-import org.onlab.packet.dhcp.DhcpRelayAgentOption;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.dhcprelay.api.DhcpHandler;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.incubator.net.routing.Route;
-import org.onosproject.incubator.net.routing.RouteStore;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
-import org.onosproject.net.HostLocation;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -72,13 +64,9 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.DefaultHostDescription;
-import org.onosproject.net.host.HostDescription;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
-import org.onosproject.net.host.HostStore;
-import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.packet.PacketContext;
@@ -92,12 +80,7 @@
 
 import com.google.common.collect.ImmutableSet;
 
-import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
 import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
-import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
-import static org.onlab.packet.MacAddress.valueOf;
 /**
  * DHCP Relay Agent Application Component.
  */
@@ -105,7 +88,7 @@
 @Service
 public class DhcpRelayManager implements DhcpRelayService {
     public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
-    protected static final ProviderId PROVIDER_ID = new ProviderId("dhcp", DHCP_RELAY_APP);
+    public static final ProviderId PROVIDER_ID = new ProviderId("host", DHCP_RELAY_APP);
     public static final String HOST_LOCATION_PROVIDER =
             "org.onosproject.provider.host.impl.HostLocationProvider";
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -135,12 +118,6 @@
     protected HostService hostService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostStore hostStore;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected RouteStore routeStore;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected InterfaceService interfaceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -149,21 +126,20 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService compCfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
+               target = "(version=4)")
+    protected DhcpHandler v4Handler;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
+               target = "(version=6)")
+    protected DhcpHandler v6Handler;
+
     @Property(name = "arpEnabled", boolValue = true,
-            label = "Enable Address resolution protocol")
+             label = "Enable Address resolution protocol")
     protected boolean arpEnabled = true;
 
     private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
     private InternalHostListener hostListener = new InternalHostListener();
-    private Ip4Address dhcpServerIp = null;
-    // dhcp server may be connected directly to the SDN network or
-    // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
-    // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
-    // to the gateway.
-    private ConnectPoint dhcpServerConnectPoint = null;
-    private MacAddress dhcpConnectMac = null;
-    private VlanId dhcpConnectVlan = null;
-    private Ip4Address dhcpGatewayIp = null;
     private ApplicationId appId;
 
     @Activate
@@ -175,8 +151,11 @@
         factories.forEach(cfgService::registerConfigFactory);
         //update the dhcp server configuration.
         updateConfig();
-        //add the packet services.
+
+        //add the packet processor
         packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
+
+        // listen host event for dhcp server or the gateway
         hostService.addListener(hostListener);
         requestDhcpPackets();
         modified(context);
@@ -196,11 +175,10 @@
         hostService.removeListener(hostListener);
         cancelDhcpPackets();
         cancelArpPackets();
-        if (dhcpGatewayIp != null) {
-            hostService.stopMonitoringIp(dhcpGatewayIp);
-        } else {
-            hostService.stopMonitoringIp(dhcpServerIp);
-        }
+        v4Handler.getDhcpGatewayIp().ifPresent(hostService::stopMonitoringIp);
+        v4Handler.getDhcpServerIp().ifPresent(hostService::stopMonitoringIp);
+        // TODO: DHCPv6 Handler
+
         compCfgService.unregisterProperties(getClass(), true);
         log.info("DHCP-RELAY Stopped");
     }
@@ -224,44 +202,33 @@
         }
     }
 
-    /**
-     * Checks if this app has been configured.
-     *
-     * @return true if all information we need have been initialized
-     */
-    private boolean configured() {
-        return dhcpServerConnectPoint != null && dhcpServerIp != null;
-    }
-
     private void updateConfig() {
         DhcpRelayConfig cfg = cfgService.getConfig(appId, DhcpRelayConfig.class);
         if (cfg == null) {
             log.warn("Dhcp Server info not available");
             return;
         }
+        Optional<IpAddress> oldDhcpServerIp = v4Handler.getDhcpServerIp();
+        Optional<IpAddress> oldDhcpGatewayIp = v4Handler.getDhcpGatewayIp();
+        v4Handler.setDhcpServerConnectPoint(cfg.getDhcpServerConnectPoint());
+        v4Handler.setDhcpServerIp(cfg.getDhcpServerIp());
+        v4Handler.setDhcpGatewayIp(cfg.getDhcpGatewayIp());
+        v4Handler.setDhcpConnectMac(null);
+        v4Handler.setDhcpConnectVlan(null);
 
-        dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
-        Ip4Address oldDhcpServerIp = dhcpServerIp;
-        Ip4Address oldDhcpGatewayIp = dhcpGatewayIp;
-        dhcpServerIp = cfg.getDhcpServerIp();
-        dhcpGatewayIp = cfg.getDhcpGatewayIp();
-        dhcpConnectMac = null; // reset for updated config
-        dhcpConnectVlan = null; // reset for updated config
-        log.info("DHCP server connect point: " + dhcpServerConnectPoint);
-        log.info("DHCP server ipaddress " + dhcpServerIp);
+        log.info("DHCP server connect point: " + cfg.getDhcpServerConnectPoint());
+        log.info("DHCP server ipaddress " + cfg.getDhcpServerIp());
 
-        IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
-        String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
+        IpAddress ipToProbe = v4Handler.getDhcpGatewayIp().isPresent() ? cfg.getDhcpGatewayIp() :
+                                                                         cfg.getDhcpServerIp();
+        String hostToProbe = v4Handler.getDhcpGatewayIp().isPresent() ? "gateway" : "DHCP server";
 
+        // TODO: DHCPv6 server config
         Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
         if (hosts.isEmpty()) {
             log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
-            if (oldDhcpGatewayIp != null) {
-                hostService.stopMonitoringIp(oldDhcpGatewayIp);
-            }
-            if (oldDhcpServerIp != null) {
-                hostService.stopMonitoringIp(oldDhcpServerIp);
-            }
+            oldDhcpGatewayIp.ifPresent(hostService::stopMonitoringIp);
+            oldDhcpServerIp.ifPresent(hostService::stopMonitoringIp);
             hostService.startMonitoringIp(ipToProbe);
         } else {
             // Probe target is known; There should be only 1 host with this ip
@@ -270,30 +237,39 @@
     }
 
     private void hostRemoved(Host host) {
-        if (host.ipAddresses().contains(dhcpServerIp)) {
-            log.warn("DHCP server {} removed", dhcpServerIp);
-            dhcpConnectMac = null;
-            dhcpConnectVlan = null;
-        }
-        if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
-            log.warn("DHCP gateway {} removed", dhcpGatewayIp);
-            dhcpConnectMac = null;
-            dhcpConnectVlan = null;
-        }
+        v4Handler.getDhcpServerIp().ifPresent(ip -> {
+            if (host.ipAddresses().contains(ip)) {
+                log.warn("DHCP server {} removed", ip);
+                v4Handler.setDhcpConnectMac(null);
+                v4Handler.setDhcpConnectVlan(null);
+            }
+        });
+        v4Handler.getDhcpGatewayIp().ifPresent(ip -> {
+            if (host.ipAddresses().contains(ip)) {
+                log.warn("DHCP gateway {} removed", ip);
+                v4Handler.setDhcpConnectMac(null);
+                v4Handler.setDhcpConnectVlan(null);
+            }
+        });
+        // TODO: v6 handler
     }
 
     private void hostUpdated(Host host) {
-        if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
-            dhcpConnectMac = host.mac();
-            dhcpConnectVlan = host.vlan();
-            log.info("DHCP gateway {} resolved to Mac/Vlan:{}/{}", dhcpGatewayIp,
-                     dhcpConnectMac, dhcpConnectVlan);
-        } else if (host.ipAddresses().contains(dhcpServerIp)) {
-            dhcpConnectMac = host.mac();
-            dhcpConnectVlan = host.vlan();
-            log.info("DHCP server {} resolved to Mac/Vlan:{}/{}", dhcpServerIp,
-                    dhcpConnectMac, dhcpConnectVlan);
-        }
+        v4Handler.getDhcpGatewayIp().ifPresent(ip -> {
+            if (host.ipAddresses().contains(ip)) {
+                log.warn("DHCP gateway {} removed", ip);
+                v4Handler.setDhcpConnectMac(host.mac());
+                v4Handler.setDhcpConnectVlan(host.vlan());
+            }
+        });
+        v4Handler.getDhcpServerIp().ifPresent(ip -> {
+            if (host.ipAddresses().contains(ip)) {
+                log.warn("DHCP server {} removed", ip);
+                v4Handler.setDhcpConnectMac(host.mac());
+                v4Handler.setDhcpConnectVlan(host.vlan());
+            }
+        });
+        // TODO: v6 handler
     }
 
     /**
@@ -360,174 +336,7 @@
 
     @Override
     public Optional<MacAddress> getDhcpServerMacAddress() {
-        return Optional.ofNullable(dhcpConnectMac);
-    }
-
-    /**
-     * Gets output interface of a dhcp packet.
-     * If option 82 exists in the dhcp packet and the option was sent by
-     * ONOS (gateway address exists in ONOS interfaces), use the connect
-     * point and vlan id from circuit id; otherwise, find host by destination
-     * address and use vlan id from sender (dhcp server).
-     *
-     * @param ethPacket the ethernet packet
-     * @param dhcpPayload the dhcp packet
-     * @return an interface represent the output port and vlan; empty value
-     *         if the host or circuit id not found
-     */
-    private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
-        VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
-        IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
-        Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
-        DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
-
-        // Sent by ONOS, and contains circuit id
-        if (!gatewayInterfaces.isEmpty() && option != null) {
-            DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
-            try {
-                CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
-                ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
-                VlanId vlanId = circuitId.vlanId();
-                return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
-            } catch (IllegalArgumentException ex) {
-                // invalid circuit format, didn't sent by ONOS
-                log.debug("Invalid circuit {}, use information from dhcp payload",
-                          circuitIdSubOption.getData());
-            }
-        }
-
-        // Use Vlan Id from DHCP server if DHCP relay circuit id was not
-        // sent by ONOS or circuit Id can't be parsed
-        MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
-        Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
-        return dhcpRecord
-                .map(DhcpRecord::locations)
-                .orElse(Collections.emptySet())
-                .stream()
-                .reduce((hl1, hl2) -> {
-                    if (hl1 == null || hl2 == null) {
-                        return hl1 == null ? hl2 : hl1;
-                    }
-                    return hl1.time() > hl2.time() ? hl1 : hl2;
-                })
-                .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
-    }
-
-    /**
-     * Send the response DHCP to the requester host.
-     *
-     * @param ethPacket the packet
-     * @param dhcpPayload the DHCP data
-     */
-    private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
-        Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
-        outInterface.ifPresent(theInterface -> {
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .setOutput(theInterface.connectPoint().port())
-                    .build();
-            OutboundPacket o = new DefaultOutboundPacket(
-                    theInterface.connectPoint().deviceId(),
-                    treatment,
-                    ByteBuffer.wrap(ethPacket.serialize()));
-            if (log.isTraceEnabled()) {
-                log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
-                          ethPacket,
-                          theInterface.connectPoint(),
-                          theInterface.vlan());
-            }
-            packetService.emit(o);
-        });
-    }
-
-    /**
-     * Send the DHCP ack to the requester host.
-     *
-     * @param ethernetPacketAck the packet
-     * @param dhcpPayload the DHCP data
-     */
-    private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
-        Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
-        if (!outInterface.isPresent()) {
-            log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
-            return;
-        }
-
-        Interface outIface = outInterface.get();
-        HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
-        MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
-        VlanId vlanId = outIface.vlan();
-        HostId hostId = HostId.hostId(macAddress, vlanId);
-        Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
-
-        if (directlyConnected(dhcpPayload)) {
-            // Add to host store if it connect to network directly
-            Set<IpAddress> ips = Sets.newHashSet(ip);
-            HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
-                                                              hostLocation, ips);
-
-            // Replace the ip when dhcp server give the host new ip address
-            hostStore.createOrUpdateHost(PROVIDER_ID, hostId, desc, true);
-        } else {
-            // Add to route store if it does not connect to network directly
-            // Get gateway host IP according to host mac address
-            DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
-
-            if (record == null) {
-                log.warn("Can't find DHCP record of host {}", hostId);
-                return;
-            }
-
-            MacAddress gwMac = record.nextHop().orElse(null);
-            if (gwMac == null) {
-                log.warn("Can't find gateway mac address from record {}", record);
-                return;
-            }
-
-            HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
-            Host gwHost = hostService.getHost(gwHostId);
-
-            if (gwHost == null) {
-                log.warn("Can't find gateway host {}", gwHostId);
-                return;
-            }
-
-            Ip4Address nextHopIp = gwHost.ipAddresses()
-                    .stream()
-                    .filter(IpAddress::isIp4)
-                    .map(IpAddress::getIp4Address)
-                    .findFirst()
-                    .orElse(null);
-
-            if (nextHopIp == null) {
-                log.warn("Can't find IP address of gateway {}", gwHost);
-                return;
-            }
-
-            Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
-            routeStore.updateRoute(route);
-        }
-        handleDhcpOffer(ethernetPacketAck, dhcpPayload);
-    }
-
-    /**
-     * forward the packet to ConnectPoint where the DHCP server is attached.
-     *
-     * @param packet the packet
-     */
-    private void handleDhcpDiscoverAndRequest(Ethernet packet) {
-        // send packet to dhcp server connect point.
-        if (dhcpServerConnectPoint != null) {
-            TrafficTreatment t = DefaultTrafficTreatment.builder()
-                    .setOutput(dhcpServerConnectPoint.port()).build();
-            OutboundPacket o = new DefaultOutboundPacket(
-                    dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
-            if (log.isTraceEnabled()) {
-                log.trace("Relaying packet to dhcp server {}", packet);
-            }
-            packetService.emit(o);
-        } else {
-            log.warn("Can't find DHCP server connect point, abort.");
-        }
+        return v4Handler.getDhcpConnectMac();
     }
 
     /**
@@ -572,41 +381,11 @@
                 .findFirst();
     }
 
-    /**
-     * Check if the host is directly connected to the network or not.
-     *
-     * @param dhcpPayload the dhcp payload
-     * @return true if the host is directly connected to the network; false otherwise
-     */
-    private boolean directlyConnected(DHCP dhcpPayload) {
-        DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
-
-        // Doesn't contains relay option
-        if (relayAgentOption == null) {
-            return true;
-        }
-
-        IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
-        Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
-
-        // Contains relay option, and added by ONOS
-        if (!gatewayInterfaces.isEmpty()) {
-            return true;
-        }
-
-        // Relay option added by other relay agent
-        return false;
-    }
 
     private class DhcpRelayPacketProcessor implements PacketProcessor {
 
         @Override
         public void process(PacketContext context) {
-            if (!configured()) {
-                log.warn("Missing DHCP relay server config. Abort packet processing");
-                return;
-            }
-
             // process the packet and get the payload
             Ethernet packet = context.inPacket().parsed();
             if (packet == null) {
@@ -614,12 +393,11 @@
             }
 
             findDhcp(packet).ifPresent(dhcpPayload -> {
-                processDhcpPacket(context, dhcpPayload);
+                v4Handler.processDhcpPacket(context, dhcpPayload);
             });
 
             findDhcp6(packet).ifPresent(dhcp6Payload -> {
-                // TODO: handle DHCPv6 packet
-                log.warn("DHCPv6 unsupported.");
+                v6Handler.processDhcpPacket(context, dhcp6Payload);
             });
 
             if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
@@ -641,8 +419,7 @@
                         .map(Interface::mac)
                         .filter(mac -> !mac.equals(MacAddress.NONE))
                         .findFirst()
-                        .orElse(null);
-
+                        .orElse(MacAddress.ONOS);
                 if (interfaceMac == null) {
                     // can't find interface mac address
                     return;
@@ -686,263 +463,6 @@
             }
             packetService.emit(o);
         }
-
-        // process the dhcp packet before sending to server
-        private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
-            ConnectPoint inPort = context.inPacket().receivedFrom();
-            Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
-            // ignore the packets if dhcp client interface is not configured on onos.
-            if (clientServerInterfaces.isEmpty()) {
-                log.warn("Virtual interface is not configured on {}", inPort);
-                return;
-            }
-            checkNotNull(dhcpPayload, "Can't find DHCP payload");
-            Ethernet packet = context.inPacket().parsed();
-            DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
-                    .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
-                    .map(DhcpOption::getData)
-                    .map(data -> DHCP.MsgType.getType(data[0]))
-                    .findFirst()
-                    .orElse(null);
-            checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
-            switch (incomingPacketType) {
-                case DHCPDISCOVER:
-                    // add the gatewayip as virtual interface ip for server to understand
-                    // the lease to be assigned and forward the packet to dhcp server.
-                    Ethernet ethernetPacketDiscover =
-                            processDhcpPacketFromClient(context, packet, clientServerInterfaces);
-
-                    if (ethernetPacketDiscover != null) {
-                        writeRequestDhcpRecord(inPort, packet, dhcpPayload);
-                        handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
-                    }
-                    break;
-                case DHCPOFFER:
-                    //reply to dhcp client.
-                    Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
-                    if (ethernetPacketOffer != null) {
-                        writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
-                        handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
-                    }
-                    break;
-                case DHCPREQUEST:
-                    // add the gateway ip as virtual interface ip for server to understand
-                    // the lease to be assigned and forward the packet to dhcp server.
-                    Ethernet ethernetPacketRequest =
-                            processDhcpPacketFromClient(context, packet, clientServerInterfaces);
-                    if (ethernetPacketRequest != null) {
-                        writeRequestDhcpRecord(inPort, packet, dhcpPayload);
-                        handleDhcpDiscoverAndRequest(ethernetPacketRequest);
-                    }
-                    break;
-                case DHCPACK:
-                    // reply to dhcp client.
-                    Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
-                    if (ethernetPacketAck != null) {
-                        writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
-                        handleDhcpAck(ethernetPacketAck, dhcpPayload);
-                    }
-                    break;
-                case DHCPRELEASE:
-                    // TODO: release the ip address from client
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        private void writeRequestDhcpRecord(ConnectPoint location,
-                                            Ethernet ethernet,
-                                            DHCP dhcpPayload) {
-            VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
-            MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
-            HostId hostId = HostId.hostId(macAddress, vlanId);
-            DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
-            if (record == null) {
-                record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
-            } else {
-                record = record.clone();
-            }
-            record.addLocation(new HostLocation(location, System.currentTimeMillis()));
-            record.ip4Status(dhcpPayload.getPacketType());
-            record.setDirectlyConnected(directlyConnected(dhcpPayload));
-            if (!directlyConnected(dhcpPayload)) {
-                // Update gateway mac address if the host is not directly connected
-                record.nextHop(ethernet.getSourceMAC());
-            }
-            record.updateLastSeen();
-            dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
-        }
-
-        private void writeResponseDhcpRecord(Ethernet ethernet,
-                                             DHCP dhcpPayload) {
-            Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
-            if (!outInterface.isPresent()) {
-                log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
-                return;
-            }
-
-            Interface outIface = outInterface.get();
-            ConnectPoint location = outIface.connectPoint();
-            VlanId vlanId = outIface.vlan();
-            MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
-            HostId hostId = HostId.hostId(macAddress, vlanId);
-            DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
-            if (record == null) {
-                record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
-            } else {
-                record = record.clone();
-            }
-            record.addLocation(new HostLocation(location, System.currentTimeMillis()));
-            if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
-                record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
-            }
-            record.ip4Status(dhcpPayload.getPacketType());
-            record.setDirectlyConnected(directlyConnected(dhcpPayload));
-            record.updateLastSeen();
-            dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
-        }
-
-        //build the DHCP discover/request packet with gatewayip(unicast packet)
-        private Ethernet processDhcpPacketFromClient(PacketContext context,
-                             Ethernet ethernetPacket, Set<Interface> clientInterfaces) {
-            Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
-            MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
-            if (relayAgentIp == null || relayAgentMac == null) {
-                log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
-                        + "packet from client on port: {}. Aborting packet processing",
-                         clientInterfaces.iterator().next().connectPoint());
-                return null;
-            }
-            if (dhcpConnectMac == null) {
-                log.warn("DHCP {} 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;
-            }
-            // get dhcp header.
-            Ethernet etherReply = (Ethernet) ethernetPacket.clone();
-            etherReply.setSourceMACAddress(relayAgentMac);
-            etherReply.setDestinationMACAddress(dhcpConnectMac);
-            etherReply.setVlanID(dhcpConnectVlan.toShort());
-            IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
-            ipv4Packet.setSourceAddress(relayAgentIp.toInt());
-            ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
-            UDP udpPacket = (UDP) ipv4Packet.getPayload();
-            DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
-
-            // If there is no relay agent option(option 82), add one to DHCP payload
-            boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
-                    .map(DhcpOption::getCode)
-                    .anyMatch(code -> code == OptionCode_CircuitID.getValue());
-
-            if (!containsRelayAgentOption) {
-                ConnectPoint inPort = context.inPacket().receivedFrom();
-                VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
-                // add connected in port and vlan
-                CircuitId cid = new CircuitId(inPort.toString(), vlanId);
-                byte[] circuitId = cid.serialize();
-
-                if (circuitId != null) {
-                    DhcpOption circuitIdSubOpt = new DhcpOption();
-                    circuitIdSubOpt
-                            .setCode(CIRCUIT_ID.getValue())
-                            .setLength((byte) circuitId.length)
-                            .setData(circuitId);
-
-                    DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
-                    newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
-                    newRelayAgentOpt.addSubOption(circuitIdSubOpt);
-
-                    // push new circuit id to last
-                    List<DhcpOption> options = dhcpPacket.getOptions();
-                    options.add(newRelayAgentOpt);
-
-                    // make sure option 255(End) is the last option
-                    options.sort((opt1, opt2) -> opt2.getCode() - opt1.getCode());
-                    dhcpPacket.setOptions(options);
-
-                    dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
-                } else {
-                    log.warn("Can't generate circuit id for port {}, vlan {}", inPort, vlanId);
-                }
-            } else {
-                // TODO: if it contains a relay agent information, sets gateway ip
-                // according to the information it provided
-            }
-
-            udpPacket.setPayload(dhcpPacket);
-            udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
-            udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
-            ipv4Packet.setPayload(udpPacket);
-            etherReply.setPayload(ipv4Packet);
-            return etherReply;
-        }
-
-        // Returns the first v4 interface ip out of a set of interfaces or null.
-        // Checks all interfaces, and ignores v6 interface ips
-        private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
-            for (Interface intf : intfs) {
-                for (InterfaceIpAddress ip : intf.ipAddressesList()) {
-                    Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
-                    if (relayAgentIp != null) {
-                        return relayAgentIp;
-                    }
-                }
-            }
-            return null;
-        }
-
-        //build the DHCP offer/ack with proper client port.
-        private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
-            // get dhcp header.
-            Ethernet etherReply = (Ethernet) ethernetPacket.clone();
-            IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
-            UDP udpPacket = (UDP) ipv4Packet.getPayload();
-            DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
-
-            // determine the vlanId of the client host - note that this vlan id
-            // could be different from the vlan in the packet from the server
-            Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
-
-            if (outInterface == null) {
-                log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
-                return null;
-            }
-
-            etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
-            etherReply.setVlanID(outInterface.vlan().toShort());
-            // we leave the srcMac from the original packet
-
-            // figure out the relay agent IP corresponding to the original request
-            Ip4Address relayAgentIP = getRelayAgentIPv4Address(
-                          interfaceService.getInterfacesByPort(outInterface.connectPoint()));
-            if (relayAgentIP == null) {
-                log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
-                        + "Aborting relay for dhcp packet from server {}",
-                         etherReply.getDestinationMAC(), outInterface.vlan(),
-                         ethernetPacket);
-                return null;
-            }
-            // SRC_IP: relay agent IP
-            // DST_IP: offered IP
-            ipv4Packet.setSourceAddress(relayAgentIP.toInt());
-            ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
-            udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
-            if (directlyConnected(dhcpPayload)) {
-                udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
-            } else {
-                // forward to another dhcp relay
-                udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
-            }
-
-            udpPacket.setPayload(dhcpPayload);
-            ipv4Packet.setPayload(udpPacket);
-            etherReply.setPayload(ipv4Packet);
-            return etherReply;
-        }
     }
 
     /**
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
new file mode 100644
index 0000000..d015532
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
@@ -0,0 +1,96 @@
+/*
+ * 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.api;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.PacketContext;
+
+import java.util.Optional;
+
+/**
+ * DHCP relay handler.
+ */
+public interface DhcpHandler {
+    /**
+     * Process the DHCP packet before sending to server or client.
+     *
+     * @param context the packet context
+     * @param dhcpPayload the DHCP payload
+     */
+    void processDhcpPacket(PacketContext context, BasePacket dhcpPayload);
+
+    /**
+     * Gets DHCP server IP.
+     *
+     * @return IP address of DHCP server; empty value if not exist
+     */
+    Optional<IpAddress> getDhcpServerIp();
+
+    /**
+     * Gets DHCP gateway IP.
+     *
+     * @return IP address of DHCP gateway; empty value if not exist
+     */
+    Optional<IpAddress> getDhcpGatewayIp();
+
+    /**
+     * Gets DHCP connect Mac address.
+     *
+     * @return the connect Mac address of server or gateway
+     */
+    Optional<MacAddress> getDhcpConnectMac();
+
+    /**
+     * Sets DHCP gateway IP.
+     *
+     * @param dhcpGatewayIp the DHCP gateway IP
+     */
+    void setDhcpGatewayIp(IpAddress dhcpGatewayIp);
+
+    /**
+     * Sets DHCP connect vlan.
+     *
+     * @param dhcpConnectVlan the DHCP connect vlan
+     */
+    void setDhcpConnectVlan(VlanId dhcpConnectVlan);
+
+    /**
+     * Sets DHCP connect Mac address.
+     *
+     * @param dhcpConnectMac the connect Mac address
+     */
+    void setDhcpConnectMac(MacAddress dhcpConnectMac);
+
+    /**
+     * Sets DHCP server connect point.
+     *
+     * @param dhcpServerConnectPoint the server connect point
+     */
+    void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint);
+
+    /**
+     * Sets DHCP server IP.
+     *
+     * @param dhcpServerIp the DHCP server IP
+     */
+    void setDhcpServerIp(IpAddress dhcpServerIp);
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java
similarity index 96%
rename from apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
rename to apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java
index c4ab2d1..9bad009 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java
@@ -12,9 +12,10 @@
  * 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;
+package org.onosproject.dhcprelay.api;
 
 import org.onlab.packet.MacAddress;
 import org.onosproject.dhcprelay.store.DhcpRecord;
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/package-info.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/package-info.java
new file mode 100644
index 0000000..fa239e8
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * APIs for DHCP relay application.
+ */
+package org.onosproject.dhcprelay.api;
\ No newline at end of file
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
index be4b75c..089b8ba 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
@@ -27,7 +27,7 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.dhcprelay.DhcpRelayConfig;
 import org.onosproject.dhcprelay.DhcpRelayManager;
-import org.onosproject.dhcprelay.DhcpRelayService;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Host;
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 784524a..3b8e08c 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -186,13 +186,20 @@
         mockHostStore = new MockHostStore();
         mockRouteStore = new MockRouteStore();
         mockDhcpRelayStore = new MockDhcpRelayStore();
-
-        manager.hostStore = mockHostStore;
-        manager.routeStore = mockRouteStore;
         manager.dhcpRelayStore = mockDhcpRelayStore;
 
         manager.interfaceService = new MockInterfaceService();
 
+        Dhcp4HandlerImpl v4Handler = new Dhcp4HandlerImpl();
+        v4Handler.dhcpRelayStore = mockDhcpRelayStore;
+        v4Handler.hostService = manager.hostService;
+        v4Handler.hostStore = mockHostStore;
+        v4Handler.interfaceService = manager.interfaceService;
+        v4Handler.packetService = manager.packetService;
+        v4Handler.routeStore = mockRouteStore;
+        manager.v4Handler = v4Handler;
+
+        // TODO: initialize v6 handler.
         // properties
         Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
         expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();