Add explicit flow rules to receive control packets: ARP, LLDP, BDDP

This fixes ONOS-540

NOTES:
 * Currently, the flow rules are pushed by each module that needs to receive
   the corresponding control packets:
   - ARP: ProxyArpManager, HostLocationProvider
   - LLDP and BDDP: LLDPLinkProvider
 * Pushing the corresponding IPv6 rules for Neighbor Discovery is not done yet
 * In the future, we might want to consider an explicit service to
   subscribe for receiving particular control packets

Change-Id: I292ad11a2e48390624f381c278e55e5d0af93c6d
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index ffe6c41..7c3d2ec 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -22,14 +22,23 @@
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
 import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DeviceId;
+import org.onosproject.net.Device;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.host.DefaultHostDescription;
 import org.onosproject.net.host.HostDescription;
 import org.onosproject.net.host.HostProvider;
@@ -68,6 +77,14 @@
 
     private final Logger log = getLogger(getClass());
 
+    private static final int FLOW_RULE_PRIORITY = 40000;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected HostProviderRegistry providerRegistry;
 
@@ -88,6 +105,8 @@
     private final InternalHostProvider processor = new InternalHostProvider();
     private final DeviceListener deviceListener = new InternalDeviceListener();
 
+    private ApplicationId appId;
+
     @Property(name = "hostRemovalEnabled", boolValue = true,
             label = "Enable host removal on port/device down events")
     private boolean hostRemovalEnabled = true;
@@ -102,10 +121,15 @@
 
     @Activate
     public void activate(ComponentContext context) {
+        appId =
+            coreService.registerApplication("org.onosproject.provider.host");
+
         modified(context);
         providerService = providerRegistry.register(this);
         pktService.addProcessor(processor, 1);
         deviceService.addListener(deviceListener);
+        pushRules();
+
         log.info("Started");
     }
 
@@ -137,6 +161,36 @@
         log.info("Triggering probe on device {}", host);
     }
 
+    /**
+     * Pushes flow rules to all devices.
+     */
+    private void pushRules() {
+        for (Device device : deviceService.getDevices()) {
+            pushRules(device);
+        }
+    }
+
+    /**
+     * Pushes flow rules to the device to receive control packets that need
+     * to be processed.
+     *
+     * @param device the device to push the rules to
+     */
+    private synchronized void pushRules(Device device) {
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+
+        // Get all ARP packets
+        sbuilder.matchEthType(Ethernet.TYPE_ARP);
+        tbuilder.punt();
+        FlowRule flowArp =
+            new DefaultFlowRule(device.id(),
+                                sbuilder.build(), tbuilder.build(),
+                                FLOW_RULE_PRIORITY, appId, 0, true);
+
+        flowRuleService.applyFlowRules(flowArp);
+    }
+
     private class InternalHostProvider implements PacketProcessor {
 
         @Override
@@ -204,23 +258,40 @@
     private class InternalDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
-            if (!hostRemovalEnabled) {
-                return;
-            }
-
-            DeviceEvent.Type type = event.type();
-            DeviceId deviceId = event.subject().id();
-            if (type == DeviceEvent.Type.PORT_UPDATED) {
-                ConnectPoint point = new ConnectPoint(deviceId, event.port().number());
-                removeHosts(hostService.getConnectedHosts(point));
-
-            } else if (type == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED) {
-                if (!deviceService.isAvailable(deviceId)) {
-                    removeHosts(hostService.getConnectedHosts(deviceId));
+            Device device = event.subject();
+            switch (event.type()) {
+            case DEVICE_ADDED:
+                pushRules(device);
+                break;
+            case DEVICE_AVAILABILITY_CHANGED:
+                if (hostRemovalEnabled &&
+                    !deviceService.isAvailable(device.id())) {
+                    removeHosts(hostService.getConnectedHosts(device.id()));
                 }
-
-            } else if (type == DeviceEvent.Type.DEVICE_REMOVED) {
-                removeHosts(hostService.getConnectedHosts(deviceId));
+                break;
+            case DEVICE_SUSPENDED:
+            case DEVICE_UPDATED:
+                // Nothing to do?
+                break;
+            case DEVICE_REMOVED:
+                if (hostRemovalEnabled) {
+                    removeHosts(hostService.getConnectedHosts(device.id()));
+                }
+                break;
+            case PORT_ADDED:
+                break;
+            case PORT_UPDATED:
+                if (hostRemovalEnabled) {
+                    ConnectPoint point =
+                        new ConnectPoint(device.id(), event.port().number());
+                    removeHosts(hostService.getConnectedHosts(point));
+                }
+                break;
+            case PORT_REMOVED:
+                // Nothing to do?
+                break;
+            default:
+                break;
             }
         }
     }