[CORD-46] Implement L2 switching in Segment Routing

DONE
- Update SpringOpenTTP to support bridging table emulation
- Populate low priority subnet broadcast entry for bridging table
- Move IP entry population to host event handler as well
- Update ArpHandler to handle intra-rack ARP forwarding/flooding
- Move TTL_OUT action from IP table to corresponding group
    Since hardware does not support TTL_OUT in the IP table
- Populate entries to bridging table (MAC learning)
- Emulate src mac table

Not in this submission
- Emulate src-mac table behavior
- Pop vlan in the group instead of the flow

Change-Id: Ib69357c1889ccddaa4daa272d9f5843790ee1a3c
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 96c85ba..2c6412c 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -20,10 +20,10 @@
 import org.onlab.packet.Ip4Address;
 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.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -60,12 +60,21 @@
 
     /**
      * Processes incoming ARP packets.
+     *
      * If it is an ARP request to router itself or known hosts,
      * then it sends ARP response.
      * If it is an ARP request to unknown hosts in its own subnet,
      * then it flood the ARP request to the ports.
      * If it is an ARP response, then set a flow rule for the host
      * and forward any IP packets to the host in the packet buffer to the host.
+     * <p>
+     * Note: We handles all ARP packet in, even for those ARP packets between
+     * hosts in the same subnet.
+     * For an ARP packet with broadcast destination MAC,
+     * some switches pipelines will send it to the controller due to table miss,
+     * other swithches will flood the packets directly in the data plane without
+     * packet in.
+     * We can deal with both cases.
      *
      * @param pkt incoming packet
      */
@@ -86,29 +95,56 @@
         if (arp.getOpCode() == ARP.OP_REQUEST) {
             handleArpRequest(deviceId, connectPoint, ethernet);
         } else {
-            srManager.ipHandler.forwardPackets(deviceId, hostIpAddress);
+            handleArpReply(deviceId, connectPoint, ethernet);
         }
     }
 
     private void handleArpRequest(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) {
         ARP arpRequest = (ARP) payload.getPayload();
+        VlanId vlanId = VlanId.vlanId(payload.getVlanID());
         HostId targetHostId = HostId.hostId(MacAddress.valueOf(
-                arpRequest.getTargetHardwareAddress()));
+                                            arpRequest.getTargetHardwareAddress()),
+                                            vlanId);
 
-        // ARP request for router
+        // ARP request for router. Send ARP reply.
         if (isArpReqForRouter(deviceId, arpRequest)) {
             Ip4Address targetAddress = Ip4Address.valueOf(arpRequest.getTargetProtocolAddress());
-
-            sendArpResponse(arpRequest, config.getRouterMacForAGatewayIp(targetAddress));
+            sendArpResponse(arpRequest, config.getRouterMacForAGatewayIp(targetAddress), vlanId);
         } else {
             Host targetHost = srManager.hostService.getHost(targetHostId);
-            // ARP request for known hosts
+            // ARP request for known hosts. Send proxy ARP reply on behalf of the target.
             if (targetHost != null) {
-                sendArpResponse(arpRequest, targetHost.mac());
+                removeVlanAndForward(payload, targetHost.location());
+            // ARP request for unknown host in the subnet. Flood in the subnet.
+            } else {
+                removeVlanAndFlood(payload, inPort);
+            }
+        }
+    }
 
-            // ARP request for unknown host in the subnet
-            } else if (isArpReqForSubnet(deviceId, arpRequest)) {
-                flood(payload, inPort);
+    private void handleArpReply(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) {
+        ARP arpReply = (ARP) payload.getPayload();
+        VlanId vlanId = VlanId.vlanId(payload.getVlanID());
+        HostId targetHostId = HostId.hostId(MacAddress.valueOf(
+                                            arpReply.getTargetHardwareAddress()),
+                                            vlanId);
+
+        // ARP reply for router. Process all pending IP packets.
+        if (isArpReqForRouter(deviceId, arpReply)) {
+            Ip4Address hostIpAddress = Ip4Address.valueOf(arpReply.getSenderProtocolAddress());
+            srManager.ipHandler.forwardPackets(deviceId, hostIpAddress);
+        } else {
+            Host targetHost = srManager.hostService.getHost(targetHostId);
+            // ARP reply for known hosts. Forward to the host.
+            if (targetHost != null) {
+                removeVlanAndForward(payload, targetHost.location());
+            // ARP reply for unknown host, Flood in the subnet.
+            } else {
+                // Don't flood to non-edge ports
+                if (vlanId.equals(VlanId.vlanId(srManager.ASSIGNED_VLAN_NO_SUBNET))) {
+                    return;
+                }
+                removeVlanAndFlood(payload, inPort);
             }
         }
     }
@@ -126,14 +162,6 @@
         return false;
     }
 
