Merge remote-tracking branch 'origin/master' into dev/murrelet
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index f3835c0..ccdb407 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -119,6 +119,14 @@
     private Ip4Address dhcpGatewayIp = null;
     private Ip4Address relayAgentIp = null;
 
+    // Indirect case DHCP server
+    private Ip4Address indirectDhcpServerIp = null;
+    private ConnectPoint indirectDhcpServerConnectPoint = null;
+    private MacAddress indirectDhcpConnectMac = null;
+    private VlanId indirectDhcpConnectVlan = null;
+    private Ip4Address indirectDhcpGatewayIp = null;
+    private Ip4Address indirectRelayAgentIp = null;
+
     @Activate
     protected void activate() {
         hostService.addListener(hostListener);
@@ -126,15 +134,25 @@
 
     @Deactivate
     protected void deactivate() {
+        if (dhcpGatewayIp != null) {
+            hostService.stopMonitoringIp(dhcpGatewayIp);
+        }
+        if (dhcpServerIp != null) {
+            hostService.stopMonitoringIp(dhcpServerIp);
+        }
+
+        if (indirectDhcpGatewayIp != null) {
+            hostService.stopMonitoringIp(indirectDhcpGatewayIp);
+        }
+        if (indirectDhcpServerIp != null) {
+            hostService.stopMonitoringIp(indirectDhcpServerIp);
+        }
+
         hostService.removeListener(hostListener);
         this.dhcpConnectMac = null;
         this.dhcpConnectVlan = null;
-
-        if (dhcpGatewayIp != null) {
-            hostService.stopMonitoringIp(dhcpGatewayIp);
-        } else if (dhcpServerIp != null) {
-            hostService.stopMonitoringIp(dhcpServerIp);
-        }
+        this.indirectDhcpConnectMac = null;
+        this.indirectDhcpConnectVlan = null;
     }
 
     @Override
@@ -248,7 +266,53 @@
 
     @Override
     public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
-        log.warn("Indirect config feature for DHCPv4 handler not implement yet");
+        if (configs.size() == 0) {
+            // no config to update
+            return;
+        }
+
+        // TODO: currently we pick up first indirect DHCP server config.
+        // Will use other server configs in the future for HA.
+        DhcpServerConfig serverConfig = configs.iterator().next();
+        checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
+                   "Connect point not exists");
+        checkState(serverConfig.getDhcpServerIp4().isPresent(),
+                   "IP of DHCP server not exists");
+        Ip4Address oldServerIp = this.indirectDhcpServerIp;
+        Ip4Address oldGatewayIp = this.indirectDhcpGatewayIp;
+
+        // stop monitoring gateway or server
+        if (oldGatewayIp != null) {
+            hostService.stopMonitoringIp(oldGatewayIp);
+        } else if (oldServerIp != null) {
+            hostService.stopMonitoringIp(oldServerIp);
+        }
+
+        this.indirectDhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
+        this.indirectDhcpServerIp = serverConfig.getDhcpServerIp4().get();
+        this.indirectDhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
+
+        // reset server mac and vlan
+        this.indirectDhcpConnectMac = null;
+        this.indirectDhcpConnectVlan = null;
+
+        log.info("Indirect DHCP server connect point: " + this.indirectDhcpServerConnectPoint);
+        log.info("Indirect DHCP server IP: " + this.indirectDhcpServerIp);
+
+        IpAddress ipToProbe = MoreObjects.firstNonNull(this.indirectDhcpGatewayIp, this.indirectDhcpServerIp);
+        String hostToProbe = this.indirectDhcpGatewayIp != null ? "gateway" : "DHCP server";
+
+        log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+        hostService.startMonitoringIp(ipToProbe);
+
+        Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+        if (!hosts.isEmpty()) {
+            Host host = hosts.iterator().next();
+            this.indirectDhcpConnectVlan = host.vlan();
+            this.indirectDhcpConnectMac = host.mac();
+        }
+
+        this.indirectRelayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
     }
 
     @Override
@@ -284,7 +348,7 @@
                         processDhcpPacketFromClient(context, packet);
                 if (ethernetPacketDiscover != null) {
                     writeRequestDhcpRecord(inPort, packet, dhcpPayload);
-                    handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
+                    handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
                 }
                 break;
             case DHCPOFFER:
@@ -302,7 +366,7 @@
                         processDhcpPacketFromClient(context, packet);
                 if (ethernetPacketRequest != null) {
                     writeRequestDhcpRecord(inPort, packet, dhcpPayload);
-                    handleDhcpDiscoverAndRequest(ethernetPacketRequest);
+                    handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
                 }
                 break;
             case DHCPACK:
@@ -343,7 +407,7 @@
      *
      * @return true if all information we need have been initialized
      */
-    public boolean configured() {
+    private boolean configured() {
         return dhcpServerConnectPoint != null && dhcpServerIp != null;
     }
 
@@ -365,25 +429,53 @@
     }
 
     /**
-     * Gets Interface facing to the server.
+     * Gets Interface facing to the server for default host.
      *
      * @return the Interface facing to the server; null if not found
      */
