[CORD-1434][CORD-1112] DHCP relay manager

Change-Id: I2e4d8fc8e85ed66b33ac517660ee72a1c0183597
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
index 7190aea..b762e79 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
@@ -28,7 +28,6 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.ARP;
 import org.onlab.packet.DHCP;
-import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -37,6 +36,7 @@
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.util.SharedScheduledExecutors;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
@@ -77,6 +77,7 @@
 import java.util.Date;
 import java.util.Dictionary;
 import java.util.HashSet;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
diff --git a/apps/dhcprelay/BUCK b/apps/dhcprelay/BUCK
index e420f72..4a2b65c 100644
--- a/apps/dhcprelay/BUCK
+++ b/apps/dhcprelay/BUCK
@@ -6,6 +6,8 @@
 
 TEST_DEPS = [
     '//lib:TEST_ADAPTERS',
+    '//core/api:onos-api-tests',
+    '//incubator/api:onos-incubator-api-tests',
 ]
 
 osgi_jar_with_tests (
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
deleted file mode 100644
index 778f778..0000000
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelay.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.dhcprelay;
-
-import java.nio.ByteBuffer;
-import java.util.Dictionary;
-import java.util.Objects;
-import java.util.Set;
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Modified;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onlab.packet.ARP;
-import org.onlab.packet.DHCP;
-import org.onlab.packet.dhcp.DhcpOption;
-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.util.Tools;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.intf.Interface;
-import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.Host;
-import org.onosproject.net.HostId;
-import org.onosproject.net.config.ConfigFactory;
-import org.onosproject.net.config.NetworkConfigEvent;
-import org.onosproject.net.config.NetworkConfigListener;
-import org.onosproject.net.config.NetworkConfigRegistry;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.HostEvent;
-import org.onosproject.net.host.HostListener;
-import org.onosproject.net.host.HostService;
-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.PacketPriority;
-import org.onosproject.net.packet.PacketProcessor;
-import org.onosproject.net.packet.PacketService;
-import org.osgi.service.component.ComponentContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableSet;
-
-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.
- */
-@Component(immediate = true)
-public class DhcpRelay {
-
-    public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private final InternalConfigListener cfgListener = new InternalConfigListener();
-    private static MacAddress myMAC = valueOf("4f:4f:4f:4f:4f:4f");
-
-    private final Set<ConfigFactory> factories = ImmutableSet.of(
-            new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
-                    DhcpRelayConfig.class,
-                    "dhcprelay") {
-                @Override
-                public DhcpRelayConfig createConfig() {
-                    return new DhcpRelayConfig();
-                }
-            }
-    );
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected NetworkConfigRegistry cfgService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CoreService coreService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected PacketService packetService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostService hostService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected InterfaceService interfaceService;
-
-    @Property(name = "arpEnabled", boolValue = true,
-            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
-    protected void activate(ComponentContext context) {
-        //start the dhcp relay agent
-        appId = coreService.registerApplication(DHCP_RELAY_APP);
-
-        cfgService.addListener(cfgListener);
-        factories.forEach(cfgService::registerConfigFactory);
-        //update the dhcp server configuration.
-        updateConfig();
-        //add the packet services.
-        packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
-        hostService.addListener(hostListener);
-        requestDhcpPackets();
-        modified(context);
-
-        log.info("DHCP-RELAY Started");
-    }
-
-    @Deactivate
-    protected void deactivate() {
-        cfgService.removeListener(cfgListener);
-        factories.forEach(cfgService::unregisterConfigFactory);
-        packetService.removeProcessor(dhcpRelayPacketProcessor);
-        hostService.removeListener(hostListener);
-        cancelDhcpPackets();
-        cancelArpPackets();
-        if (dhcpGatewayIp != null) {
-            hostService.stopMonitoringIp(dhcpGatewayIp);
-        } else {
-            hostService.stopMonitoringIp(dhcpServerIp);
-        }
-
-        log.info("DHCP-RELAY Stopped");
-    }
-
-    @Modified
-    protected void modified(ComponentContext context) {
-        Dictionary<?, ?> properties = context.getProperties();
-        Boolean flag;
-
-        flag = Tools.isPropertyEnabled(properties, "arpEnabled");
-        if (flag != null) {
-            arpEnabled = flag;
-            log.info("Address resolution protocol is {}",
-                     arpEnabled ? "enabled" : "disabled");
-        }
-
-        if (arpEnabled) {
-            requestArpPackets();
-        } else {
-            cancelArpPackets();
-        }
-    }
-
-    /**
-     * 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;
-        }
-
-        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);
-
-        IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
-        String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
-
-        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);
-            }
-            hostService.startMonitoringIp(ipToProbe);
-        } else {
-            // Probe target is known; There should be only 1 host with this ip
-            hostUpdated(hosts.iterator().next());
-        }
-    }
-
-    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;
-        }
-    }
-
-    private void hostUpdated(Host host) {
-        if (dhcpGatewayIp != null) {
-            if (host.ipAddresses().contains(dhcpGatewayIp)) {
-                dhcpConnectMac = host.mac();
-                dhcpConnectVlan = host.vlan();
-                log.info("DHCP gateway {} resolved to Mac/Vlan:{}/{}", dhcpGatewayIp,
-                        dhcpConnectMac, dhcpConnectVlan);
-            }
-            return;
-        }
-        if (host.ipAddresses().contains(dhcpServerIp)) {
-            dhcpConnectMac = host.mac();
-            dhcpConnectVlan = host.vlan();
-            log.info("DHCP server {} resolved to Mac/Vlan:{}/{}", dhcpServerIp,
-                    dhcpConnectMac, dhcpConnectVlan);
-        }
-    }
-
-    /**
-     * Request DHCP packet in via PacketService.
-     */
-    private void requestDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
-    }
-
-    /**
-     * Cancel requested DHCP packets in via packet service.
-     */
-    private void cancelDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
-
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
-    }
-
-    /**
-     * Request ARP packet in via PacketService.
-     */
-    private void requestArpPackets() {
-        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP);
-        packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
-    }
-
-    /**
-     * Cancel requested ARP packets in via packet service.
-     */
-    private void cancelArpPackets() {
-        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP);
-        packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
-    }
-
-    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) {
-                return;
-            }
-
-            if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
-                IPv4 ipv4Packet = (IPv4) packet.getPayload();
-
-                if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
-                    UDP udpPacket = (UDP) ipv4Packet.getPayload();
-                    DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
-                    if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
-                        udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
-                        //This packet is dhcp.
-                        processDhcpPacket(context, dhcpPayload);
-                    }
-                }
-            } else if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
-                ARP arpPacket = (ARP) packet.getPayload();
-                Set<Interface> serverInterfaces = interfaceService.
-                        getInterfacesByPort(context.inPacket().receivedFrom());
-                //ignore the packets if dhcp server interface is not configured on onos.
-                if (serverInterfaces.isEmpty()) {
-                    log.warn("server virtual interface not configured");
-                    return;
-                }
-                if ((arpPacket.getOpCode() == ARP.OP_REQUEST) &&
-                        checkArpRequestFrmDhcpServ(serverInterfaces, arpPacket)) {
-                    processArpPacket(context, packet);
-                }
-            }
-        }
-
-        //method to check the arp request is from dhcp server for default-gateway.
-        private boolean checkArpRequestFrmDhcpServ(Set<Interface> serverInterfaces, ARP arpPacket) {
-            if (Objects.equals(serverInterfaces.iterator().next().ipAddressesList().get(0).
-                    ipAddress().getIp4Address(),
-                    Ip4Address.valueOf(arpPacket.getTargetProtocolAddress()))) {
-                return true;
-            }
-            return false;
-        }
-
-        //forward the packet to ConnectPoint where the DHCP server is attached.
-        private void forwardPacket(Ethernet packet) {
-            //send Packetout to dhcp server connectpoint.
-            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);
-            }
-        }
-
-        /**
-         * Processes the ARP Payload and initiates a reply to the client.
-         *
-         * @param context context of the incoming message
-         * @param packet the ethernet payload
-         */
-        private void processArpPacket(PacketContext context, Ethernet packet) {
-            ARP arpPacket = (ARP) packet.getPayload();
-
-            ARP arpReply = (ARP) arpPacket.clone();
-            arpReply.setOpCode(ARP.OP_REPLY);
-
-            arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
-            arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
-            arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
-            arpReply.setSenderHardwareAddress(myMAC.toBytes());
-
-            // Ethernet Frame.
-            Ethernet ethReply = new Ethernet();
-            ethReply.setSourceMACAddress(myMAC);
-            ethReply.setDestinationMACAddress(packet.getSourceMAC());
-            ethReply.setEtherType(Ethernet.TYPE_ARP);
-            ethReply.setVlanID(packet.getVlanID());
-
-            ethReply.setPayload(arpReply);
-            forwardPacket(ethReply);
-        }
-
-        //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;
-            }
-
-            if (dhcpPayload == null) {
-                return;
-            }
-
-            Ethernet packet = context.inPacket().parsed();
-            DHCP.MsgType incomingPacketType = null;
-            for (DhcpOption option : dhcpPayload.getOptions()) {
-                if (option.getCode() == OptionCode_MessageType.getValue()) {
-                    byte[] data = option.getData();
-                    incomingPacketType = DHCP.MsgType.getType(data[0]);
-                }
-            }
-
-            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) {
-                    forwardPacket(ethernetPacketDiscover);
-                }
-                break;
-            case DHCPOFFER:
-                //reply to dhcp client.
-                Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
-                if (ethernetPacketOffer != null) {
-                    sendReply(ethernetPacketOffer, dhcpPayload);
-                }
-                break;
-            case DHCPREQUEST:
-                // add the gatewayip 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) {
-                    forwardPacket(ethernetPacketRequest);
-                }
-                break;
-            case DHCPACK:
-                //reply to dhcp client.
-                Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
-                if (ethernetPacketAck != null) {
-                    sendReply(ethernetPacketAck, dhcpPayload);
-                }
-                break;
-            default:
-                break;
-            }
-        }
-
-        //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();
-            dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
-            udpPacket.setPayload(dhcpPacket);
-            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
-            MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
-            Set<Host> hosts = hostService.getHostsByMac(dstMac);
-            if (hosts == null || hosts.isEmpty()) {
-                log.warn("Cannot determine host for DHCP client: {}. Aborting "
-                        + "relay for dhcp packet from server {}",
-                         dhcpPayload.getClientHardwareAddress(), ethernetPacket);
-                return null;
-            } else if (hosts.size() > 1) {
-                // XXX  redo to send reply to all hosts found
-                log.warn("Multiple hosts found for mac:{}. Picking one "
-                        + "host out of {}", dstMac, hosts);
-            }
-            Host host = hosts.iterator().next();
-            etherReply.setDestinationMACAddress(dstMac);
-            etherReply.setVlanID(host.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(host.location()));
-            if (relayAgentIP == null) {
-                log.warn("Cannot determine relay agent interface Ipv4 addr for host {}. "
-                        + "Aborting relay for dhcp packet from server {}",
-                        host, ethernetPacket);
-                return null;
-            }
-            // SRC_IP: relay agent IP
-            // DST_IP: offered IP
-            ipv4Packet.setSourceAddress(relayAgentIP.toInt());
-            ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
-
-            udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
-            udpPacket.setPayload(dhcpPayload);
-            ipv4Packet.setPayload(udpPacket);
-            etherReply.setPayload(ipv4Packet);
-            return etherReply;
-        }
-
-        //send the response to the requester host.
-        private void sendReply(Ethernet ethPacket, DHCP dhcpPayload) {
-            MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
-            Host host = hostService.getHost(HostId.hostId(descMac,
-                    VlanId.vlanId(ethPacket.getVlanID())));
-
-            // Send packet out to requester if the host information is available
-            if (host != null) {
-                TrafficTreatment t = DefaultTrafficTreatment.builder()
-                        .setOutput(host.location().port()).build();
-                OutboundPacket o = new DefaultOutboundPacket(
-                        host.location().deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
-                if (log.isTraceEnabled()) {
-                    log.trace("Relaying packet to dhcp client {}", ethPacket);
-                }
-                packetService.emit(o);
-            }
-        }
-    }
-
-    /**
-     * Listener for network config events.
-     */
-    private class InternalConfigListener implements NetworkConfigListener {
-
-        @Override
-        public void event(NetworkConfigEvent event) {
-
-            if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
-                    event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
-                    event.configClass().equals(DhcpRelayConfig.class)) {
-                updateConfig();
-                log.info("Reconfigured");
-            }
-        }
-    }
-
-    /**
-     * Internal listener for host events.
-     */
-    private class InternalHostListener implements HostListener {
-        @Override
-        public void event(HostEvent event) {
-            switch (event.type()) {
-            case HOST_ADDED:
-            case HOST_UPDATED:
-                hostUpdated(event.subject());
-                break;
-            case HOST_REMOVED:
-                hostRemoved(event.subject());
-                break;
-            case HOST_MOVED:
-                // XXX todo -- moving dhcp server
-                break;
-            default:
-                break;
-            }
-        }
-    }
-}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
new file mode 100644
index 0000000..7a0aec8
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -0,0 +1,975 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.dhcprelay;
+
+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;
+import org.apache.felix.scr.annotations.Modified;
+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.ARP;
+import org.onlab.packet.DHCP;
+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.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;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+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;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.
+ */
+@Component(immediate = true)
+@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 String HOST_LOCATION_PROVIDER =
+            "org.onosproject.provider.host.impl.HostLocationProvider";
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final InternalConfigListener cfgListener = new InternalConfigListener();
+
+    private final Set<ConfigFactory> factories = ImmutableSet.of(
+            new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
+                    DhcpRelayConfig.class,
+                    "dhcprelay") {
+                @Override
+                public DhcpRelayConfig createConfig() {
+                    return new DhcpRelayConfig();
+                }
+            }
+    );
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry cfgService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    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)
+    protected DhcpRelayStore dhcpRelayStore;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService compCfgService;
+
+    @Property(name = "arpEnabled", boolValue = true,
+            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
+    protected void activate(ComponentContext context) {
+        //start the dhcp relay agent
+        appId = coreService.registerApplication(DHCP_RELAY_APP);
+
+        cfgService.addListener(cfgListener);
+        factories.forEach(cfgService::registerConfigFactory);
+        //update the dhcp server configuration.
+        updateConfig();
+        //add the packet services.
+        packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
+        hostService.addListener(hostListener);
+        requestDhcpPackets();
+        modified(context);
+
+        // disable dhcp from host location provider
+        compCfgService.preSetProperty(HOST_LOCATION_PROVIDER,
+                                      "useDhcp", Boolean.FALSE.toString());
+        compCfgService.registerProperties(getClass());
+        log.info("DHCP-RELAY Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        cfgService.removeListener(cfgListener);
+        factories.forEach(cfgService::unregisterConfigFactory);
+        packetService.removeProcessor(dhcpRelayPacketProcessor);
+        hostService.removeListener(hostListener);
+        cancelDhcpPackets();
+        cancelArpPackets();
+        if (dhcpGatewayIp != null) {
+            hostService.stopMonitoringIp(dhcpGatewayIp);
+        } else {
+            hostService.stopMonitoringIp(dhcpServerIp);
+        }
+        compCfgService.unregisterProperties(getClass(), true);
+        log.info("DHCP-RELAY Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, "arpEnabled");
+        if (flag != null) {
+            arpEnabled = flag;
+            log.info("Address resolution protocol is {}",
+                     arpEnabled ? "enabled" : "disabled");
+        }
+
+        if (arpEnabled) {
+            requestArpPackets();
+        } else {
+            cancelArpPackets();
+        }
+    }
+
+    /**
+     * 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;
+        }
+
+        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);
+
+        IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
+        String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
+
+        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);
+            }
+            hostService.startMonitoringIp(ipToProbe);
+        } else {
+            // Probe target is known; There should be only 1 host with this ip
+            hostUpdated(hosts.iterator().next());
+        }
+    }
+
+    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;
+        }
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * Request DHCP packet in via PacketService.
+     */
+    private void requestDhcpPackets() {
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
+
+        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+        packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+    }
+
+    /**
+     * Cancel requested DHCP packets in via packet service.
+     */
+    private void cancelDhcpPackets() {
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
+
+        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+        packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
+    }
+
+    /**
+     * Request ARP packet in via PacketService.
+     */
+    private void requestArpPackets() {
+        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP);
+        packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+    }
+
+    /**
+     * Cancel requested ARP packets in via packet service.
+     */
+    private void cancelArpPackets() {
+        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP);
+        packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
+    }
+
+    @Override
+    public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+        return dhcpRelayStore.getDhcpRecord(hostId);
+    }
+
+    @Override
+    public Collection<DhcpRecord> getDhcpRecords() {
+        return dhcpRelayStore.getDhcpRecords();
+    }
+
+    /**
+     * 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.");
+        }
+    }
+
+    /**
+     * Gets DHCP data from a packet.
+     *
+     * @param packet the packet
+     * @return the DHCP data; empty if it is not a DHCP packet
+     */
+    private Optional<DHCP> findDhcp(Ethernet packet) {
+        return Stream.of(packet)
+                .filter(Objects::nonNull)
+                .map(Ethernet::getPayload)
+                .filter(p -> p instanceof IPv4)
+                .map(IPacket::getPayload)
+                .filter(Objects::nonNull)
+                .filter(p -> p instanceof UDP)
+                .map(IPacket::getPayload)
+                .filter(Objects::nonNull)
+                .filter(p -> p instanceof DHCP)
+                .map(p -> (DHCP) p)
+                .findFirst();
+    }
+
+    /**
+     * Gets DHCPv6 data from a packet.
+     *
+     * @param packet the packet
+     * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
+     */
+    private Optional<DHCP6> findDhcp6(Ethernet packet) {
+        return Stream.of(packet)
+                .filter(Objects::nonNull)
+                .map(Ethernet::getPayload)
+                .filter(p -> p instanceof IPv6)
+                .map(IPacket::getPayload)
+                .filter(Objects::nonNull)
+                .filter(p -> p instanceof UDP)
+                .map(IPacket::getPayload)
+                .filter(Objects::nonNull)
+                .filter(p -> p instanceof DHCP6)
+                .map(p -> (DHCP6) p)
+                .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) {
+                return;
+            }
+
+            findDhcp(packet).ifPresent(dhcpPayload -> {
+                processDhcpPacket(context, dhcpPayload);
+            });
+
+            findDhcp6(packet).ifPresent(dhcp6Payload -> {
+                // TODO: handle DHCPv6 packet
+                log.warn("DHCPv6 unsupported.");
+            });
+
+            if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
+                ARP arpPacket = (ARP) packet.getPayload();
+                VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+                Set<Interface> interfaces = interfaceService.
+                        getInterfacesByPort(context.inPacket().receivedFrom());
+                //ignore the packets if dhcp server interface is not configured on onos.
+                if (interfaces.isEmpty()) {
+                    log.warn("server virtual interface not configured");
+                    return;
+                }
+                if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
+                    // handle request only
+                    return;
+                }
+                MacAddress interfaceMac = interfaces.stream()
+                        .filter(iface -> iface.vlan().equals(vlanId))
+                        .map(Interface::mac)
+                        .filter(mac -> !mac.equals(MacAddress.NONE))
+                        .findFirst()
+                        .orElse(null);
+
+                if (interfaceMac == null) {
+                    // can't find interface mac address
+                    return;
+                }
+                processArpPacket(context, packet, interfaceMac);
+            }
+        }
+
+        /**
+         * Processes the ARP Payload and initiates a reply to the client.
+         *
+         * @param context the packet context
+         * @param packet the ethernet payload
+         * @param replyMac mac address to be replied
+         */
+        private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
+            ARP arpPacket = (ARP) packet.getPayload();
+            ARP arpReply = (ARP) arpPacket.clone();
+            arpReply.setOpCode(ARP.OP_REPLY);
+
+            arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
+            arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
+            arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
+            arpReply.setSenderHardwareAddress(replyMac.toBytes());
+
+            // Ethernet Frame.
+            Ethernet ethReply = new Ethernet();
+            ethReply.setSourceMACAddress(replyMac.toBytes());
+            ethReply.setDestinationMACAddress(packet.getSourceMAC());
+            ethReply.setEtherType(Ethernet.TYPE_ARP);
+            ethReply.setVlanID(packet.getVlanID());
+            ethReply.setPayload(arpReply);
+
+            ConnectPoint targetPort = context.inPacket().receivedFrom();
+            TrafficTreatment t = DefaultTrafficTreatment.builder()
+                    .setOutput(targetPort.port()).build();
+            OutboundPacket o = new DefaultOutboundPacket(
+                    targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
+            if (log.isTraceEnabled()) {
+                log.trace("Relaying ARP packet {} to {}", packet, targetPort);
+            }
+            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);
+            outInterface.ifPresent(outIface -> {
+                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.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;
+        }
+    }
+
+    /**
+     * Listener for network config events.
+     */
+    private class InternalConfigListener implements NetworkConfigListener {
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
+                    event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
+                    event.configClass().equals(DhcpRelayConfig.class)) {
+                updateConfig();
+                log.info("Reconfigured");
+            }
+        }
+    }
+
+    /**
+     * Internal listener for host events.
+     */
+    private class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            switch (event.type()) {
+            case HOST_ADDED:
+            case HOST_UPDATED:
+                hostUpdated(event.subject());
+                break;
+            case HOST_REMOVED:
+                hostRemoved(event.subject());
+                break;
+            case HOST_MOVED:
+                // XXX todo -- moving dhcp server
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
new file mode 100644
index 0000000..7ced882
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.dhcprelay;
+
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.net.HostId;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface DhcpRelayService {
+    /**
+     * Gets DHCP record for specific host id (mac + vlan).
+     *
+     * @param hostId the id of host
+     * @return the DHCP record of the host
+     */
+    Optional<DhcpRecord> getDhcpRecord(HostId hostId);
+
+    /**
+     * Gets all DHCP records from store.
+     *
+     * @return all DHCP records from store
+     */
+    Collection<DhcpRecord> getDhcpRecords();
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
index bca3df3..ca1112e 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
@@ -45,7 +45,7 @@
      * @param type the type of event
      * @param subject the DHCP record of this event
      */
-    protected DhcpRelayStoreEvent(Type type, DhcpRecord subject) {
+    public DhcpRelayStoreEvent(Type type, DhcpRecord subject) {
         super(type, subject);
     }
 }
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
index b5f2bf8..e2225f1 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
@@ -21,6 +21,7 @@
 import org.apache.felix.scr.annotations.Deactivate;
 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.DHCP;
 import org.onlab.packet.DHCP6;
 import org.onlab.util.KryoNamespace;
@@ -43,7 +44,8 @@
 /**
  * Distributed DHCP relay store.
  */
-@Component
+@Component(immediate = true)
+@Service
 public class DistributedDhcpRelayStore implements DhcpRelayStore {
     private static final KryoNamespace APP_KRYO = KryoNamespace.newBuilder()
             .register(KryoNamespaces.API)
diff --git a/apps/dhcprelay/src/main/resources/dhcp-relay.json b/apps/dhcprelay/src/main/resources/dhcp-relay.json
index 616c4ab..be8b062 100644
--- a/apps/dhcprelay/src/main/resources/dhcp-relay.json
+++ b/apps/dhcprelay/src/main/resources/dhcp-relay.json
@@ -4,7 +4,7 @@
       "dhcprelay" : {
         "dhcpserverConnectPoint": "of:0000000000000002/2",
         "serverip": "172.168.10.2",
-        "servermac": "d2:70:98:90:8c:44"
+        "gatewayip": "192.168.10.254"
       }
     }
   }
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
new file mode 100644
index 0000000..fe2cbff
--- /dev/null
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.dhcprelay;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ARP;
+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.TestApplicationId;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceServiceAdapter;
+import org.onosproject.incubator.net.routing.Route;
+import org.onosproject.incubator.net.routing.RouteStoreAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostStoreAdapter;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketContextAdapter;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.StoreDelegate;
+import org.osgi.service.component.ComponentContext;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class DhcpRelayManagerTest {
+    // Ip address for interfaces
+    private static final InterfaceIpAddress INTERFACE_IP = InterfaceIpAddress.valueOf("10.0.3.254/32");
+    private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP);
+
+    // DHCP client (will send without option 82)
+    private static final Ip4Address IP_FOR_CLIENT = Ip4Address.valueOf("10.0.0.1");
+    private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
+    private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
+    private static final MacAddress CLIENT_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+    private static final Interface CLIENT_INTERFACE = new Interface("C1",
+                                                                    CLIENT_CP,
+                                                                    INTERFACE_IPS,
+                                                                    CLIENT_IFACE_MAC,
+                                                                    CLIENT_VLAN);
+
+    // DHCP client 2 (will send with option 82, so the vlan should equals to vlan from server)
+    private static final MacAddress CLIENT2_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final VlanId CLIENT2_VLAN = VlanId.NONE;
+    private static final ConnectPoint CLIENT2_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+    private static final MacAddress CLIENT2_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+    private static final Interface CLIENT2_INTERFACE = new Interface("C2",
+                                                                     CLIENT2_CP,
+                                                                     INTERFACE_IPS,
+                                                                     CLIENT2_IFACE_MAC,
+                                                                     CLIENT2_VLAN);
+
+    // Outer relay information
+    private static final Ip4Address OUTER_RELAY_IP = Ip4Address.valueOf("10.0.5.253");
+    private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP);
+    private static final MacAddress OUTER_RELAY_MAC = MacAddress.valueOf("00:01:02:03:04:05");
+    private static final VlanId OUTER_RELAY_VLAN = VlanId.NONE;
+    private static final ConnectPoint OUTER_RELAY_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+    private static final HostLocation OUTER_REPLAY_HL = new HostLocation(OUTER_RELAY_CP, 0);
+    private static final HostId OUTER_RELAY_HOST_ID = HostId.hostId(OUTER_RELAY_MAC, OUTER_RELAY_VLAN);
+    private static final Host OUTER_RELAY_HOST = new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+                                                                 OUTER_RELAY_HOST_ID,
+                                                                 OUTER_RELAY_MAC,
+                                                                 OUTER_RELAY_VLAN,
+                                                                 OUTER_REPLAY_HL,
+                                                                 OUTER_RELAY_IPS);
+
+    // DHCP Server
+    private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final VlanId SERVER_VLAN = VlanId.NONE;
+    private static final ConnectPoint SERVER_CONNECT_POINT =
+            ConnectPoint.deviceConnectPoint("of:0000000000000001/5");
+    private static final HostLocation SERVER_LOCATION =
+            new HostLocation(SERVER_CONNECT_POINT, 0);
+    private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
+    private static final Set<IpAddress> DHCP_SERVER_IPS = ImmutableSet.of(SERVER_IP);
+    private static final HostId SERVER_HOST_ID = HostId.hostId(SERVER_MAC, SERVER_VLAN);
+    private static final Host SERVER_HOST = new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+                                                            SERVER_HOST_ID,
+                                                            SERVER_MAC,
+                                                            SERVER_VLAN,
+                                                            SERVER_LOCATION,
+                                                            DHCP_SERVER_IPS);
+    private static final MacAddress SERVER_IFACE_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final Interface SERVER_INTERFACE = new Interface("SERVER",
+                                                                    SERVER_CONNECT_POINT,
+                                                                    INTERFACE_IPS,
+                                                                    SERVER_IFACE_MAC,
+                                                                    SERVER_VLAN);
+
+    // Components
+    private static final ApplicationId APP_ID = TestApplicationId.create(DhcpRelayManager.DHCP_RELAY_APP);
+    private static final DhcpRelayConfig CONFIG = new MockDhcpRelayConfig();
+    private static final Set<Interface> INTERFACES = ImmutableSet.of(
+            CLIENT_INTERFACE,
+            CLIENT2_INTERFACE,
+            SERVER_INTERFACE
+    );
+
+    private DhcpRelayManager manager;
+    private MockPacketService packetService;
+    private MockHostStore mockHostStore;
+    private MockRouteStore mockRouteStore;
+    private MockDhcpRelayStore mockDhcpRelayStore;
+
+    @Before
+    public void setup() {
+        manager = new DhcpRelayManager();
+        manager.cfgService = createNiceMock(NetworkConfigRegistry.class);
+
+        expect(manager.cfgService.getConfig(anyObject(), anyObject()))
+                .andReturn(CONFIG)
+                .anyTimes();
+
+        manager.coreService = createNiceMock(CoreService.class);
+        expect(manager.coreService.registerApplication(anyString()))
+                .andReturn(APP_ID).anyTimes();
+
+        manager.hostService = createNiceMock(HostService.class);
+        expect(manager.hostService.getHostsByIp(anyObject())).andReturn(ImmutableSet.of(SERVER_HOST));
+        expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST);
+
+        packetService = new MockPacketService();
+        manager.packetService = packetService;
+        manager.compCfgService = createNiceMock(ComponentConfigService.class);
+
+        mockHostStore = new MockHostStore();
+        mockRouteStore = new MockRouteStore();
+        mockDhcpRelayStore = new MockDhcpRelayStore();
+
+        manager.hostStore = mockHostStore;
+        manager.routeStore = mockRouteStore;
+        manager.dhcpRelayStore = mockDhcpRelayStore;
+
+        manager.interfaceService = new MockInterfaceService();
+
+        // properties
+        Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
+        expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();
+        ComponentContext context = createNiceMock(ComponentContext.class);
+        expect(context.getProperties()).andReturn(dictionary).anyTimes();
+
+        EasyMock.replay(manager.cfgService, manager.coreService, manager.hostService,
+                        manager.compCfgService, dictionary, context);
+        manager.activate(context);
+    }
+
+    @After
+    public void tearDown() {
+        manager.deactivate();
+    }
+
+    /**
+     * Relay a DHCP packet without option 82.
+     * Should add new host to host store after dhcp ack.
+     */
+    @Test
+    public void relayDhcpWithoutAgentInfo() {
+        // send request
+        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+                                                                     CLIENT_VLAN,
+                                                                     CLIENT_CP,
+                                                                     INTERFACE_IP.ipAddress().getIp4Address(),
+                                                                     false));
+
+        Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+        assertEquals(0, hosts.size());
+        assertEquals(0, mockRouteStore.routes.size());
+
+        // send ack
+        packetService.processPacket(new TestDhcpAckPacketContext(CLIENT_CP, CLIENT_MAC,
+                                                                 CLIENT_VLAN, INTERFACE_IP.ipAddress().getIp4Address(),
+                                                                 false));
+        hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+        assertEquals(1, hosts.size());
+        assertEquals(0, mockRouteStore.routes.size());
+
+        Host host = hosts.iterator().next();
+        assertEquals(CLIENT_MAC, host.mac());
+        assertEquals(CLIENT_VLAN, host.vlan());
+        assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
+        assertEquals(CLIENT_CP.port(), host.location().port());
+        assertEquals(1, host.ipAddresses().size());
+        assertEquals(IP_FOR_CLIENT, host.ipAddresses().iterator().next());
+        assertEquals(HostId.hostId(CLIENT_MAC, CLIENT_VLAN), host.id());
+    }
+
+    /**
+     * Relay a DHCP packet with option 82 (Indirectly connected host).
+     */
+    @Test
+    public void relayDhcpWithAgentInfo() {
+        // Assume outer dhcp relay agent exists in store already
+        // send request
+        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
+                                                                     CLIENT2_VLAN,
+                                                                     CLIENT2_CP,
+                                                                     INTERFACE_IP.ipAddress().getIp4Address(),
+                                                                     true));
+
+        Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+        assertEquals(0, hosts.size());
+        assertEquals(0, mockRouteStore.routes.size());
+
+        // send ack
+        packetService.processPacket(new TestDhcpAckPacketContext(CLIENT2_CP,
+                                                                 CLIENT2_MAC,
+                                                                 CLIENT2_VLAN,
+                                                                 INTERFACE_IP.ipAddress().getIp4Address(),
+                                                                 true));
+
+        hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+        assertEquals(0, hosts.size());
+        assertEquals(1, mockRouteStore.routes.size());
+
+        Route route = mockRouteStore.routes.get(0);
+        assertEquals(OUTER_RELAY_IP, route.nextHop());
+        assertEquals(IP_FOR_CLIENT.toIpPrefix(), route.prefix());
+        assertEquals(Route.Source.STATIC, route.source());
+    }
+
+    @Test
+    public void testArpRequest() throws Exception {
+        packetService.processPacket(new TestArpRequestPacketContext(CLIENT_INTERFACE));
+        OutboundPacket outboundPacket = packetService.emitedPacket;
+        byte[] outPacketData = outboundPacket.data().array();
+        Ethernet eth = Ethernet.deserializer().deserialize(outPacketData, 0, outPacketData.length);
+
+        assertEquals(eth.getEtherType(), Ethernet.TYPE_ARP);
+        ARP arp = (ARP) eth.getPayload();
+        assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
+    }
+
+    private static class MockDhcpRelayConfig extends DhcpRelayConfig {
+        @Override
+        public boolean isValid() {
+            return true;
+        }
+
+        @Override
+        public ConnectPoint getDhcpServerConnectPoint() {
+            return SERVER_CONNECT_POINT;
+        }
+
+        public Ip4Address getDhcpServerIp() {
+            return SERVER_IP;
+        }
+
+        public Ip4Address getDhcpGatewayIp() {
+            return null;
+        }
+    }
+
+    private class MockHostStore extends HostStoreAdapter {
+
+        private final Map<HostId, HostDescription> hosts = Maps.newHashMap();
+
+        @Override
+        public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
+                                            HostDescription hostDescription,
+                                            boolean replaceIps) {
+            hosts.put(hostId, hostDescription);
+
+            // not necessary to return host event in this test.
+            return null;
+        }
+
+        public HostDescription hostDesc(HostId hostId) {
+            return hosts.get(hostId);
+        }
+
+        @Override
+        public Iterable<Host> getHosts() {
+            return hosts.values().stream()
+                    .map(hd -> new DefaultHost(DhcpRelayManager.PROVIDER_ID,
+                                               HostId.hostId(hd.hwAddress(), hd.vlan()),
+                                               hd.hwAddress(),
+                                               hd.vlan(), hd.locations(),
+                                               hd.ipAddress(), false))
+                    .collect(Collectors.toList());
+        }
+    }
+
+    private class MockRouteStore extends RouteStoreAdapter {
+        private List<Route> routes = Lists.newArrayList();
+
+        @Override
+        public void updateRoute(Route route) {
+            routes.add(route);
+        }
+    }
+
+    private class MockInterfaceService extends InterfaceServiceAdapter {
+
+        @Override
+        public Set<Interface> getInterfaces() {
+            return INTERFACES;
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByIp(IpAddress ip) {
+            return INTERFACES.stream()
+                    .filter(iface -> {
+                        return iface.ipAddressesList().stream()
+                                .anyMatch(ifaceIp -> ifaceIp.ipAddress().equals(ip));
+                    })
+                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+            return INTERFACES.stream()
+                    .filter(iface -> iface.connectPoint().equals(port))
+                    .collect(Collectors.toSet());
+        }
+    }
+
+    private class MockDhcpRelayStore implements DhcpRelayStore {
+        StoreDelegate<DhcpRelayStoreEvent> delegate;
+        private Map<HostId, DhcpRecord> records = Maps.newHashMap();
+
+        @Override
+        public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
+            records.put(hostId, dhcpRecord);
+            DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.UPDATED,
+                                                                dhcpRecord);
+            if (delegate != null) {
+                delegate.notify(event);
+            }
+        }
+
+        @Override
+        public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+            return Optional.ofNullable(records.get(hostId));
+        }
+
+        @Override
+        public Collection<DhcpRecord> getDhcpRecords() {
+            return records.values();
+        }
+
+        @Override
+        public Optional<DhcpRecord> removeDhcpRecord(HostId hostId) {
+            DhcpRecord dhcpRecord = records.remove(hostId);
+            if (dhcpRecord != null) {
+                DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.REMOVED,
+                                                                    dhcpRecord);
+                if (delegate != null) {
+                    delegate.notify(event);
+                }
+            }
+            return Optional.ofNullable(dhcpRecord);
+        }
+
+        @Override
+        public void setDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void unsetDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+            this.delegate = null;
+        }
+
+        @Override
+        public boolean hasDelegate() {
+            return this.delegate != null;
+        }
+    }
+
+    private class MockPacketService extends PacketServiceAdapter {
+        Set<PacketProcessor> packetProcessors = Sets.newHashSet();
+        OutboundPacket emitedPacket;
+
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+            packetProcessors.add(processor);
+        }
+
+        public void processPacket(PacketContext packetContext) {
+            packetProcessors.forEach(p -> p.process(packetContext));
+        }
+
+        @Override
+        public void emit(OutboundPacket packet) {
+            this.emitedPacket = packet;
+        }
+    }
+
+
+
+    /**
+     * Generates DHCP REQUEST packet.
+     */
+    private class TestDhcpRequestPacketContext extends PacketContextAdapter {
+
+
+        private InboundPacket inPacket;
+
+        public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId,
+                                            ConnectPoint clientCp,
+                                            Ip4Address clientGwAddr,
+                                            boolean withNonOnosRelayInfo) {
+            super(0, null, null, false);
+            byte[] dhcpMsgType = new byte[1];
+            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
+
+            DhcpOption dhcpOption = new DhcpOption();
+            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            dhcpOption.setData(dhcpMsgType);
+            dhcpOption.setLength((byte) 1);
+            DhcpOption endOption = new DhcpOption();
+            endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+            DHCP dhcp = new DHCP();
+            dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+            dhcp.setHardwareAddressLength((byte) 6);
+            dhcp.setClientHardwareAddress(clientMac.toBytes());
+            if (withNonOnosRelayInfo) {
+                DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+                DhcpOption circuitIdOption = new DhcpOption();
+                CircuitId circuitId = new CircuitId("Custom option", VlanId.NONE);
+                byte[] cid = circuitId.serialize();
+                circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+                circuitIdOption.setLength((byte) cid.length);
+                circuitIdOption.setData(cid);
+                relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+                relayOption.addSubOption(circuitIdOption);
+                dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+                dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+            } else {
+                dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+            }
+
+
+            UDP udp = new UDP();
+            udp.setPayload(dhcp);
+            udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+            udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+
+            IPv4 ipv4 = new IPv4();
+            ipv4.setPayload(udp);
+            ipv4.setDestinationAddress(SERVER_IP.toInt());
+            ipv4.setSourceAddress(clientGwAddr.toInt());
+
+            Ethernet eth = new Ethernet();
+            if (withNonOnosRelayInfo) {
+                eth.setEtherType(Ethernet.TYPE_IPV4)
+                        .setVlanID(vlanId.toShort())
+                        .setSourceMACAddress(OUTER_RELAY_MAC)
+                        .setDestinationMACAddress(MacAddress.BROADCAST)
+                        .setPayload(ipv4);
+            } else {
+                eth.setEtherType(Ethernet.TYPE_IPV4)
+                        .setVlanID(vlanId.toShort())
+                        .setSourceMACAddress(clientMac)
+                        .setDestinationMACAddress(MacAddress.BROADCAST)
+                        .setPayload(ipv4);
+            }
+
+            this.inPacket = new DefaultInboundPacket(clientCp, eth,
+                                                     ByteBuffer.wrap(eth.serialize()));
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            return this.inPacket;
+        }
+    }
+
+    /**
+     * Generates DHCP ACK packet.
+     */
+    private class TestDhcpAckPacketContext extends PacketContextAdapter {
+        private InboundPacket inPacket;
+
+        public TestDhcpAckPacketContext(ConnectPoint clientCp, MacAddress clientMac,
+                                        VlanId clientVlan, Ip4Address clientGwAddr,
+                                        boolean withNonOnosRelayInfo) {
+            super(0, null, null, false);
+
+            byte[] dhcpMsgType = new byte[1];
+            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPACK.getValue();
+
+            DhcpOption dhcpOption = new DhcpOption();
+            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            dhcpOption.setData(dhcpMsgType);
+            dhcpOption.setLength((byte) 1);
+
+            DhcpOption endOption = new DhcpOption();
+            endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+            DHCP dhcp = new DHCP();
+            if (withNonOnosRelayInfo) {
+                DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+                DhcpOption circuitIdOption = new DhcpOption();
+                CircuitId circuitId = new CircuitId("Custom cid", VlanId.NONE);
+                byte[] cid = circuitId.serialize();
+                circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+                circuitIdOption.setLength((byte) cid.length);
+                circuitIdOption.setData(cid);
+                relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+                relayOption.addSubOption(circuitIdOption);
+                dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+                dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+            } else {
+                CircuitId cid = new CircuitId(clientCp.toString(), clientVlan);
+                byte[] circuitId = cid.serialize();
+                DhcpOption circuitIdSubOption = new DhcpOption();
+                circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+                circuitIdSubOption.setData(circuitId);
+                circuitIdSubOption.setLength((byte) circuitId.length);
+
+                DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+                relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+                relayInfoOption.addSubOption(circuitIdSubOption);
+                dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption, endOption));
+                dhcp.setGatewayIPAddress(clientGwAddr.toInt());
+            }
+            dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+            dhcp.setHardwareAddressLength((byte) 6);
+            dhcp.setClientHardwareAddress(clientMac.toBytes());
+            dhcp.setYourIPAddress(IP_FOR_CLIENT.toInt());
+
+            UDP udp = new UDP();
+            udp.setPayload(dhcp);
+            udp.setSourcePort(UDP.DHCP_SERVER_PORT);
+            udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+            IPv4 ipv4 = new IPv4();
+            ipv4.setPayload(udp);
+            ipv4.setDestinationAddress(IP_FOR_CLIENT.toString());
+            ipv4.setSourceAddress(SERVER_IP.toString());
+            Ethernet eth = new Ethernet();
+            if (withNonOnosRelayInfo) {
+                eth.setEtherType(Ethernet.TYPE_IPV4)
+                        .setVlanID(SERVER_VLAN.toShort())
+                        .setSourceMACAddress(SERVER_MAC)
+                        .setDestinationMACAddress(OUTER_RELAY_MAC)
+                        .setPayload(ipv4);
+            } else {
+                eth.setEtherType(Ethernet.TYPE_IPV4)
+                        .setVlanID(SERVER_VLAN.toShort())
+                        .setSourceMACAddress(SERVER_MAC)
+                        .setDestinationMACAddress(CLIENT_MAC)
+                        .setPayload(ipv4);
+            }
+
+            this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth,
+                                                     ByteBuffer.wrap(eth.serialize()));
+
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            return this.inPacket;
+        }
+    }
+
+    private class TestArpRequestPacketContext extends PacketContextAdapter {
+        private InboundPacket inPacket;
+
+        public TestArpRequestPacketContext(Interface fromInterface) {
+            super(0, null, null, false);
+            ARP arp = new ARP();
+            arp.setOpCode(ARP.OP_REQUEST);
+
+            IpAddress targetIp = fromInterface.ipAddressesList().get(0).ipAddress();
+            arp.setTargetProtocolAddress(targetIp.toOctets());
+            arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
+            arp.setSenderHardwareAddress(MacAddress.NONE.toBytes());
+            arp.setSenderProtocolAddress(Ip4Address.valueOf(0).toOctets());
+            arp.setHardwareAddressLength((byte) MacAddress.MAC_ADDRESS_LENGTH);
+            Ethernet eth = new Ethernet();
+            eth.setEtherType(Ethernet.TYPE_ARP);
+            eth.setSourceMACAddress(MacAddress.NONE);
+            eth.setDestinationMACAddress(MacAddress.BROADCAST);
+            eth.setVlanID(fromInterface.vlan().toShort());
+            eth.setPayload(arp);
+
+            this.inPacket = new DefaultInboundPacket(fromInterface.connectPoint(), eth,
+                                                     ByteBuffer.wrap(eth.serialize()));
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            return this.inPacket;
+        }
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
new file mode 100644
index 0000000..796e6d6
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.host;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.provider.ProviderId;
+
+import java.util.Set;
+
+/**
+ * Test adapter for host store.
+ */
+public class HostStoreAdapter implements HostStore {
+    @Override
+    public void setDelegate(HostStoreDelegate delegate) {
+
+    }
+
+    @Override
+    public void unsetDelegate(HostStoreDelegate delegate) {
+
+    }
+
+    @Override
+    public boolean hasDelegate() {
+        return false;
+    }
+
+    @Override
+    public HostEvent createOrUpdateHost(ProviderId providerId,
+                                        HostId hostId,
+                                        HostDescription hostDescription,
+                                        boolean replaceIps) {
+        return null;
+    }
+
+    @Override
+    public HostEvent removeHost(HostId hostId) {
+        return null;
+    }
+
+    @Override
+    public HostEvent removeIp(HostId hostId, IpAddress ipAddress) {
+        return null;
+    }
+
+    @Override
+    public void removeLocation(HostId hostId, HostLocation location) {
+
+    }
+
+    @Override
+    public int getHostCount() {
+        return 0;
+    }
+
+    @Override
+    public Iterable<Host> getHosts() {
+        return null;
+    }
+
+    @Override
+    public Host getHost(HostId hostId) {
+        return null;
+    }
+
+    @Override
+    public Set<Host> getHosts(VlanId vlanId) {
+        return null;
+    }
+
+    @Override
+    public Set<Host> getHosts(MacAddress mac) {
+        return null;
+    }
+
+    @Override
+    public Set<Host> getHosts(IpAddress ip) {
+        return null;
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+        return null;
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(DeviceId deviceId) {
+        return null;
+    }
+}
diff --git a/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java b/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java
new file mode 100644
index 0000000..c5250d7
--- /dev/null
+++ b/incubator/api/src/test/java/org/onosproject/incubator/net/routing/RouteStoreAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.incubator.net.routing;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Adapter class for the route store.
+ */
+public class RouteStoreAdapter implements RouteStore {
+    @Override
+    public void updateRoute(Route route) {
+
+    }
+
+    @Override
+    public void removeRoute(Route route) {
+
+    }
+
+    @Override
+    public Set<RouteTableId> getRouteTables() {
+        return null;
+    }
+
+    @Override
+    public Collection<RouteSet> getRoutes(RouteTableId table) {
+        return null;
+    }
+
+    @Override
+    public Collection<Route> getRoutesForNextHop(IpAddress ip) {
+        return null;
+    }
+
+    @Override
+    public RouteSet getRoutes(IpPrefix prefix) {
+        return null;
+    }
+
+    @Override
+    public void setDelegate(RouteStoreDelegate delegate) {
+
+    }
+
+    @Override
+    public void unsetDelegate(RouteStoreDelegate delegate) {
+
+    }
+
+    @Override
+    public boolean hasDelegate() {
+        return false;
+    }
+}
diff --git a/providers/host/BUCK b/providers/host/BUCK
index f5cf0c3..2cfed29 100644
--- a/providers/host/BUCK
+++ b/providers/host/BUCK
@@ -1,5 +1,6 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
+    '//incubator/api:onos-incubator-api',
 ]
 
 TEST_DEPS = [
diff --git a/providers/host/pom.xml b/providers/host/pom.xml
index 7ead81d..f6016ca 100644
--- a/providers/host/pom.xml
+++ b/providers/host/pom.xml
@@ -64,6 +64,9 @@
             <artifactId>easymock</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-incubator-api</artifactId>
+        </dependency>
     </dependencies>
-
 </project>
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 996bd43..e197a0d 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -43,6 +43,7 @@
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
@@ -77,7 +78,10 @@
 
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Stream;
 
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
@@ -89,7 +93,6 @@
  */
 @Component(immediate = true)
 public class HostLocationProvider extends AbstractProvider implements HostProvider {
-
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -113,6 +116,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService cfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
     private HostProviderService providerService;
 
     private final InternalHostProvider processor = new InternalHostProvider();
@@ -124,18 +130,18 @@
             label = "Enable host removal on port/device down events")
     private boolean hostRemovalEnabled = true;
 
-    @Property(name = "useArp", boolValue = true,
-            label = "Enable using ARP for neighbor discovery by the " +
+    @Property(name = "requestArp", boolValue = true,
+            label = "Request ARP packets for neighbor discovery by the " +
                     "Host Location Provider; default is true")
-    private boolean useArp = true;
+    private boolean requestArp = true;
 
-    @Property(name = "useIpv6ND", boolValue = false,
-            label = "Enable using IPv6 Neighbor Discovery by the " +
+    @Property(name = "requestIpv6ND", boolValue = false,
+            label = "Requests IPv6 Neighbor Discovery by the " +
                     "Host Location Provider; default is false")
-    private boolean useIpv6ND = false;
+    private boolean requestIpv6ND = false;
 
     @Property(name = "useDhcp", boolValue = false,
-            label = "Enable using DHCP for neighbor discovery by the " +
+            label = "Use DHCP for neighbor discovery by the " +
                     "Host Location Provider; default is false")
     private boolean useDhcp = false;
 
@@ -202,7 +208,7 @@
         TrafficSelector arpSelector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_ARP)
                 .build();
-        if (useArp) {
+        if (requestArp) {
             packetService.requestPackets(arpSelector, PacketPriority.CONTROL, appId);
         } else {
             packetService.cancelPackets(arpSelector, PacketPriority.CONTROL, appId);
@@ -219,7 +225,7 @@
                 .matchIPProtocol(IPv6.PROTOCOL_ICMP6)
                 .matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT)
                 .build();
-        if (useIpv6ND) {
+        if (requestIpv6ND) {
             packetService.requestPackets(ipv6NsSelector, PacketPriority.CONTROL, appId);
             packetService.requestPackets(ipv6NaSelector, PacketPriority.CONTROL, appId);
         } else {
@@ -238,13 +244,6 @@
                 .matchIPProtocol(IPv4.PROTOCOL_UDP)
                 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
                 .build();
-        if (useDhcp) {
-            packetService.requestPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
-            packetService.requestPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
-        } else {
-            packetService.cancelPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
-            packetService.cancelPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
-        }
     }
 
     /**
@@ -278,51 +277,51 @@
         flag = Tools.isPropertyEnabled(properties, "hostRemovalEnabled");
         if (flag == null) {
             log.info("Host removal on port/device down events is not configured, " +
-                             "using current value of {}", hostRemovalEnabled);
+                     "using current value of {}", hostRemovalEnabled);
         } else {
             hostRemovalEnabled = flag;
             log.info("Configured. Host removal on port/device down events is {}",
                      hostRemovalEnabled ? "enabled" : "disabled");
         }
 
-        flag = Tools.isPropertyEnabled(properties, "useArp");
+        flag = Tools.isPropertyEnabled(properties, "requestArp");
         if (flag == null) {
             log.info("Using ARP is not configured, " +
-                    "using current value of {}", useArp);
+                     "using current value of {}", requestArp);
         } else {
-            useArp = flag;
+            requestArp = flag;
             log.info("Configured. Using ARP is {}",
-                    useArp ? "enabled" : "disabled");
+                     requestArp ? "enabled" : "disabled");
         }
 
-        flag = Tools.isPropertyEnabled(properties, "useIpv6ND");
+        flag = Tools.isPropertyEnabled(properties, "requestIpv6ND");
         if (flag == null) {
             log.info("Using IPv6 Neighbor Discovery is not configured, " +
-                             "using current value of {}", useIpv6ND);
+                             "using current value of {}", requestIpv6ND);
         } else {
-            useIpv6ND = flag;
+            requestIpv6ND = flag;
             log.info("Configured. Using IPv6 Neighbor Discovery is {}",
-                     useIpv6ND ? "enabled" : "disabled");
+                     requestIpv6ND ? "enabled" : "disabled");
         }
 
         flag = Tools.isPropertyEnabled(properties, "useDhcp");
         if (flag == null) {
             log.info("Using DHCP is not configured, " +
-                    "using current value of {}", useDhcp);
+                     "using current value of {}", useDhcp);
         } else {
             useDhcp = flag;
             log.info("Configured. Using DHCP is {}",
-                    useDhcp ? "enabled" : "disabled");
+                     useDhcp ? "enabled" : "disabled");
         }
 
         flag = Tools.isPropertyEnabled(properties, "requestInterceptsEnabled");
         if (flag == null) {
             log.info("Request intercepts is not configured, " +
-                    "using current value of {}", requestInterceptsEnabled);
+                     "using current value of {}", requestInterceptsEnabled);
         } else {
             requestInterceptsEnabled = flag;
             log.info("Configured. Request intercepts is {}",
-                    requestInterceptsEnabled ? "enabled" : "disabled");
+                     requestInterceptsEnabled ? "enabled" : "disabled");
         }
     }
 
@@ -475,30 +474,12 @@
                 createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
 
             // IPv4: update location only
-            // DHCP ACK: additionally update IP of DHCP client
             } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
-                IPacket pkt = eth.getPayload();
-                if (pkt != null && pkt instanceof IPv4) {
-                    pkt = pkt.getPayload();
-                    if (pkt != null && pkt instanceof UDP) {
-                        pkt = pkt.getPayload();
-                        if (pkt != null && pkt instanceof DHCP) {
-                            DHCP dhcp = (DHCP) pkt;
-                            if (dhcp.getOptions().stream()
-                                    .anyMatch(dhcpOption -> dhcpOption.getCode() ==
-                                            DHCP.DHCPOptionCode.OptionCode_MessageType.getValue() &&
-                                            dhcpOption.getLength() == 1 &&
-                                            dhcpOption.getData()[0] == DHCP.MsgType.DHCPACK.getValue())) {
-                                MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
-                                VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
-                                HostId hostId = HostId.hostId(hostMac, hostVlan);
-                                updateHostIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
-                            }
-                        }
-                    }
+                // DHCP ACK: additionally update IP of DHCP client
+                Optional<DHCP> dhcp = findDhcp(eth);
+                if (useDhcp || !dhcp.isPresent()) {
+                    createOrUpdateHost(hid, srcMac, vlan, hloc, null);
                 }
-                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
-
             //
             // NeighborAdvertisement and NeighborSolicitation: possible
             // new hosts, update both location and IP.
@@ -546,6 +527,21 @@
                 createOrUpdateHost(hid, srcMac, vlan, hloc, null);
             }
         }
+
+        private Optional<DHCP> findDhcp(Ethernet eth) {
+            IPacket pkt = eth.getPayload();
+            return Stream.of(pkt)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof IPv4)
+                    .map(IPacket::getPayload)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof UDP)
+                    .map(IPacket::getPayload)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof DHCP)
+                    .map(p -> (DHCP) p)
+                    .findFirst();
+        }
     }
 
     // Auxiliary listener to device events.
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index b28ec17..080e0e4 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -15,16 +15,19 @@
  */
 package org.onosproject.provider.host.impl;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.MoreExecutors;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestUtils;
 import org.onlab.osgi.ComponentContextAdapter;
 import org.onlab.packet.ARP;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.DHCP;
+import org.onlab.packet.dhcp.CircuitId;
 import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
@@ -35,6 +38,7 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
 import org.onlab.packet.ndp.NeighborAdvertisement;
 import org.onlab.packet.ndp.NeighborSolicitation;
 import org.onlab.packet.ndp.RouterAdvertisement;
@@ -43,6 +47,9 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceListener;
+import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.DefaultHost;
@@ -60,6 +67,7 @@
 import org.onosproject.net.host.HostProviderRegistry;
 import org.onosproject.net.host.HostProviderService;
 import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.PacketContextAdapter;
@@ -85,7 +93,6 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 import static org.onlab.packet.VlanId.vlanId;
 import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.net.DeviceId.deviceId;
@@ -109,6 +116,7 @@
     private static final String DEV6 = "of:6";
 
     private static final VlanId VLAN = vlanId();
+    private static final VlanId VLAN_100 = VlanId.vlanId("100");
 
     // IPv4 Host
     private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
@@ -122,6 +130,10 @@
             new DefaultHost(PROVIDER_ID, hostId(MAC), MAC,
                     VLAN, LOCATION,
                     ImmutableSet.of(IP_ADDRESS));
+    private static final DefaultHost HOST_VLAN_100 =
+            new DefaultHost(PROVIDER_ID, hostId(MAC, VLAN_100), MAC,
+                            VLAN_100, LOCATION,
+                            ImmutableSet.of(IP_ADDRESS));
 
     // IPv6 Host
     private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
@@ -149,6 +161,14 @@
                     VLAN, LOCATION3,
                     ImmutableSet.of(IP_ADDRESS3));
 
+    // Gateway information for relay agent
+    private static final InterfaceIpAddress GW_IFACE_ADDR = InterfaceIpAddress.valueOf("10.0.1.1/32");
+    private static final Interface GW_IFACE = new Interface("gateway",
+                                                                    LOCATION,
+                                                                    ImmutableList.of(GW_IFACE_ADDR),
+                                                                    null,
+                                                                    VLAN_100);
+
     private static final ComponentContextAdapter CTX_FOR_REMOVE =
             new ComponentContextAdapter() {
                 @Override
@@ -173,6 +193,7 @@
     private final TestDeviceService deviceService = new TestDeviceService();
     private final TestHostService hostService = new TestHostService();
     private final TestPacketService packetService = new TestPacketService();
+    private final TestInterfaceService interfaceService = new TestInterfaceService();
 
     private PacketProcessor testProcessor;
     private CoreService coreService;
@@ -183,7 +204,6 @@
 
     @Before
     public void setUp() {
-
         coreService = createMock(CoreService.class);
         expect(coreService.registerApplication(appId.name()))
                 .andReturn(appId).anyTimes();
@@ -197,6 +217,7 @@
         provider.packetService = packetService;
         provider.deviceService = deviceService;
         provider.hostService = hostService;
+        provider.interfaceService = interfaceService;
 
         provider.activate(CTX_FOR_NO_REMOVE);
 
@@ -335,8 +356,9 @@
      */
     @Test
     public void receiveDhcp() {
+        TestUtils.setField(provider, "useDhcp", true);
         // DHCP Request
-        testProcessor.process(new TestDhcpRequestPacketContext(DEV1));
+        testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
         assertThat("receiveDhcpRequest. One host description expected",
                 providerService.descriptions.size(), is(1));
         // Should learn the MAC and location of DHCP client
@@ -347,23 +369,30 @@
         assertThat(descr.vlan(), is(VLAN));
 
         // DHCP Ack
-        testProcessor.process(new TestDhcpAckPacketContext(DEV1));
+        testProcessor.process(new TestDhcpAckPacketContext(DEV1, false));
         assertThat("receiveDhcpAck. Two additional host descriptions expected",
-                providerService.descriptions.size(), is(3));
-        // Should learn the IP of DHCP client
-        HostDescription descr2 = providerService.descriptions.get(1);
-        assertThat(descr2.location(), is(LOCATION));
-        assertThat(descr2.hwAddress(), is(MAC));
-        assertThat(descr2.ipAddress().size(), is(1));
-        assertTrue(descr2.ipAddress().contains(IP_ADDRESS));
-        assertThat(descr2.vlan(), is(VLAN));
-        // Should also learn the MAC, location of DHCP server
-        HostDescription descr3 = providerService.descriptions.get(2);
-        assertThat(descr3.location(), is(LOCATION3));
-        assertThat(descr3.hwAddress(), is(MAC3));
-        assertThat(descr3.ipAddress().size(), is(0));
-        assertThat(descr3.vlan(), is(VLAN));
+                providerService.descriptions.size(), is(2));
 
+        // Should also learn the MAC, location of DHCP server
+        HostDescription descr2 = providerService.descriptions.get(1);
+        assertThat(descr2.location(), is(LOCATION3));
+        assertThat(descr2.hwAddress(), is(MAC3));
+        assertThat(descr2.ipAddress().size(), is(0));
+        assertThat(descr2.vlan(), is(VLAN));
+
+        // Should not update the IP address of the host.
+    }
+
+    /**
+     * The host store should not updated when we disabled "useDhcp".
+     */
+    @Test
+    public void receiveDhcpButNotEnabled() {
+        TestUtils.setField(provider, "useDhcp", false);
+        // DHCP Request
+        testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
+        assertThat("receiveDhcpButNotEnabled. No host description expected",
+                   providerService.descriptions.size(), is(0));
     }
 
     /**
@@ -604,10 +633,12 @@
      */
     private class TestDhcpRequestPacketContext extends PacketContextAdapter {
         private final String deviceId;
+        private final VlanId vlanId;
 
-        public TestDhcpRequestPacketContext(String deviceId) {
+        public TestDhcpRequestPacketContext(String deviceId, VlanId vlanId) {
             super(0, null, null, false);
             this.deviceId = deviceId;
+            this.vlanId = vlanId;
         }
 
         @Override
@@ -632,7 +663,7 @@
             ipv4.setSourceAddress(IP_ADDRESS.toString());
             Ethernet eth = new Ethernet();
             eth.setEtherType(Ethernet.TYPE_IPV4)
-                    .setVlanID(VLAN.toShort())
+                    .setVlanID(this.vlanId.toShort())
                     .setSourceMACAddress(MAC)
                     .setDestinationMACAddress(MAC3)
                     .setPayload(ipv4);
@@ -648,10 +679,12 @@
      */
     private class TestDhcpAckPacketContext extends PacketContextAdapter {
         private final String deviceId;
+        private final boolean withRelayinfo;
 
-        public TestDhcpAckPacketContext(String deviceId) {
+        public TestDhcpAckPacketContext(String deviceId, boolean withRelayInfo) {
             super(0, null, null, false);
             this.deviceId = deviceId;
+            this.withRelayinfo = withRelayInfo;
         }
 
         @Override
@@ -663,10 +696,29 @@
             dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
             dhcpOption.setData(dhcpMsgType);
             dhcpOption.setLength((byte) 1);
+
             DHCP dhcp = new DHCP();
-            dhcp.setOptions(Collections.singletonList(dhcpOption));
+
+            if (withRelayinfo) {
+                CircuitId cid = new CircuitId(LOCATION.toString(), VLAN_100);
+                byte[] circuitId = cid.serialize();
+                DhcpOption circuitIdSubOption = new DhcpOption();
+                circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+                circuitIdSubOption.setData(circuitId);
+                circuitIdSubOption.setLength((byte) circuitId.length);
+
+                DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+                relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+                relayInfoOption.addSubOption(circuitIdSubOption);
+                dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption));
+                dhcp.setGatewayIPAddress(GW_IFACE_ADDR.ipAddress().getIp4Address().toInt());
+            } else {
+                dhcp.setOptions(ImmutableList.of(dhcpOption));
+            }
+
             dhcp.setClientHardwareAddress(MAC.toBytes());
             dhcp.setYourIPAddress(IP_ADDRESS.getIp4Address().toInt());
+
             UDP udp = new UDP();
             udp.setPayload(dhcp);
             udp.setSourcePort(UDP.DHCP_SERVER_PORT);
@@ -961,9 +1013,60 @@
                 return HOST2;
             } else if (hostId.equals(HostId.hostId(MAC3, VLAN))) {
                 return HOST3;
+            } else if (hostId.equals(HostId.hostId(MAC, VLAN_100))) {
+                return HOST_VLAN_100;
             }
             return null;
         }
+    }
 
+    private class TestInterfaceService implements InterfaceService {
+        @Override
+        public Set<Interface> getInterfaces() {
+            return null;
+        }
+
+        @Override
+        public Interface getInterfaceByName(ConnectPoint connectPoint, String name) {
+            return null;
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+            return null;
+        }
+
+        public Set<Interface> getInterfacesByIp(IpAddress ip) {
+            if (ip.equals(GW_IFACE_ADDR.ipAddress())) {
+                return ImmutableSet.of(GW_IFACE);
+            } else {
+                return ImmutableSet.of();
+            }
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByVlan(VlanId vlan) {
+            return null;
+        }
+
+        @Override
+        public Interface getMatchingInterface(IpAddress ip) {
+            return null;
+        }
+
+        @Override
+        public Set<Interface> getMatchingInterfaces(IpAddress ip) {
+            return null;
+        }
+
+        @Override
+        public void addListener(InterfaceListener listener) {
+
+        }
+
+        @Override
+        public void removeListener(InterfaceListener listener) {
+
+        }
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP.java b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
index ed1741e..f673da0 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP.java
@@ -583,7 +583,6 @@
             boolean foundEndOptionsMarker = false;
             while (bb.hasRemaining()) {
                 DhcpOption option;
-
                 int pos = bb.position();
                 int optCode = UNSIGNED_BYTE_MASK & bb.array()[pos]; // to unsigned integer
                 int optLen;