-    private boolean isArpReqForSubnet(DeviceId deviceId, ARP arpRequest) {
-        return config.getSubnets(deviceId).stream()
-                     .anyMatch((prefix)->
-                     prefix.contains(Ip4Address.
-                                     valueOf(arpRequest.
-                                             getTargetProtocolAddress())));
-    }
-
     /**
      * Sends an APR request for the target IP address to all ports except in-port.
      *
@@ -170,11 +198,10 @@
                 .setSourceMACAddress(senderMacAddress)
                 .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
 
-        flood(eth, inPort);
+        removeVlanAndFlood(eth, inPort);
     }
 
-    private void sendArpResponse(ARP arpRequest, MacAddress targetMac) {
-
+    private void sendArpResponse(ARP arpRequest, MacAddress targetMac, VlanId vlanId) {
         ARP arpReply = new ARP();
         arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
                 .setProtocolType(ARP.PROTO_TYPE_IP)
@@ -193,8 +220,9 @@
                 .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply);
 
 
-        HostId dstId = HostId.hostId(MacAddress.valueOf(
-                arpReply.getTargetHardwareAddress()));
+        HostId dstId = HostId.hostId(
+                MacAddress.valueOf(arpReply.getTargetHardwareAddress()),
+                vlanId);
         Host dst = srManager.hostService.getHost(dstId);
         if (dst == null) {
             log.warn("Cannot send ARP response to unknown device");
@@ -209,19 +237,51 @@
         srManager.packetService.emit(packet);
     }
 
-    private void flood(Ethernet request, ConnectPoint inPort) {
-        TrafficTreatment.Builder builder;
-        ByteBuffer buf = ByteBuffer.wrap(request.serialize());
+    /**
+     * Remove VLAN tag and flood to all ports in the same subnet.
+     *
+     * @param packet packet to be flooded
+     * @param inPort where the packet comes from
+     */
+    private void removeVlanAndFlood(Ethernet packet, ConnectPoint inPort) {
+        Ip4Address targetProtocolAddress = Ip4Address.valueOf(
+                ((ARP) packet.getPayload()).getTargetProtocolAddress()
+        );
 
-        for (Port port: srManager.deviceService.getPorts(inPort.deviceId())) {
-            if (!port.number().equals(inPort.port()) &&
-                    port.number().toLong() > 0) {
-                builder = DefaultTrafficTreatment.builder();
-                builder.setOutput(port.number());
-                srManager.packetService.emit(new DefaultOutboundPacket(inPort.deviceId(),
-                        builder.build(), buf));
+        srManager.deviceConfiguration.getSubnetPortsMap(inPort.deviceId()).forEach((subnet, ports) -> {
+            if (subnet.contains(targetProtocolAddress)) {
+                ports.stream()
+                        .filter(port -> port != inPort.port())
+                        .forEach(port -> {
+                            removeVlanAndForward(packet, new ConnectPoint(inPort.deviceId(), port));
+                        });
             }
-        }
+        });
     }
 
+    /**
+     * Remove VLAN tag and packet out to given port.
+     *
+     * Note: In current implementation, we expect all communication with
+     * end hosts within a subnet to be untagged.
+     * <p>
+     * For those pipelines that internally assigns a VLAN, the VLAN tag will be
+     * removed before egress.
+     * <p>
+     * For those pipelines that do not assign internal VLAN, the packet remains
+     * untagged.
+     *
+     * @param packet packet to be forwarded
+     * @param outPort where the packet should be forwarded
+     */
+    private void removeVlanAndForward(Ethernet packet, ConnectPoint outPort) {
+        packet.setEtherType(Ethernet.TYPE_ARP);
+        packet.setVlanID(Ethernet.VLAN_UNTAGGED);
+        ByteBuffer buf = ByteBuffer.wrap(packet.serialize());
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.setOutput(outPort.port());
+        srManager.packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
+                                                               tbuilder.build(), buf));
+    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index c4267eb..eb3b3fd 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -105,8 +105,17 @@
         }
     }
 
+    /**
+     * Sends an ICMP reply message.
+     *
+     * Note: we assume that packets sending from the edge switches to the hosts
+     * have untagged VLAN.
+     * @param icmpRequest the original ICMP request
+     * @param outport the output port where the ICMP reply should be sent to
+     */
     private void sendICMPResponse(Ethernet icmpRequest, ConnectPoint outport) {
-
+        // Note: We assume that packets arrive at the edge switches have
+        // untagged VLAN.
         Ethernet icmpReplyEth = new Ethernet();
 
         IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
@@ -129,7 +138,6 @@
         icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4);
         icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress());
         icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress());