-    public Interface getServerInterface() {
+    private Interface getServerInterface() {
         if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
             return null;
         }
         return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
                 .stream()
-                .filter(iface -> iface.vlan().equals(dhcpConnectVlan) ||
-                        iface.vlanUntagged().equals(dhcpConnectVlan) ||
-                        iface.vlanTagged().contains(dhcpConnectVlan) ||
-                        iface.vlanNative().equals(dhcpConnectVlan))
+                .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
                 .findFirst()
                 .orElse(null);
     }
 
     /**
+     * Gets Interface facing to the server for indirect hosts.
+     * Use default server Interface if indirect server not configured.
+     *
+     * @return the Interface facing to the server; null if not found
+     */
+    private Interface getIndirectServerInterface() {
+        if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) {
+            return getServerInterface();
+        }
+        return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint)
+                .stream()
+                .filter(iface -> interfaceContainsVlan(iface, indirectDhcpConnectVlan))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * Determind if an Interface contains a vlan id.
+     *
+     * @param iface the Interface
+     * @param vlanId the vlan id
+     * @return true if the Interface contains the vlan id
+     */
+    private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
+        return iface.vlan().equals(vlanId) ||
+                iface.vlanUntagged().equals(vlanId) ||
+                iface.vlanTagged().contains(vlanId) ||
+                iface.vlanNative().equals(vlanId);
+    }
+
+    /**
      * Build the DHCP discover/request packet with gateway IP(unicast packet).
      *
      * @param context the packet context
@@ -392,6 +484,12 @@
      */
     private Ethernet processDhcpPacketFromClient(PacketContext context,
                                                  Ethernet ethernetPacket) {
+        // get dhcp header.
+        Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+        IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+        UDP udpPacket = (UDP) ipv4Packet.getPayload();
+        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
         Ip4Address clientInterfaceIp =
                 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
                         .stream()
@@ -407,9 +505,10 @@
                      context.inPacket().receivedFrom());
             return null;
         }
-        Interface serverInterface = getServerInterface();
+        boolean isDirectlyConnected = directlyConnected(dhcpPacket);
+        Interface serverInterface = isDirectlyConnected ? getServerInterface() : getIndirectServerInterface();
         if (serverInterface == null) {
-            log.warn("Can't get server interface, ignore");
+            log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
             return null;
         }
         Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
@@ -426,18 +525,14 @@
                      context.inPacket().receivedFrom());
             return null;
         }
-        // get dhcp header.
-        Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+
         etherReply.setSourceMACAddress(macFacingServer);
         etherReply.setDestinationMACAddress(dhcpConnectMac);
         etherReply.setVlanID(dhcpConnectVlan.toShort());
-        IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
         ipv4Packet.setSourceAddress(ipFacingServer.toInt());
         ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
-        UDP udpPacket = (UDP) ipv4Packet.getPayload();
-        DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
 
-        if (directlyConnected(dhcpPacket)) {
+        if (isDirectlyConnected) {
             ConnectPoint inPort = context.inPacket().receivedFrom();
             VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
             // add connected in port and vlan
@@ -471,17 +566,26 @@
             // Sets giaddr to IP address from the Interface which facing to
             // DHCP client
             dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
-        }
 
-        // replace giaddr if relay agent IP is set
-        // FIXME for both direct and indirect case now, should be separated
-        if (relayAgentIp != null) {
-            dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+            // replace giaddr if relay agent IP is set
+            if (relayAgentIp != null) {
+                dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+            }
+        } else if (indirectDhcpServerIp != null) {
+            // Indirect case, replace destination to indirect dhcp server if exist
+            etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
+            etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
+            ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
+
+            // replace giaddr if relay agent IP is set
+            if (indirectRelayAgentIp != null) {
+                dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
+            }
         }
 
         udpPacket.setPayload(dhcpPacket);
-        // As a DHCP relay, the source port should be server port(67) instead
-        // of client port(68)
+        // As a DHCP relay, the source port should be server port( instead
+        // of client port.
         udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
         udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
         ipv4Packet.setPayload(udpPacket);
@@ -535,7 +639,7 @@
 
         Interface outIface = outInterface.get();
         ConnectPoint location = outIface.connectPoint();
-        VlanId vlanId = getVlanIdFromOption(dhcpPayload);
+        VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
         if (vlanId == null) {
             vlanId = outIface.vlan();
         }
@@ -583,7 +687,7 @@
             vlanId = clientInterface.vlan();
         } else {
             // might be multiple vlan in same interface
-            vlanId = getVlanIdFromOption(dhcpPayload);
+            vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
         }
         if (vlanId == null) {
             vlanId = VlanId.NONE;
@@ -642,7 +746,7 @@
      * @param dhcpPayload the DHCP payload
      * @return VLAN ID from DHCP payload; null if not exists
      */
-    private VlanId getVlanIdFromOption(DHCP dhcpPayload) {
+    private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
         DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
         if (option == null) {
             return null;
@@ -733,7 +837,7 @@
         Interface outIface = outInterface.get();
         HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
         MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
-        VlanId vlanId = getVlanIdFromOption(dhcpPayload);
+        VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
         if (vlanId == null) {
             vlanId = outIface.vlan();
         }
@@ -795,13 +899,17 @@
      *
      * @param packet the packet
      */
-    private void handleDhcpDiscoverAndRequest(Ethernet packet) {
+    private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
+        ConnectPoint portToFotward = dhcpServerConnectPoint;
+        if (!directlyConnected(dhcpPayload) && indirectDhcpServerConnectPoint != null) {
+            portToFotward = indirectDhcpServerConnectPoint;
+        }
         // send packet to dhcp server connect point.
-        if (dhcpServerConnectPoint != null) {
+        if (portToFotward != null) {
             TrafficTreatment t = DefaultTrafficTreatment.builder()
-                    .setOutput(dhcpServerConnectPoint.port()).build();
+                    .setOutput(portToFotward.port()).build();
             OutboundPacket o = new DefaultOutboundPacket(
-                    dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+                    portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
             if (log.isTraceEnabled()) {
                 log.trace("Relaying packet to dhcp server {}", packet);
             }
@@ -815,7 +923,7 @@
     /**
      * 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
+     * ONOS (circuit format is correct), use the connect
      * point and vlan id from circuit id; otherwise, find host by destination
      * address and use vlan id from sender (dhcp server).
      *
@@ -826,31 +934,21 @@
      */
     private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
         VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
-        IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
-
-        // get all possible interfaces for client
-        Set<Interface> clientInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
         DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
 
-        // Sent by ONOS, and contains circuit id
-        if (!clientInterfaces.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 clientInterfaces.stream()
-                        .filter(iface -> iface.vlanUntagged().equals(vlanId) ||
-                                iface.vlan().equals(vlanId) ||
-                                iface.vlanNative().equals(vlanId) ||
-                                iface.vlanTagged().contains(vlanId))
-                        .filter(iface -> iface.connectPoint().equals(connectPoint))
-                        .findFirst();
-            } catch (IllegalArgumentException ex) {
-                // invalid circuit format, didn't sent by ONOS
-                log.debug("Invalid circuit {}, use information from dhcp payload",
-                          circuitIdSubOption.getData());
-            }
+        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 interfaceService.getInterfacesByPort(connectPoint)
+                    .stream()
+                    .filter(iface -> interfaceContainsVlan(iface, vlanId))
+                    .findFirst();
+        } 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
@@ -874,8 +972,7 @@
         if (clientConnectPoint != null) {
             return interfaceService.getInterfacesByPort(clientConnectPoint)
                     .stream()
-                    .filter(iface -> iface.vlan().equals(originalPacketVlanId) ||
-                            iface.vlanUntagged().equals(originalPacketVlanId))
+                    .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
                     .findFirst();
         }
         return Optional.empty();
@@ -941,16 +1038,12 @@
      * @param host the host
      */
     private void hostMoved(Host host) {
-        if (this.dhcpServerConnectPoint == null) {
-            return;
-        }
         if (this.dhcpGatewayIp != null) {
             if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
                     !host.locations().contains(this.dhcpServerConnectPoint)) {
                 this.dhcpConnectMac = null;
                 this.dhcpConnectVlan = null;
             }
-            return;
         }
         if (this.dhcpServerIp != null) {
             if (host.ipAddresses().contains(this.dhcpServerIp) &&
@@ -959,6 +1052,20 @@
                 this.dhcpConnectVlan = null;
             }
         }
+        if (this.indirectDhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpGatewayIp) &&
+                    !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
+                this.indirectDhcpConnectMac = null;
+                this.indirectDhcpConnectVlan = null;
+            }
+        }
+        if (this.indirectDhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpServerIp) &&
+                    !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
+                this.indirectDhcpConnectMac = null;
+                this.indirectDhcpConnectVlan = null;
+            }
+        }
     }
 
     /**
@@ -973,7 +1080,6 @@
                 this.dhcpConnectMac = host.mac();
                 this.dhcpConnectVlan = host.vlan();
             }
-            return;
         }
         if (this.dhcpServerIp != null) {
             if (host.ipAddresses().contains(this.dhcpServerIp)) {
@@ -981,6 +1087,18 @@
                 this.dhcpConnectVlan = host.vlan();
             }
         }
+        if (this.indirectDhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
+                this.indirectDhcpConnectMac = host.mac();
+                this.indirectDhcpConnectVlan = host.vlan();
+            }
+        }
+        if (this.indirectDhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
+                this.indirectDhcpConnectMac = host.mac();
+                this.indirectDhcpConnectVlan = host.vlan();
+            }
+        }
     }
 
     /**
@@ -995,7 +1113,6 @@
                 this.dhcpConnectMac = null;
                 this.dhcpConnectVlan = null;
             }
-            return;
         }
         if (this.dhcpServerIp != null) {
             if (host.ipAddresses().contains(this.dhcpServerIp)) {
@@ -1003,5 +1120,17 @@
                 this.dhcpConnectVlan = null;
             }
         }
+        if (this.indirectDhcpGatewayIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
+                this.indirectDhcpConnectMac = null;
+                this.indirectDhcpConnectVlan = null;
+            }
+        }
+        if (this.indirectDhcpServerIp != null) {
+            if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
+                this.indirectDhcpConnectMac = null;
+                this.indirectDhcpConnectVlan = null;
+            }
+        }
     }
 }
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
index 99b8afb..1b50297 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -17,12 +17,17 @@
 
 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.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -38,6 +43,7 @@
 import org.onlab.packet.IPv6;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
@@ -49,10 +55,22 @@
 import org.onosproject.dhcprelay.api.DhcpHandler;
 import org.onosproject.dhcprelay.api.DhcpRelayService;
 import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
 import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.net.DeviceId;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.net.Host;
 import org.onosproject.net.config.Config;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
@@ -92,6 +110,36 @@
             "org.onosproject.provider.host.impl.HostLocationProvider";
     public static final String ROUTE_STORE_IMPL =
             "org.onosproject.routeservice.store.RouteStoreImpl";
+    private static final TrafficSelector DHCP_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV4)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+            .build();
+    private static final TrafficSelector DHCP_CLIENT_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV4)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+            .build();
+    private static final TrafficSelector DHCP6_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV6)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT))
+            .build();
+    private static final TrafficSelector DHCP6_CLIENT_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV6)
+            .matchIPProtocol(IPv4.PROTOCOL_UDP)
+            .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_CLIENT_PORT))
+            .build();
+    static final List<TrafficSelector> DHCP_SELECTORS = ImmutableList.of(
+            DHCP_SERVER_SELECTOR,
+            DHCP_CLIENT_SELECTOR,
+            DHCP6_SERVER_SELECTOR,
+            DHCP6_CLIENT_SELECTOR
+    );
+    private static final TrafficSelector ARP_SELECTOR = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_ARP)
+            .build();
+    private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
     private final Logger log = LoggerFactory.getLogger(getClass());
     private final InternalConfigListener cfgListener = new InternalConfigListener();
 
@@ -113,6 +161,15 @@
                 public IndirectDhcpRelayConfig createConfig() {
                     return new IndirectDhcpRelayConfig();
                 }
+            },
+            new ConfigFactory<ApplicationId, IgnoreDhcpConfig>(APP_SUBJECT_FACTORY,
+                                                               IgnoreDhcpConfig.class,
+                                                               IgnoreDhcpConfig.KEY,
+                                                               true) {
+                @Override
+                public IgnoreDhcpConfig createConfig() {
+                    return new IgnoreDhcpConfig();
+                }
             }
     );
 
@@ -137,6 +194,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService compCfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
                target = "(version=4)")
     protected DhcpHandler v4Handler;
@@ -149,6 +209,7 @@
              label = "Enable Address resolution protocol")
     protected boolean arpEnabled = true;
 
+    protected Multimap<DeviceId, VlanId> ignoredVlans = HashMultimap.create();
     private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
     private ApplicationId appId;
 
@@ -218,14 +279,18 @@
                 cfgService.getConfig(appId, DefaultDhcpRelayConfig.class);
         IndirectDhcpRelayConfig indirectConfig =
                 cfgService.getConfig(appId, IndirectDhcpRelayConfig.class);
+        IgnoreDhcpConfig ignoreDhcpConfig =
+                cfgService.getConfig(appId, IgnoreDhcpConfig.class);
 
         if (defaultConfig != null) {
             updateConfig(defaultConfig);
         }
-
         if (indirectConfig != null) {
             updateConfig(indirectConfig);
         }
+        if (ignoreDhcpConfig != null) {
+            updateConfig(ignoreDhcpConfig);
+        }
     }
 
     /**
@@ -233,12 +298,7 @@
      *
      * @param config the configuration ot update
      */
-    private void updateConfig(Config config) {
-        if (config == null) {
-            // Ignore if config is not present
-            return;
-        }
-
+    protected void updateConfig(Config config) {
         if (config instanceof IndirectDhcpRelayConfig) {
             IndirectDhcpRelayConfig indirectConfig = (IndirectDhcpRelayConfig) config;
             v4Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
@@ -248,58 +308,141 @@
             v4Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
             v6Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
         }
+        if (config instanceof IgnoreDhcpConfig) {
+            addIgnoreVlanRules((IgnoreDhcpConfig) config);
+        }
+    }
+
+    protected void removeConfig(Config config) {
+        if (config instanceof IndirectDhcpRelayConfig) {
+            v4Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+            v6Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+        } else if (config instanceof DefaultDhcpRelayConfig) {
+            v4Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+            v6Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+        }
+        if (config instanceof IgnoreDhcpConfig) {
+            ignoredVlans.forEach(this::removeIgnoreVlanRule);
+            ignoredVlans.clear();
+        }
+    }
+
+    private void addIgnoreVlanRules(IgnoreDhcpConfig config) {
+        config.ignoredVlans().forEach((deviceId, vlanId) -> {
+            if (ignoredVlans.get(deviceId).contains(vlanId)) {
+                // don't need to process if it already ignored
+                return;
+            }
+            installIgnoreVlanRule(deviceId, vlanId);
+            ignoredVlans.put(deviceId, vlanId);
+        });
+
+        Multimap<DeviceId, VlanId> removedVlans = HashMultimap.create();
+        ignoredVlans.forEach((deviceId, vlanId) -> {
+            if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
+                // not contains in new config, remove it
+                removeIgnoreVlanRule(deviceId, vlanId);
+                removedVlans.put(deviceId, vlanId);
+
+            }
+        });
+        removedVlans.forEach(ignoredVlans::remove);
+    }
+
+    private void installIgnoreVlanRule(DeviceId deviceId, VlanId vlanId) {
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            UdpPortCriterion udpDst = (UdpPortCriterion) trafficSelector.getCriterion(Criterion.Type.UDP_DST);
+            TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+                    .matchVlanId(vlanId)
+                    .build();
+
+            ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withSelector(selector)
+                    .withPriority(IGNORE_CONTROL_PRIORITY)
+                    .withTreatment(dropTreatment)
+                    .fromApp(appId)
+                    .add(new ObjectiveContext() {
+                        @Override
+                        public void onSuccess(Objective objective) {
+                            log.info("Vlan id {} from device {} ignored (UDP port {})",
+                                     vlanId, deviceId, udpDst.udpPort().toInt());
+                        }
+
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Can't ignore vlan id {} from device {} due to {}",
+                                     vlanId, deviceId, error);
+                        }
+                    });
+            flowObjectiveService.apply(deviceId, fwd);
+        });
+    }
+
+    private void removeIgnoreVlanRule(DeviceId deviceId, VlanId vlanId) {
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            UdpPortCriterion udpDst = (UdpPortCriterion) trafficSelector.getCriterion(Criterion.Type.UDP_DST);
+            TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+                    .matchVlanId(vlanId)
+                    .build();
+
+            ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withSelector(selector)
+                    .withPriority(IGNORE_CONTROL_PRIORITY)
+                    .withTreatment(dropTreatment)
+                    .fromApp(appId)
+                    .remove(new ObjectiveContext() {
+                        @Override
+                        public void onSuccess(Objective objective) {
+                            log.info("Vlan id {} from device {} ignore rule removed (UDP port {})",
+                                     vlanId, deviceId, udpDst.udpPort().toInt());
+                        }
+
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Can't remove ignore rule of vlan id {} from device {} due to {}",
+                                     vlanId, deviceId, error);
+                        }
+                    });
+            flowObjectiveService.apply(deviceId, fwd);
+        });
     }
 
     /**
      * 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);
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            packetService.requestPackets(trafficSelector, 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);
+        DHCP_SELECTORS.forEach(trafficSelector -> {
+            packetService.cancelPackets(trafficSelector, 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);
+        packetService.requestPackets(ARP_SELECTOR, 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);
+        packetService.cancelPackets(ARP_SELECTOR, PacketPriority.CONTROL, appId);
     }
 
     @Override
@@ -314,7 +457,14 @@
 
     @Override
     public Optional<MacAddress> getDhcpServerMacAddress() {
-        return v4Handler.getDhcpConnectMac();
+        // TODO: depreated it
+        DefaultDhcpRelayConfig config = cfgService.getConfig(appId, DefaultDhcpRelayConfig.class);
+        DhcpServerConfig serverConfig = config.dhcpServerConfigs().get(0);
+        Ip4Address serverip = serverConfig.getDhcpServerIp4().get();
+        return hostService.getHostsByIp(serverip)
+                .stream()
+                .map(Host::mac)
+                .findFirst();
     }
 
     /**
@@ -449,20 +599,25 @@
     private class InternalConfigListener implements NetworkConfigListener {
         @Override
         public void event(NetworkConfigEvent event) {
-            if (event.type() != NetworkConfigEvent.Type.CONFIG_ADDED &&
-                    event.type() != NetworkConfigEvent.Type.CONFIG_UPDATED) {
-                // Ignore unhandled event type
-                return;
+            switch (event.type()) {
+                case CONFIG_UPDATED:
+                case CONFIG_ADDED:
+                    event.config().ifPresent(config -> {
+                        updateConfig(config);
+                        log.info("{} updated", config.getClass().getSimpleName());
+                    });
+                    break;
+                case CONFIG_REMOVED:
+                    event.prevConfig().ifPresent(config -> {
+                        removeConfig(config);
+                        log.info("{} removed", config.getClass().getSimpleName());
+                    });
+                    break;
+                default:
+                    log.warn("Unsupported event type {}", event.type());
+                    break;
             }
-            if (!event.configClass().equals(DefaultDhcpRelayConfig.class) &&
-                    !event.configClass().equals(IndirectDhcpRelayConfig.class)) {
-                // Ignore unhandled config type
-                return;
-            }
-            event.config().ifPresent(config -> {
-                updateConfig(config);
-                log.info("Reconfigured");
-            });
+
         }
     }
 }
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
index a821be5..7f5dd46 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
@@ -46,6 +46,7 @@
      * @return IP address of DHCP server; empty value if not exist
      * @deprecated 1.12 get the address from config service
      */