-        icmpReplyEth.setVlanID(icmpRequest.getVlanID());
 
         Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress());
         Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index f827403..bc3ce8c 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -55,7 +55,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 public class RoutingRulePopulator {
-
     private static final Logger log = LoggerFactory
             .getLogger(RoutingRulePopulator.class);
 
@@ -105,13 +104,45 @@
      */
     public void populateIpRuleForHost(DeviceId deviceId, Ip4Address hostIp,
                                       MacAddress hostMac, PortNumber outPort) {
-        MacAddress deviceMac;
+        log.debug("Populate IP table entry for host {} at {}:{}",
+                hostIp, deviceId, outPort);
+        ForwardingObjective.Builder fwdBuilder;
         try {
-            deviceMac = config.getDeviceMac(deviceId);
+            fwdBuilder = getForwardingObjectiveBuilder(
+                    deviceId, hostIp, hostMac, outPort);
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting populateIpRuleForHost.");
             return;
         }
+        srManager.flowObjectiveService.
+            forward(deviceId, fwdBuilder.add(new SRObjectiveContext(deviceId,
+                    SRObjectiveContext.ObjectiveType.FORWARDING)));
+        rulePopulationCounter.incrementAndGet();
+    }
+
+    public void revokeIpRuleForHost(DeviceId deviceId, Ip4Address hostIp,
+            MacAddress hostMac, PortNumber outPort) {
+        log.debug("Revoke IP table entry for host {} at {}:{}",
+                hostIp, deviceId, outPort);
+        ForwardingObjective.Builder fwdBuilder;
+        try {
+            fwdBuilder = getForwardingObjectiveBuilder(
+                    deviceId, hostIp, hostMac, outPort);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting revokeIpRuleForHost.");
+            return;
+        }
+        srManager.flowObjectiveService.
+                forward(deviceId, fwdBuilder.remove(new SRObjectiveContext(deviceId,
+                        SRObjectiveContext.ObjectiveType.FORWARDING)));
+    }
+
+    private ForwardingObjective.Builder getForwardingObjectiveBuilder(
+            DeviceId deviceId, Ip4Address hostIp,
+            MacAddress hostMac, PortNumber outPort)
+            throws DeviceConfigNotFoundException {
+        MacAddress deviceMac;
+        deviceMac = config.getDeviceMac(deviceId);
 
         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
@@ -127,19 +158,10 @@
         TrafficTreatment treatment = tbuilder.build();
         TrafficSelector selector = sbuilder.build();
 
-        ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
-                .builder().fromApp(srManager.appId).makePermanent()
+        return DefaultForwardingObjective.builder()
+                .fromApp(srManager.appId).makePermanent()
                 .withSelector(selector).withTreatment(treatment)
                 .withPriority(100).withFlag(ForwardingObjective.Flag.SPECIFIC);
-
-        log.debug("Installing IPv4 forwarding objective "
-                + "for host {} in switch {}", hostIp, deviceId);
-        srManager.flowObjectiveService.
-            forward(deviceId,
-                    fwdBuilder.
-                    add(new SRObjectiveContext(deviceId,
-                                           SRObjectiveContext.ObjectiveType.FORWARDING)));
-        rulePopulationCounter.incrementAndGet();
     }
 
     /**
@@ -186,26 +208,25 @@
         }
 
         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
-
         sbuilder.matchIPDst(ipPrefix);
         sbuilder.matchEthType(Ethernet.TYPE_IPV4);
+        TrafficSelector selector = sbuilder.build();
 
-        NeighborSet ns = null;
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        NeighborSet ns;
+        TrafficTreatment treatment;
 
         // If the next hop is the same as the final destination, then MPLS label
         // is not set.
         if (nextHops.size() == 1 && nextHops.toArray()[0].equals(destSw)) {
-            tbuilder.deferred().decNwTtl();
+            tbuilder.immediate().decNwTtl();
             ns = new NeighborSet(nextHops);
+            treatment = tbuilder.build();
         } else {
-            tbuilder.deferred().copyTtlOut();
             ns = new NeighborSet(nextHops, segmentId);
+            treatment = null;
         }
 
-        TrafficTreatment treatment = tbuilder.build();
-        TrafficSelector selector = sbuilder.build();
-
         if (srManager.getNextObjectiveId(deviceId, ns) <= 0) {
             log.warn("No next objective in {} for ns: {}", deviceId, ns);
             return false;
@@ -216,10 +237,12 @@
                 .fromApp(srManager.appId)
                 .makePermanent()
                 .nextStep(srManager.getNextObjectiveId(deviceId, ns))
-                .withTreatment(treatment)
                 .withSelector(selector)
                 .withPriority(100)
                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
+        if (treatment != null) {
+            fwdBuilder.withTreatment(treatment);
+        }
         log.debug("Installing IPv4 forwarding objective "
                         + "for router IP/subnet {} in switch {}",
                 ipPrefix,
@@ -423,8 +446,6 @@
                         ? VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)
                         : srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
 
-
-
                 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
                 fob.withKey(Criteria.matchInPort(port.number()))
                 .addCondition(Criteria.matchEthDst(deviceMac))
@@ -469,14 +490,14 @@
         Set<Ip4Address> allIps = new HashSet<Ip4Address>(config.getPortIPs(deviceId));
         allIps.add(routerIp);
         for (Ip4Address ipaddr : allIps) {
-            TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-            TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-            selector.matchEthType(Ethernet.TYPE_IPV4);
-            selector.matchIPDst(IpPrefix.valueOf(ipaddr,
+            TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+            TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+            sbuilder.matchEthType(Ethernet.TYPE_IPV4);
+            sbuilder.matchIPDst(IpPrefix.valueOf(ipaddr,
                                                  IpPrefix.MAX_INET_MASK_LENGTH));
-            treatment.setOutput(PortNumber.CONTROLLER);
-            puntIp.withSelector(selector.build());
-            puntIp.withTreatment(treatment.build());
+            tbuilder.setOutput(PortNumber.CONTROLLER);
+            puntIp.withSelector(sbuilder.build());
+            puntIp.withTreatment(tbuilder.build());
             puntIp.withFlag(Flag.VERSATILE)
                 .withPriority(HIGHEST_PRIORITY)
                 .makePermanent()
@@ -489,6 +510,48 @@
         }
     }
 
+    /**
+     * Populates a forwarding objective to send packets that miss other high
+     * priority Bridging Table entries to a group that contains all ports of
+     * its subnet.
+     *
+     * Note: We assume that packets sending from the edge switches to the hosts
+     * have untagged VLAN.
+     * The VLAN tag will be popped later in the flooding group.
+     *
+     * @param deviceId switch ID to set the rules
+     */
+    public void populateSubnetBroadcastRule(DeviceId deviceId) {
+        config.getSubnets(deviceId).forEach(subnet -> {
+            int nextId = srManager.getSubnetNextObjectiveId(deviceId, subnet);
+            VlanId vlanId = srManager.getSubnetAssignedVlanId(deviceId, subnet);
+
+            /* Driver should treat objective with MacAddress.NONE as the
+             * subnet broadcast rule
+             */
+            TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+            sbuilder.matchVlanId(vlanId);
+            sbuilder.matchEthDst(MacAddress.NONE);
+
+            ForwardingObjective.Builder fob = DefaultForwardingObjective.builder();
+            fob.withFlag(Flag.SPECIFIC)
+                    .withSelector(sbuilder.build())
+                    .nextStep(nextId)
+                    .withPriority(5)
+                    .fromApp(srManager.appId)
+                    .makePermanent();
+
+            srManager.flowObjectiveService.forward(
+                    deviceId,
+                    fob.add(new SRObjectiveContext(
+                                    deviceId,
+                                    SRObjectiveContext.ObjectiveType.FORWARDING)
+                    )
+            );
+        });
+    }
+
+
     private PortNumber selectOnePort(DeviceId srcId, Set<DeviceId> destIds) {
 
         Set<Link> links = srManager.linkService.getDeviceLinks(srcId);
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 787f934..84fe516 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -22,6 +22,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
@@ -33,11 +34,23 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.event.Event;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.basics.SubjectFactories;
+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.DefaultForwardingObjective;
+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.host.HostEvent;
+import org.onosproject.net.host.HostListener;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingConfig;
@@ -139,11 +152,13 @@
 
     private static ScheduledFuture<?> eventHandlerFuture = null;
     private ConcurrentLinkedQueue<Event> eventQueue = new ConcurrentLinkedQueue<Event>();
-    private Map<DeviceId, DefaultGroupHandler> groupHandlerMap = new ConcurrentHashMap<DeviceId, DefaultGroupHandler>();
+    private Map<DeviceId, DefaultGroupHandler> groupHandlerMap =
+            new ConcurrentHashMap<DeviceId, DefaultGroupHandler>();
     // Per device next objective ID store with (device id + neighbor set) as key
-    private EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
-        Integer> nsNextObjStore = null;
-    private EventuallyConsistentMap<SubnetNextObjectiveStoreKey, Integer> subnetNextObjStore = null;
+    private EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey, Integer>
+            nsNextObjStore = null;
+    private EventuallyConsistentMap<SubnetNextObjectiveStoreKey, Integer>
+            subnetNextObjStore = null;
     private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
     private EventuallyConsistentMap<String, Policy> policyStore = null;
     // Per device, per-subnet assigned-vlans store, with (device id + subnet
@@ -170,6 +185,8 @@
                 }
             };
 