+    @Deprecated
     Optional<IpAddress> getDhcpServerIp();
 
     /**
@@ -61,7 +62,7 @@
      * Gets DHCP connect Mac address.
      *
      * @return the connect Mac address of server or gateway
-     * @deprecated 1.12 get host mac from host server
+     * @deprecated 1.12 get host mac from host service
      */
     @Deprecated
     Optional<MacAddress> getDhcpConnectMac();
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
index 5a4ecde..d4ea1d8 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
@@ -66,6 +66,7 @@
 
     @Override
     protected void execute() {
+        // TODO: add indirect config
         DefaultDhcpRelayConfig cfg = CFG_SERVICE.getConfig(APP_ID, DefaultDhcpRelayConfig.class);
         if (cfg == null || cfg.dhcpServerConfigs().size() == 0) {
             print(MISSING_SERVER_CFG);
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
new file mode 100644
index 0000000..b4b8adb
--- /dev/null
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.dhcprelay.config;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class IgnoreDhcpConfig extends Config<ApplicationId> {
+    public static final String KEY = "ignoreDhcp";
+    private static final String DEVICE_ID = "deviceId";
+    private static final String VLAN_ID = "vlan";
+
+    @Override
+    public boolean isValid() {
+        AtomicBoolean valid = new AtomicBoolean(true);
+        if (array == null) {
+            return false;
+        }
+        array.forEach(node -> {
+            valid.compareAndSet(true, node.has(DEVICE_ID) && node.has(VLAN_ID));
+        });
+        return valid.get();
+    }
+
+    public Multimap<DeviceId, VlanId> ignoredVlans() {
+        Multimap<DeviceId, VlanId> ignored = ArrayListMultimap.create();
+
+        array.forEach(node -> {
+            DeviceId deviceId = DeviceId.deviceId(node.get(DEVICE_ID).asText());
+            VlanId vlanId = VlanId.vlanId((short) node.get(VLAN_ID).asInt());
+            ignored.put(deviceId, vlanId);
+        });
+
+        return ignored;
+    }
+}
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 45cfa23..4494ddb 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -15,12 +15,17 @@
  */
 package org.onosproject.dhcprelay;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 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 com.google.common.io.Resources;
 import org.apache.commons.io.Charsets;
+import org.easymock.Capture;
+import org.easymock.CaptureType;
 import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Before;
@@ -45,12 +50,22 @@
 import org.onosproject.dhcprelay.api.DhcpHandler;
 import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
 import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
 import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
 import org.onosproject.dhcprelay.store.DhcpRecord;
 import org.onosproject.dhcprelay.store.DhcpRelayStore;
 import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
+import org.onosproject.net.DeviceId;
+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.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceServiceAdapter;
+import org.onosproject.net.packet.PacketPriority;
 import org.onosproject.routeservice.Route;
 import org.onosproject.routeservice.RouteStoreAdapter;
 import org.onosproject.net.ConnectPoint;
@@ -86,8 +101,12 @@
 
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.*;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
 
 public class DhcpRelayManagerTest {
+    private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+    private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+    private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
     // 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);
@@ -165,6 +184,8 @@
             SERVER_INTERFACE
     );
     private static final String NON_ONOS_CID = "Non-ONOS circuit ID";
+    private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+    private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
 
     private DhcpRelayManager manager;
     private MockPacketService packetService;
@@ -204,6 +225,7 @@
         manager.dhcpRelayStore = mockDhcpRelayStore;
 
         manager.interfaceService = new MockInterfaceService();
+        manager.flowObjectiveService = EasyMock.niceMock(FlowObjectiveService.class);
 
         Dhcp4HandlerImpl v4Handler = new Dhcp4HandlerImpl();
         v4Handler.dhcpRelayStore = mockDhcpRelayStore;
@@ -307,6 +329,8 @@
     public void testWithRelayAgentConfig() throws DeserializationException {
         manager.v4Handler
                 .setDefaultDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
+        manager.v4Handler
+                .setIndirectDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
         packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
                                                                      CLIENT2_VLAN,
                                                                      CLIENT2_CP,
@@ -333,6 +357,95 @@
         assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
     }
 
+    /**
+     * Ignores specific vlans from specific devices if config.
+     *
+     * @throws Exception the exception from this test
+     */
+    @Test
+    public void testIgnoreVlan() throws Exception {
+        ObjectMapper om = new ObjectMapper();
+        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+        expectLastCall().times(2);
+        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+        expectLastCall().times(2);
+        replay(manager.flowObjectiveService);
+        manager.updateConfig(config);
+        verify(manager.flowObjectiveService);
+
+        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+
+        for (int index = 0; index < objectivesFromDev1.size(); index++) {
+            TrafficSelector selector =
+                    DefaultTrafficSelector.builder(DhcpRelayManager.DHCP_SELECTORS.get(index))
+                    .matchVlanId(IGNORED_VLAN)
+                    .build();
+            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+            assertEquals(selector, fwd.selector());
+            assertEquals(dropTreatment, fwd.treatment());
+            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+            assertEquals(Objective.Operation.ADD, fwd.op());
+        }
+
+        assertEquals(2, manager.ignoredVlans.size());
+    }
+
+    /**
+     * "IgnoreVlan" policy should be removed when the config removed.
+     */
+    @Test
+    public void testRemoveIgnoreVlan() {
+        manager.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
+        manager.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+
+        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+        expectLastCall().times(2);
+        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+        manager.flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+        expectLastCall().times(2);
+        replay(manager.flowObjectiveService);
+        manager.removeConfig(config);
+        verify(manager.flowObjectiveService);
+
+        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+        TrafficTreatment dropTreatment = DefaultTrafficTreatment.emptyTreatment();
+        dropTreatment.clearedDeferred();
+
+        for (int index = 0; index < objectivesFromDev1.size(); index++) {
+            TrafficSelector selector =
+                    DefaultTrafficSelector.builder(DhcpRelayManager.DHCP_SELECTORS.get(index))
+                    .matchVlanId(IGNORED_VLAN)
+                    .build();
+            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+            assertEquals(selector, fwd.selector());
+            assertEquals(dropTreatment, fwd.treatment());
+            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+            assertEquals(Objective.Operation.REMOVE, fwd.op());
+        }
+        assertEquals(0, manager.ignoredVlans.size());
+    }
+
     private static class MockDefaultDhcpRelayConfig extends DefaultDhcpRelayConfig {
         @Override
         public boolean isValid() {
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
index 4160e06..c743247 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
@@ -27,7 +27,6 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 
-
 import java.io.IOException;
 
 import static org.junit.Assert.*;
@@ -45,7 +44,6 @@
     private static final Ip4Address DEFAULT_GATEWAY_IP = Ip4Address.valueOf("192.168.10.254");
     private static final Ip6Address DEFAULT_SERVER_IP_V6 = Ip6Address.valueOf("2000::200:1");
     private static final Ip6Address DEFAULT_GATEWAY_IP_V6 = Ip6Address.valueOf("1000::100:1");
-
     private static final ConnectPoint INDIRECT_CONNECT_POINT = ConnectPoint.deviceConnectPoint("of:0000000000000002/3");
     private static final Ip4Address INDIRECT_SERVER_IP = Ip4Address.valueOf("172.168.10.3");
 
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
new file mode 100644
index 0000000..3712c06
--- /dev/null
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.dhcprelay.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Resources;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
+
+public class IgnoreDhcpConfigTest {
+    private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+    private static final ApplicationId APP_ID = new TestApplicationId("DhcpRelayTest");
+    private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+    private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
+    private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+    @Test
+    public void testIgnoredDhcpConfig() throws IOException {
+        ObjectMapper om = new ObjectMapper();
+        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+        assertEquals(2, config.ignoredVlans().size());
+        Collection<VlanId> vlanForDev1 = config.ignoredVlans().get(DEV_1_ID);
+        Collection<VlanId> vlanForDev2 = config.ignoredVlans().get(DEV_2_ID);
+
+        assertEquals(1, vlanForDev1.size());
+        assertEquals(1, vlanForDev2.size());
+
+        assertTrue(vlanForDev1.contains(IGNORED_VLAN));
+        assertTrue(vlanForDev2.contains(IGNORED_VLAN));
+    }
+}
diff --git a/apps/dhcprelay/src/test/resources/dhcp-relay.json b/apps/dhcprelay/src/test/resources/dhcp-relay.json
index dc724ee..e77d8a7 100644
--- a/apps/dhcprelay/src/test/resources/dhcp-relay.json
+++ b/apps/dhcprelay/src/test/resources/dhcp-relay.json
@@ -15,6 +15,10 @@
           "serverIps": ["172.168.10.3"],
           "relayAgentIps": ["10.0.1.1"]
         }
+      ],
+      "ignoreDhcp": [
+        {"deviceId": "of:0000000000000001", "vlan": 100},
+        {"deviceId": "of:0000000000000002", "vlan": 100}
       ]
     }
   }
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index 515640b..ad9329a 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -40,6 +40,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
@@ -52,19 +53,25 @@
  */
 public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
 
-    // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
     /*
     When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
     issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
      */
+    // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
     private boolean deleteEntryBeforeUpdate = true;
 
-    // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
     /*
     If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
      */
+    // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
     private boolean checkEntryStoreBeforeUpdate = true;
 
+    /*
+    If true, we avoid querying the device and return the content of the ENTRY_STORE.
+     */
+    // TODO: can remove this check as soon as the BMv2 bug when reading ECMP entries is fixed.
+    private boolean ignoreDeviceWhenGet = true;
+
     // Needed to synchronize operations over the same table entry.
     private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
 
@@ -101,6 +108,14 @@
             return Collections.emptyList();
         }
 
+        if (ignoreDeviceWhenGet) {
+            return ENTRY_STORE.values().stream()
+                    .filter(frWrapper -> frWrapper.rule().deviceId().equals(this.deviceId))
+                    .map(frWrapper -> new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
+                                                           0, 0))
+                    .collect(Collectors.toList());
+        }
+
         ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
         List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
 
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/meter/impl/MeterManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/meter/impl/MeterManager.java
index bd61a5d..f7418ab 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/meter/impl/MeterManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/meter/impl/MeterManager.java
@@ -19,6 +19,7 @@
 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.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
@@ -54,8 +55,12 @@
 
 import java.util.Collection;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -68,6 +73,16 @@
         implements MeterService, MeterProviderRegistry {
 
     private static final String METERCOUNTERIDENTIFIER = "meter-id-counter-%s";
+    private static final String NUM_THREAD = "numThreads";
+    private static final String WORKER_PATTERN = "installer-%d";
+    private static final String GROUP_THREAD_NAME = "onos/meter";
+
+    private static final int DEFAULT_NUM_THREADS = 4;
+    @Property(name = NUM_THREAD,
+            intValue = DEFAULT_NUM_THREADS,
+            label = "Number of worker threads")
+    private int numThreads = DEFAULT_NUM_THREADS;
+
     private final Logger log = getLogger(getClass());
     private final MeterStoreDelegate delegate = new InternalMeterStoreDelegate();
 
@@ -85,6 +100,8 @@
 
     private TriConsumer<MeterRequest, MeterStoreResult, Throwable> onComplete;
 
+    private ExecutorService executorService;
+
     @Activate
     public void activate() {
         store.setDelegate(delegate);
@@ -104,6 +121,9 @@
                 });
 
             };