+    private final HostListener hostListener = new InternalHostListener();
+
     private Object threadSchedulerLock = new Object();
     private static int numOfEventsQueued = 0;
     private static int numOfEventsExecuted = 0;
@@ -259,6 +276,8 @@
         cfgService.addListener(cfgListener);
         cfgService.registerConfigFactory(cfgFactory);
 
+        hostService.addListener(hostListener);
+
         processor = new InternalPacketProcessor();
         linkListener = new InternalLinkListener();
         deviceListener = new InternalDeviceListener();
@@ -637,6 +656,7 @@
         if (mastershipService.isLocalMaster(device.id())) {
             DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id());
             groupHandler.createGroupsFromSubnetConfig();
+            routingRulePopulator.populateSubnetBroadcastRule(device.id());
         }
     }
 
@@ -703,6 +723,7 @@
                 if (mastershipService.isLocalMaster(device.id())) {
                     DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id());
                     groupHandler.createGroupsFromSubnetConfig();
+                    routingRulePopulator.populateSubnetBroadcastRule(device.id());
                 }
             }
 
@@ -723,4 +744,205 @@
             }
         }
     }
+
+    private class InternalHostListener implements HostListener {
+        private ForwardingObjective.Builder getForwardingObjectiveBuilder(
+                MacAddress mac, VlanId vlanId, PortNumber port) {
+            TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+            sbuilder.matchEthDst(mac);
+            sbuilder.matchVlanId(vlanId);
+
+            TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+            // TODO Move popVlan from flow action to group action
+            tbuilder.immediate().popVlan();
+            tbuilder.immediate().setOutput(port);
+
+            return DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                    .withSelector(sbuilder.build())
+                    .withTreatment(tbuilder.build())
+                    .withPriority(100)
+                    .fromApp(appId)
+                    .makePermanent();
+        }
+
+        private void processHostAddedEvent(HostEvent event) {
+            MacAddress mac = event.subject().mac();
+            VlanId vlanId = event.subject().vlan();
+            DeviceId deviceId = event.subject().location().deviceId();
+            PortNumber port = event.subject().location().port();
+            Set<IpAddress> ips = event.subject().ipAddresses();
+            log.debug("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port);
+
+            // TODO Move bridging table population to a separate class
+            // Populate bridging table entry
+            ForwardingObjective.Builder fob =
+                    getForwardingObjectiveBuilder(mac, vlanId, port);
+            flowObjectiveService.forward(deviceId, fob.add(
+                    new BridgingTableObjectiveContext(mac, vlanId)
+            ));
+
+            // Populate IP table entry
+            ips.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.populateIpRuleForHost(
+                            deviceId, ip.getIp4Address(), mac, port);
+                }
+            });
+        }
+
+        private void processHostRemoveEvent(HostEvent event) {
+            MacAddress mac = event.subject().mac();
+            VlanId vlanId = event.subject().vlan();
+            DeviceId deviceId = event.subject().location().deviceId();
+            PortNumber port = event.subject().location().port();
+            Set<IpAddress> ips = event.subject().ipAddresses();
+            log.debug("Host {}/{} is removed from {}:{}", mac, vlanId, deviceId, port);
+
+            // Revoke bridging table entry
+            ForwardingObjective.Builder fob =
+                    getForwardingObjectiveBuilder(mac, vlanId, port);
+            flowObjectiveService.forward(deviceId, fob.remove(
+                    new BridgingTableObjectiveContext(mac, vlanId)
+            ));
+
+            // Revoke IP table entry
+            ips.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.revokeIpRuleForHost(
+                            deviceId, ip.getIp4Address(), mac, port);
+                }
+            });
+        }
+
+        private void processHostMovedEvent(HostEvent event) {
+            MacAddress mac = event.subject().mac();
+            VlanId vlanId = event.subject().vlan();
+            DeviceId prevDeviceId = event.prevSubject().location().deviceId();
+            PortNumber prevPort = event.prevSubject().location().port();
+            Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
+            DeviceId newDeviceId = event.subject().location().deviceId();
+            PortNumber newPort = event.subject().location().port();
+            Set<IpAddress> newIps = event.subject().ipAddresses();
+            log.debug("Host {}/{} is moved from {}:{} to {}:{}",
+                    mac, vlanId, prevDeviceId, prevPort, newDeviceId, newPort);
+
+            // Revoke previous bridging table entry
+            ForwardingObjective.Builder prevFob =
+                    getForwardingObjectiveBuilder(mac, vlanId, prevPort);
+            flowObjectiveService.forward(prevDeviceId, prevFob.remove(
+                    new BridgingTableObjectiveContext(mac, vlanId)
+            ));
+
+            // Revoke previous IP table entry
+            prevIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.revokeIpRuleForHost(
+                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
+                }
+            });
+
+            // Populate new bridging table entry
+            ForwardingObjective.Builder newFob =
+                    getForwardingObjectiveBuilder(mac, vlanId, prevPort);
+            flowObjectiveService.forward(newDeviceId, newFob.add(
+                    new BridgingTableObjectiveContext(mac, vlanId)
+            ));
+
+            // Populate new IP table entry
+            newIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.populateIpRuleForHost(
+                            newDeviceId, ip.getIp4Address(), mac, newPort);
+                }
+            });
+        }
+
+        private void processHostUpdatedEvent(HostEvent event) {
+            MacAddress mac = event.subject().mac();
+            VlanId vlanId = event.subject().vlan();
+            DeviceId prevDeviceId = event.prevSubject().location().deviceId();
+            PortNumber prevPort = event.prevSubject().location().port();
+            Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
+            DeviceId newDeviceId = event.subject().location().deviceId();
+            PortNumber newPort = event.subject().location().port();
+            Set<IpAddress> newIps = event.subject().ipAddresses();
+            log.debug("Host {}/{} is updated", mac, vlanId);
+
+            // Revoke previous IP table entry
+            prevIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.revokeIpRuleForHost(
+                            prevDeviceId, ip.getIp4Address(), mac, prevPort);
+                }
+            });
+
+            // Populate new IP table entry
+            newIps.forEach(ip -> {
+                if (ip.isIp4()) {
+                    routingRulePopulator.populateIpRuleForHost(
+                            newDeviceId, ip.getIp4Address(), mac, newPort);
+                }
+            });
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            // Do not proceed without mastership
+            DeviceId deviceId = event.subject().location().deviceId();
+            if (!mastershipService.isLocalMaster(deviceId)) {
+                return;
+            }
+
+            switch (event.type()) {
+                case HOST_ADDED:
+                    processHostAddedEvent(event);
+                    break;
+                case HOST_MOVED:
+                    processHostMovedEvent(event);
+                    break;
+                case HOST_REMOVED:
+                    processHostRemoveEvent(event);
+                    break;
+                case HOST_UPDATED:
+                    processHostUpdatedEvent(event);
+                    break;
+                default:
+                    log.warn("Unsupported host event type: {}", event.type());
+                    break;
+            }
+        }
+    }
+
+    private static class BridgingTableObjectiveContext implements ObjectiveContext {
+        final MacAddress mac;
+        final VlanId vlanId;
+
+        BridgingTableObjectiveContext(MacAddress mac, VlanId vlanId) {
+            this.mac = mac;
+            this.vlanId = vlanId;
+        }
+
+        @Override
+        public void onSuccess(Objective objective) {
+            if (objective.op() == Objective.Operation.ADD) {
+                log.debug("Successfully populate bridging table entry for {}/{}",
+                        mac, vlanId);
+            } else {
+                log.debug("Successfully revoke bridging table entry for {}/{}",
+                        mac, vlanId);
+            }
+        }
+
+        @Override
+        public void onError(Objective objective, ObjectiveError error) {
+            if (objective.op() == Objective.Operation.ADD) {
+                log.debug("Fail to populate bridging table entry for {}/{}. {}",
+                        mac, vlanId, error);
+            } else {
+                log.debug("Fail to revoke bridging table entry for {}/{}. {}",
+                         mac, vlanId, error);
+            }
+        }
+    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
index 55b556e..b394db5 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -224,8 +224,8 @@
                     .setEthSrc(nodeMacAddr);
             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
                 tBuilder.pushMpls()
-                        .setMpls(MplsLabel.
-                                 mplsLabel(ns.getEdgeLabel()));
+                        .copyTtlOut()
+                        .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
             }
 
             Integer nextId = nsNextObjStore.