+
+        executorService = newFixedThreadPool(numThreads,
+                groupedThreads(GROUP_THREAD_NAME, WORKER_PATTERN, log));
         log.info("Started");
     }
 
@@ -111,6 +131,7 @@
     public void deactivate() {
         store.unsetDelegate(delegate);
         eventDispatcher.removeSink(MeterEvent.class);
+        executorService.shutdown();
         log.info("Stopped");
     }
 
@@ -308,21 +329,22 @@
         @Override
         public void notify(MeterEvent event) {
             DeviceId deviceId = event.subject().deviceId();
-            MeterProvider p = getProvider(event.subject().deviceId());
             switch (event.type()) {
                 case METER_ADD_REQ:
-                    p.performMeterOperation(deviceId, new MeterOperation(event.subject(),
-                                                                         MeterOperation.Type.ADD));
+                    executorService.execute(new MeterInstaller(deviceId, event.subject(),
+                            MeterOperation.Type.ADD));
                     break;
                 case METER_REM_REQ:
-                    p.performMeterOperation(deviceId, new MeterOperation(event.subject(),
-                                                                         MeterOperation.Type.REMOVE));
+                    executorService.execute(new MeterInstaller(deviceId, event.subject(),
+                            MeterOperation.Type.REMOVE));
                     break;
                 case METER_ADDED:
                     log.info("Meter added {}", event.subject());
+                    post(new MeterEvent(MeterEvent.Type.METER_ADDED, event.subject()));
                     break;
                 case METER_REMOVED:
                     log.info("Meter removed {}", event.subject());
+                    post(new MeterEvent(MeterEvent.Type.METER_REMOVED, event.subject()));
                     break;
                 default:
                     log.warn("Unknown meter event {}", event.type());
@@ -330,5 +352,29 @@
 
         }
     }
+    /**
+     * Task that passes the meter down to the provider.
+     */
+    private class MeterInstaller implements Runnable {
+        private final DeviceId deviceId;
+        private final Meter meter;
+        private final MeterOperation.Type op;
+
+        public MeterInstaller(DeviceId deviceId, Meter meter, MeterOperation.Type op) {
+            this.deviceId = checkNotNull(deviceId);
+            this.meter = checkNotNull(meter);
+            this.op = checkNotNull(op);
+        }
+
+        @Override
+        public void run() {
+            MeterProvider p = getProvider(this.deviceId);
+            if (p == null) {
+                log.error("Unable to recover {}'s provider", deviceId);
+                return;
+            }
+            p.performMeterOperation(deviceId, new MeterOperation(meter, op));
+        }
+    }
 
 }
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
index 418807c..1c8eacd 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
@@ -21,16 +21,16 @@
 hw_version=하드웨어 버전
 sw_version=소프트웨어 버전
 serial_number=시리얼 번호
-app_id=어플리케이션 아아디
+app_id=애플리케이션 아이디
 
 type=종류
-vendor=벤더
+vendor=제조사
 icon=아이콘
 title=이름
 state=상태
 category=카테고리
 version=버전
-origin=제조사
+origin=Origin
 role=역할
 
 latitude=위도