@@ -292,8 +292,9 @@
                     .setEthDst(dstMac)
                     .setEthSrc(nodeMacAddr);
             if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
-                tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
-                                                    .getEdgeLabel()));
+                tBuilder.pushMpls()
+                        .copyTtlOut()
+                        .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
             }
 
             Integer nextId = nsNextObjStore.
@@ -536,8 +537,9 @@
                             .setEthDst(deviceMac)
                             .setEthSrc(nodeMacAddr);
                     if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) {
-                        tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns
-                                .getEdgeLabel()));
+                        tBuilder.pushMpls()
+                                .copyTtlOut()
+                                .setMpls(MplsLabel.mplsLabel(ns.getEdgeLabel()));
                     }
                     nextObjBuilder.addTreatment(tBuilder.build());
                 }
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
index 31297ff..b554106 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
@@ -25,6 +25,7 @@
 
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
@@ -54,6 +55,7 @@
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveStore;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -94,7 +96,9 @@
     private static final int TABLE_TMAC = 1;
     private static final int TABLE_IPV4_UNICAST = 2;
     private static final int TABLE_MPLS = 3;
+    private static final int TABLE_DMAC = 4;
     private static final int TABLE_ACL = 5;
+    private static final int TABLE_SMAC = 6;
 
     /**
      * Set the default values. These variables will get overwritten based on the
@@ -104,7 +108,9 @@
     protected int tmacTableId = TABLE_TMAC;
     protected int ipv4UnicastTableId = TABLE_IPV4_UNICAST;
     protected int mplsTableId = TABLE_MPLS;
+    protected int dstMacTableId = TABLE_DMAC;
     protected int aclTableId = TABLE_ACL;
+    protected int srcMacTableId = TABLE_SMAC;
 
     protected final Logger log = getLogger(getClass());
 
@@ -448,12 +454,14 @@
                     fwd.treatment().allInstructions().get(0).type() == Instruction.Type.OUTPUT) {
                 OutputInstruction o = (OutputInstruction) fwd.treatment().allInstructions().get(0);
                 if (o.port() == PortNumber.CONTROLLER) {
-                    log.warn("Punts to the controller are handled by misses in"
-                            + " the TMAC, IP and MPLS tables.");
-                    return Collections.emptySet();
+                    treatmentBuilder.punt();
+                    treatment = treatmentBuilder.build();
+                } else {
+                    treatment = fwd.treatment();
                 }
+            } else {
+                treatment = fwd.treatment();
             }
-            treatment = fwd.treatment();
         } else {
             log.warn("VERSATILE forwarding objective needs next objective ID "
                     + "or treatment.");
@@ -475,19 +483,52 @@
         return Collections.singletonList(ruleBuilder.build());
     }
 
-    protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
-        log.debug("Processing specific");
+    private boolean isSupportedEthTypeObjective(ForwardingObjective fwd) {
         TrafficSelector selector = fwd.selector();
         EthTypeCriterion ethType = (EthTypeCriterion) selector
                 .getCriterion(Criterion.Type.ETH_TYPE);
         if ((ethType == null) ||
-                (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
-                (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) {
+                ((ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
+                        (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST))) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean isSupportedEthDstObjective(ForwardingObjective fwd) {
+        TrafficSelector selector = fwd.selector();
+        EthCriterion ethDst = (EthCriterion) selector
+                .getCriterion(Criterion.Type.ETH_DST);
+        VlanIdCriterion vlanId = (VlanIdCriterion) selector
+                .getCriterion(Criterion.Type.VLAN_VID);
+        if (ethDst == null && vlanId == null) {
+            return false;
+        }
+        return true;
+    }
+
+    protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
+        log.debug("Processing specific");
+        boolean isEthTypeObj = isSupportedEthTypeObjective(fwd);
+        boolean isEthDstObj = isSupportedEthDstObjective(fwd);
+
+        if (isEthTypeObj) {
+            return processEthTypeSpecificObjective(fwd);
+        } else if (isEthDstObj) {
+            return processEthDstSpecificObjective(fwd);
+        } else {
             log.warn("processSpecific: Unsupported "
                     + "forwarding objective criteraia");
             fail(fwd, ObjectiveError.UNSUPPORTED);
             return Collections.emptySet();
         }
+    }
+
+    protected Collection<FlowRule>
+    processEthTypeSpecificObjective(ForwardingObjective fwd) {
+        TrafficSelector selector = fwd.selector();
+        EthTypeCriterion ethType = (EthTypeCriterion) selector
+                .getCriterion(Criterion.Type.ETH_TYPE);
 
         TrafficSelector.Builder filteredSelectorBuilder =
                 DefaultTrafficSelector.builder();
@@ -565,59 +606,167 @@
 
     }
 
-    protected List<FlowRule> processEthDstFilter(Criterion c,
-                                       FilteringObjective filt,
-                                       ApplicationId applicationId) {
+    protected Collection<FlowRule>
+    processEthDstSpecificObjective(ForwardingObjective fwd) {
         List<FlowRule> rules = new ArrayList<>();
-        EthCriterion e = (EthCriterion) c;
+
+        // Build filtered selector
+        TrafficSelector selector = fwd.selector();
+        EthCriterion ethCriterion = (EthCriterion) selector
+                .getCriterion(Criterion.Type.ETH_DST);
+        VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) selector
+                .getCriterion(Criterion.Type.VLAN_VID);
+        TrafficSelector.Builder filteredSelectorBuilder =
+                DefaultTrafficSelector.builder();
+        // Do not match MacAddress for subnet broadcast entry
+        if (!ethCriterion.mac().equals(MacAddress.NONE)) {
+            filteredSelectorBuilder.matchEthDst(ethCriterion.mac());
+        }
+        filteredSelectorBuilder.matchVlanId(vlanIdCriterion.vlanId());
+        TrafficSelector filteredSelector = filteredSelectorBuilder.build();
+
+        // Build filtered treatment
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        if (fwd.treatment() != null) {
+            treatmentBuilder.deferred();
+            fwd.treatment().allInstructions().forEach(treatmentBuilder::add);
+        }
+        if (fwd.nextId() != null) {
+            NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
+            if (next != null) {
+                GroupKey key = appKryo.deserialize(next.data());
+                Group group = groupService.getGroup(deviceId, key);
+                if (group != null) {
+                    treatmentBuilder.deferred().group(group.id());
+                } else {
+                    log.warn("Group Missing");
+                    fail(fwd, ObjectiveError.GROUPMISSING);
+                    return Collections.emptySet();
+                }
+            }
+        }
+        treatmentBuilder.immediate().transition(aclTableId);
+        TrafficTreatment filteredTreatment = treatmentBuilder.build();
+
+        // Build bridging table entries
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder();
+        flowRuleBuilder.fromApp(fwd.appId())
+                .withPriority(fwd.priority())
+                .forDevice(deviceId)
+                .withSelector(filteredSelector)
+                .withTreatment(filteredTreatment)
+                .forTable(dstMacTableId);
+        if (fwd.permanent()) {
+            flowRuleBuilder.makePermanent();
+        } else {
+            flowRuleBuilder.makeTemporary(fwd.timeout());
+        }
+        rules.add(flowRuleBuilder.build());
+
+        /*
+        // TODO Emulate source MAC table behavior
+        // Do not install source MAC table entry for subnet broadcast
+        if (!ethCriterion.mac().equals(MacAddress.NONE)) {
+            // Build filtered selector
+            selector = fwd.selector();
+            ethCriterion = (EthCriterion) selector.getCriterion(Criterion.Type.ETH_DST);
+            filteredSelectorBuilder = DefaultTrafficSelector.builder();
+            filteredSelectorBuilder.matchEthSrc(ethCriterion.mac());
+            filteredSelector = filteredSelectorBuilder.build();
+
+            // Build empty treatment. Apply existing instruction if match.
+            treatmentBuilder = DefaultTrafficTreatment.builder();
+            filteredTreatment = treatmentBuilder.build();
+
+            // Build bridging table entries
+            flowRuleBuilder = DefaultFlowRule.builder();
+            flowRuleBuilder.fromApp(fwd.appId())
+                    .withPriority(fwd.priority())
+                    .forDevice(deviceId)
+                    .withSelector(filteredSelector)
+                    .withTreatment(filteredTreatment)
+                    .forTable(srcMacTableId)
+                    .makePermanent();
+            rules.add(flowRuleBuilder.build());
+        }
+        */
+
+        return rules;
+    }
+
+    protected List<FlowRule> processEthDstFilter(EthCriterion ethCriterion,
+                                       VlanIdCriterion vlanIdCriterion,
+                                       FilteringObjective filt,
+                                       VlanId assignedVlan,
+                                       ApplicationId applicationId) {
+        //handling untagged packets via assigned VLAN
+        if (vlanIdCriterion.vlanId() == VlanId.NONE) {
+            vlanIdCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
+        }
+
+        /*
+         * Note: CpqD switches do not handle MPLS-related operation properly
+         * for a packet with VLAN tag. We pop VLAN here as a workaround.
+         * Side effect: HostService learns redundant hosts with same MAC but
+         * different VLAN. No known side effect on the network reachability.
+         */
+        List<FlowRule> rules = new ArrayList<>();
         TrafficSelector.Builder selectorIp = DefaultTrafficSelector
                 .builder();
         TrafficTreatment.Builder treatmentIp = DefaultTrafficTreatment
                 .builder();
-        selectorIp.matchEthDst(e.mac());
+        selectorIp.matchEthDst(ethCriterion.mac());
         selectorIp.matchEthType(Ethernet.TYPE_IPV4);
+        selectorIp.matchVlanId(vlanIdCriterion.vlanId());
+        treatmentIp.popVlan();
         treatmentIp.transition(ipv4UnicastTableId);
         FlowRule ruleIp = DefaultFlowRule.builder().forDevice(deviceId)
                 .withSelector(selectorIp.build())
                 .withTreatment(treatmentIp.build())
                 .withPriority(filt.priority()).fromApp(applicationId)
                 .makePermanent().forTable(tmacTableId).build();
-        log.debug("adding IP ETH rule for MAC: {}", e.mac());
+        log.debug("adding IP ETH rule for MAC: {}", ethCriterion.mac());
         rules.add(ruleIp);
 
         TrafficSelector.Builder selectorMpls = DefaultTrafficSelector
                 .builder();
         TrafficTreatment.Builder treatmentMpls = DefaultTrafficTreatment
                 .builder();
-        selectorMpls.matchEthDst(e.mac());
+        selectorMpls.matchEthDst(ethCriterion.mac());
         selectorMpls.matchEthType(Ethernet.MPLS_UNICAST);
+        selectorMpls.matchVlanId(vlanIdCriterion.vlanId());
+        treatmentMpls.popVlan();
         treatmentMpls.transition(mplsTableId);
         FlowRule ruleMpls = DefaultFlowRule.builder()
                 .forDevice(deviceId).withSelector(selectorMpls.build())
                 .withTreatment(treatmentMpls.build())
                 .withPriority(filt.priority()).fromApp(applicationId)
                 .makePermanent().forTable(tmacTableId).build();
-        log.debug("adding MPLS ETH rule for MAC: {}", e.mac());
+        log.debug("adding MPLS ETH rule for MAC: {}", ethCriterion.mac());
         rules.add(ruleMpls);
 
         return rules;
     }
 