diff --git a/web/gui/src/main/webapp/app/fw/svg/glyphData.js b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
index 083acbd..386a74c 100644
--- a/web/gui/src/main/webapp/app/fw/svg/glyphData.js
+++ b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
@@ -294,6 +294,9 @@
             download: 'M90.3,94.5H19.7V79.2H90.3V94.5Z' +
             'm-49.1-79V44H26.2L55,72.3,83.8,44H68.9V15.5H41.1Z',
 
+            upload: 'M90.3,79.4H19.7V94.6H90.3V79.4Z' +
+            'M41.1,71.8V43.5H26.2L55,15.4,83.8,43.5H68.9V71.8H41.1Z',
+
             // --- Navigation glyphs ------------------------------------
 
             flowTable: tableFrame +
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index 5dc8a3d..9027960 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -37,6 +37,7 @@
         play: 'play',
         stop: 'stop',
 
+        upload: 'upload',
         download: 'download',
         delta: 'delta',
         nonzero: 'nonzero',
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 1d6c94a..54a5390 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -19,7 +19,7 @@
                        file-model="appFile">
             </form>
 
-            <div icon icon-size="42" icon-id="plus"
+            <div icon icon-size="42" icon-id="upload"
                  class="active" trigger-form
                  tooltip tt-msg="uploadTip">
             </div>
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
index 0d6e17e..73c1d6c 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
@@ -21,7 +21,7 @@
 describe('factory: fw/svg/glyph.js', function() {
     var $log, fs, gs, d3Elem, svg;
 
-    var numBaseGlyphs = 104,
+    var numBaseGlyphs = 105,
         vbBird = '352 224 113 112',
         vbGlyph = '0 0 110 110',
         vbBadge = '0 0 10 10',