-    protected List<FlowRule> processVlanIdFilter(Criterion c,
+    protected List<FlowRule> processVlanIdFilter(VlanIdCriterion vlanIdCriterion,
                                                  FilteringObjective filt,
+                                                 VlanId assignedVlan,
                                                  ApplicationId applicationId) {
         List<FlowRule> rules = new ArrayList<>();
-        VlanIdCriterion v = (VlanIdCriterion) c;
-        log.debug("adding rule for VLAN: {}", v.vlanId());
+        log.debug("adding rule for VLAN: {}", vlanIdCriterion.vlanId());
         TrafficSelector.Builder selector = DefaultTrafficSelector
                 .builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment
                 .builder();
         PortCriterion p = (PortCriterion) filt.key();
-        if (v.vlanId() != VlanId.NONE) {
-            selector.matchVlanId(v.vlanId());
+        if (vlanIdCriterion.vlanId() != VlanId.NONE) {
+            selector.matchVlanId(vlanIdCriterion.vlanId());
             selector.matchInPort(p.port());
             treatment.deferred().popVlan();
+        } else {
+            selector.matchInPort(p.port());
+            treatment.immediate().pushVlan().setVlanId(assignedVlan);
         }
         treatment.transition(tmacTableId);
         FlowRule rule = DefaultFlowRule.builder().forDevice(deviceId)
@@ -641,30 +790,79 @@
             fail(filt, ObjectiveError.UNKNOWN);
             return;
         }
+
+        EthCriterion ethCriterion = null;
+        VlanIdCriterion vlanIdCriterion = null;
+
         // convert filtering conditions for switch-intfs into flowrules
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        for (Criterion c : filt.conditions()) {
-            if (c.type() == Criterion.Type.ETH_DST) {
-                for (FlowRule rule : processEthDstFilter(c,
-                                                         filt,
-                                                         applicationId)) {
-                    ops = install ? ops.add(rule) : ops.remove(rule);
-                }
-            } else if (c.type() == Criterion.Type.VLAN_VID) {
-                for (FlowRule rule : processVlanIdFilter(c,
-                                                         filt,
-                                                         applicationId)) {
-                    ops = install ? ops.add(rule) : ops.remove(rule);
-                }
-            } else if (c.type() == Criterion.Type.IPV4_DST) {
+
+        for (Criterion criterion : filt.conditions()) {
+            if (criterion.type() == Criterion.Type.ETH_DST) {
+                ethCriterion = (EthCriterion) criterion;
+            } else if (criterion.type() == Criterion.Type.VLAN_VID) {
+                vlanIdCriterion = (VlanIdCriterion) criterion;
+            } else if (criterion.type() == Criterion.Type.IPV4_DST) {
                 log.debug("driver does not process IP filtering rules as it " +
                         "sends all misses in the IP table to the controller");
             } else {
                 log.warn("Driver does not currently process filtering condition"
-                                 + " of type: {}", c.type());
+                                 + " of type: {}", criterion.type());
                 fail(filt, ObjectiveError.UNSUPPORTED);
             }
         }
+
+        VlanId assignedVlan = null;
+        if (vlanIdCriterion != null && vlanIdCriterion.vlanId() == VlanId.NONE) {
+            // Assign a VLAN ID to untagged packets
+            if (filt.meta() == null) {
+                log.error("Missing metadata in filtering objective required "
+                                  + "for vlan assignment in dev {}", deviceId);
+                fail(filt, ObjectiveError.BADPARAMS);
+                return;
+            }
+            for (Instruction i : filt.meta().allInstructions()) {
+                if (i instanceof ModVlanIdInstruction) {
+                    assignedVlan = ((ModVlanIdInstruction) i).vlanId();
+                }
+            }
+            if (assignedVlan == null) {
+                log.error("Driver requires an assigned vlan-id to tag incoming "
+                                  + "untagged packets. Not processing vlan filters on "
+                                  + "device {}", deviceId);
+                fail(filt, ObjectiveError.BADPARAMS);
+                return;
+            }
+        }
+
+        if (ethCriterion == null) {
+            log.debug("filtering objective missing dstMac, cannot program TMAC table");
+        } else {
+            for (FlowRule tmacRule : processEthDstFilter(ethCriterion,
+                                                         vlanIdCriterion,
+                                                         filt,
+                                                         assignedVlan,
+                                                         applicationId)) {
+                log.debug("adding MAC filtering rules in TMAC table: {} for dev: {}",
+                          tmacRule, deviceId);
+                ops = install ? ops.add(tmacRule) : ops.remove(tmacRule);
+            }
+        }
+
+        if (ethCriterion == null || vlanIdCriterion == null) {
+            log.debug("filtering objective missing dstMac or vlan, cannot program"
+                              + "Vlan Table");
+        } else {
+            for (FlowRule vlanRule : processVlanIdFilter(vlanIdCriterion,
+                                                         filt,
+                                                         assignedVlan,
+                                                         applicationId)) {
+                log.debug("adding VLAN filtering rule in VLAN table: {} for dev: {}",
+                          vlanRule, deviceId);
+                ops = install ? ops.add(vlanRule) : ops.remove(vlanRule);
+            }
+        }
+
         // apply filtering flow rules
         flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
             @Override
@@ -686,10 +884,10 @@
     protected void setTableMissEntries() {
         // set all table-miss-entries
         populateTableMissEntry(vlanTableId, true, false, false, -1);
-        populateTableMissEntry(tmacTableId, true, false, false, -1);
-        populateTableMissEntry(ipv4UnicastTableId, false, true, true,
-                               aclTableId);
+        populateTableMissEntry(tmacTableId, false, false, true, dstMacTableId);
+        populateTableMissEntry(ipv4UnicastTableId, false, true, true, aclTableId);
         populateTableMissEntry(mplsTableId, false, true, true, aclTableId);
+        populateTableMissEntry(dstMacTableId, false, false, true, aclTableId);
         populateTableMissEntry(aclTableId, false, false, false, -1);
     }
 
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
index 3267d550..91f2679 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTPDell.java
@@ -21,6 +21,7 @@
 
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.behaviour.NextGroup;
 import org.onosproject.net.flow.DefaultFlowRule;
@@ -34,6 +35,7 @@
 import org.onosproject.net.flow.criteria.EthTypeCriterion;
 import org.onosproject.net.flow.criteria.IPCriterion;
 import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -175,12 +177,13 @@
     //Dell switches need ETH_DST based match condition in all IP table entries.
     //So while processing the ETH_DST based filtering objective, store
     //the device MAC to be used locally to use it while pushing the IP rules.
-    protected List<FlowRule> processEthDstFilter(Criterion c,
+    protected List<FlowRule> processEthDstFilter(EthCriterion ethCriterion,
+                                                 VlanIdCriterion vlanIdCriterion,
                                                  FilteringObjective filt,
+                                                 VlanId assignedVlan,
                                                  ApplicationId applicationId) {
         // Store device termination Mac to be used in IP flow entries
-        EthCriterion e = (EthCriterion) c;
-        deviceTMac = e.mac();
+        deviceTMac = ethCriterion.mac();
 
         log.debug("For now not adding any TMAC rules "
                 + "into Dell switches as it is ignoring");
@@ -189,8 +192,9 @@
     }
 
     @Override
-    protected List<FlowRule> processVlanIdFilter(Criterion c,
+    protected List<FlowRule> processVlanIdFilter(VlanIdCriterion vlanIdCriterion,
                                                  FilteringObjective filt,
+                                                 VlanId assignedVlan,
                                                  ApplicationId applicationId) {
         log.debug("For now not adding any VLAN rules "
                 + "into Dell switches as it is ignoring");