CORD-472 Added table 0 and physical access rules

Change-Id: I73fe01ec31cfc379b2a0c8ee99a781415fe70c5d
diff --git a/src/main/java/org/onosproject/cordvtn/CordVtn.java b/src/main/java/org/onosproject/cordvtn/CordVtn.java
index 107f3e7..0104c71 100644
--- a/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -426,7 +426,7 @@
     private void postInit(CordVtnNode node) {
         disconnect(node);
 
-        ruleInstaller.init(node.intBrId(), getTunnelPort(node.intBrId()));
+        ruleInstaller.init(node.intBrId(), node.phyPortName(), node.localIp());
         hostService.getConnectedHosts(node.intBrId())
                 .stream()
                 .forEach(vmHandler::connected);
diff --git a/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java b/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
index 1984629..dd76ea9 100644
--- a/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
+++ b/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
@@ -18,11 +18,13 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
 import org.onlab.util.ItemNotFoundException;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.DefaultGroupId;
@@ -94,15 +96,19 @@
 
     protected final Logger log = getLogger(getClass());
 
-    private static final int TABLE_IN_PORT = 0;
-    private static final int TABLE_ACCESS_TYPE = 1;
-    private static final int TABLE_IN_SERVICE = 2;
-    private static final int TABLE_DST_IP = 3;
-    private static final int TABLE_TUNNEL_IN = 4;
+    private static final int TABLE_FIRST = 0;
+    private static final int TABLE_IN_PORT = 1;
+    private static final int TABLE_ACCESS_TYPE = 2;
+    private static final int TABLE_IN_SERVICE = 3;
+    private static final int TABLE_DST_IP = 4;
+    private static final int TABLE_TUNNEL_IN = 5;
 
     private static final int DEFAULT_PRIORITY = 5000;
     private static final int LOWER_PRIORITY = 4000;
     private static final int LOWEST_PRIORITY = 0;
+    private static final int HIGHER_PRIORITY = 50000;
+
+    private static final int VXLAN_UDP_PORT = 4789;
 
     private final ApplicationId appId;
     private final FlowRuleService flowRuleService;
@@ -141,13 +147,18 @@
      * Installs table miss rule to a give device.
      *
      * @param deviceId device id to install the rules
-     * @param tunnelPort tunnel port number of the device
+     * @param phyPortName physical port name
+     * @param localIp local data plane ip address
      */
-    public void init(DeviceId deviceId, PortNumber tunnelPort) {
+    public void init(DeviceId deviceId, String phyPortName, IpAddress localIp) {
         // default is drop packets which can be accomplished without
         // a table miss entry for all table.
-        populateTunnelInPortRule(deviceId, tunnelPort);
-        processAccessTypeTable(deviceId);
+        PortNumber tunnelPort = getTunnelPort(deviceId);
+        PortNumber phyPort = getPhyPort(deviceId, phyPortName);
+
+        processFirstTable(deviceId, phyPort, localIp);
+        processInPortTable(deviceId, tunnelPort, phyPort);
+        processAccessTypeTable(deviceId, phyPort);
     }
 
     /**
@@ -376,109 +387,93 @@
     }
 
     /**
-     * Creates a new group for a given service.
-     *
-     * @param deviceId device id to create a group
-     * @param service cord service
-     * @return group id, or null if it fails to create
-     */
-    private GroupId createServiceGroup(DeviceId deviceId, CordService service) {
-        checkNotNull(service);
-
-        GroupKey groupKey = getGroupKey(service.id());
-        Group group = groupService.getGroup(deviceId, groupKey);
-        GroupId groupId = getGroupId(service.id(), deviceId);
-
-        if (group != null) {
-            log.debug("Group {} is already exist in {}", service.id(), deviceId);
-            return groupId;
-        }
-
-        GroupBuckets buckets = getServiceGroupBuckets(deviceId, service.segmentationId(), service.hosts());
-        GroupDescription groupDescription = new DefaultGroupDescription(
-                deviceId,
-                GroupDescription.Type.SELECT,
-                buckets,
-                groupKey,
-                groupId.id(),
-                appId);
-
-        groupService.addGroup(groupDescription);
-
-        return groupId;
-    }
-
-    /**
-     * Returns group buckets for a given device.
+     * Populates default rules on the first table.
+     * The rules are for shuttling vxlan-encapped packets and supporting physical
+     * network connectivity.
      *
      * @param deviceId device id
-     * @param tunnelId tunnel id
-     * @param hosts list of host
-     * @return group buckets
+     * @param phyPort physical port number
+     * @param localIp local data plane ip address
      */
-    private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId, Map<Host, IpAddress> hosts) {
-        List<GroupBucket> buckets = Lists.newArrayList();
+    private void processFirstTable(DeviceId deviceId, PortNumber phyPort, IpAddress localIp) {
+        // take vxlan packet out onto the physical port
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.LOCAL)
+                .build();
 
-        for (Map.Entry<Host, IpAddress> entry : hosts.entrySet()) {
-            Host host = entry.getKey();
-            Ip4Address remoteIp = entry.getValue().getIp4Address();
-            DeviceId hostDevice = host.location().deviceId();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(phyPort)
+                .build();
 
-            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
-                    .builder()
-                    .setEthDst(host.mac());
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(HIGHER_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_FIRST)
+                .makePermanent()
+                .build();
 
-            if (deviceId.equals(hostDevice)) {
-                tBuilder.setOutput(host.location().port());
-            } else {
-                ExtensionTreatment tunnelDst = getTunnelDst(deviceId, remoteIp);
-                if (tunnelDst == null) {
-                    continue;
-                }
+        processFlowRule(true, flowRule);
 
-                tBuilder.extension(tunnelDst, deviceId)
-                        .setTunnelId(tunnelId)
-                        .setOutput(getTunnelPort(hostDevice));
-            }
+        // take a vxlan encap'd packet through the Linux stack
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(phyPort)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
+                .build();
 
-            buckets.add(DefaultGroupBucket.createSelectGroupBucket(tBuilder.build()));
-        }
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
 
-        return new GroupBuckets(buckets);
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(HIGHER_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_FIRST)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take all else to the next table
+        selector = DefaultTrafficSelector.builder()
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_IN_PORT)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(LOWEST_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_FIRST)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
     }
 
     /**
-     * Returns globally unique group ID.
-     *
-     * @param serviceId service id
-     * @param deviceId device id
-     * @return group id
-     */
-    private GroupId getGroupId(CordServiceId serviceId, DeviceId deviceId) {
-        return new DefaultGroupId(Objects.hash(serviceId, deviceId));
-    }
-
-    /**
-     * Returns group key of a service.
-     *
-     * @param serviceId service id
-     * @return group key
-     */
-    private GroupKey getGroupKey(CordServiceId serviceId) {
-        return new DefaultGroupKey(serviceId.id().getBytes());
-    }
-
-    /**
-     * Forward table miss rules in ACCESS_TYPE table to IN_SERVICE table.
+     * Forward table miss packets in ACCESS_TYPE table to physical port.
      *
      * @param deviceId device id
+     * @param phyPort physical port number
      */
-    private void processAccessTypeTable(DeviceId deviceId) {
+    private void processAccessTypeTable(DeviceId deviceId, PortNumber phyPort) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .build();
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_IN_SERVICE)
+                .setOutput(phyPort)
                 .build();
 
         FlowRule flowRule = DefaultFlowRule.builder()
@@ -495,18 +490,21 @@
     }
 
     /**
-     * Populates rules for tunnel flows in port in IN_PORT table.
-     * All flows from tunnel port are forwarded to TUNNEL_ID table.
+     * Populates default rules for IN_PORT table.
+     * All packets from tunnel port are forwarded to TUNNEL_ID table and all packets
+     * from physical port to ACCESS_TYPE table.
      *
      * @param deviceId device id to install the rules
-     * @param tunnelPort tunnel port
+     * @param tunnelPort tunnel port number
+     * @param phyPort physical port number
      */
-    private void populateTunnelInPortRule(DeviceId deviceId, PortNumber tunnelPort) {
+    private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber phyPort) {
         checkNotNull(tunnelPort);
 
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchInPort(tunnelPort)
                 .build();
+
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                 .transition(TABLE_TUNNEL_IN)
                 .build();
@@ -522,6 +520,26 @@
                 .build();
 
         processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(phyPort)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_DST_IP)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(DEFAULT_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_IN_PORT)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
     }
 
     /**
@@ -539,6 +557,7 @@
                 .matchEthType(Ethernet.TYPE_IPV4)
                 .matchIPSrc(srcIp.toIpPrefix())
                 .build();
+
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                 .transition(TABLE_ACCESS_TYPE)
                 .build();
@@ -559,6 +578,7 @@
         selector = DefaultTrafficSelector.builder()
                 .matchInPort(inPort)
                 .build();
+
         treatment = DefaultTrafficTreatment.builder()
                 .transition(TABLE_IN_SERVICE)
                 .build();
@@ -589,6 +609,7 @@
                 .matchIPSrc(srcRange)
                 .matchIPDst(dstRange)
                 .build();
+
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                 .transition(TABLE_DST_IP)
                 .build();
@@ -666,6 +687,7 @@
                 TrafficSelector selector = DefaultTrafficSelector.builder()
                         .matchInPort(port)
                         .build();
+
                 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                         .group(groupId)
                         .build();
@@ -701,6 +723,7 @@
                 .matchEthType(Ethernet.TYPE_IPV4)
                 .matchIPDst(dstIp.toIpPrefix())
                 .build();
+
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                 .setEthDst(dstMac)
                 .setOutput(inPort)
@@ -815,6 +838,24 @@
     }
 
     /**
+     * Returns physical port name of a given device.
+     *
+     * @param deviceId device id
+     * @param phyPortName physical port name
+     * @return physical port number, or null if no physical port exists
+     */
+    private PortNumber getPhyPort(DeviceId deviceId, String phyPortName) {
+        try {
+            return deviceService.getPorts(deviceId).stream()
+                    .filter(p -> p.annotations().value("portName").contains(phyPortName) &&
+                            p.isEnabled())
+                    .findFirst().get().number();
+        } catch (NoSuchElementException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns the inport from a given flow rule if the rule contains the match of it.
      *
      * @param flowRule flow rule
@@ -923,6 +964,99 @@
     }
 
     /**
+     * Creates a new group for a given service.
+     *
+     * @param deviceId device id to create a group
+     * @param service cord service
+     * @return group id, or null if it fails to create
+     */
+    private GroupId createServiceGroup(DeviceId deviceId, CordService service) {
+        checkNotNull(service);
+
+        GroupKey groupKey = getGroupKey(service.id());
+        Group group = groupService.getGroup(deviceId, groupKey);
+        GroupId groupId = getGroupId(service.id(), deviceId);
+
+        if (group != null) {
+            log.debug("Group {} is already exist in {}", service.id(), deviceId);
+            return groupId;
+        }
+
+        GroupBuckets buckets = getServiceGroupBuckets(deviceId, service.segmentationId(), service.hosts());
+        GroupDescription groupDescription = new DefaultGroupDescription(
+                deviceId,
+                GroupDescription.Type.SELECT,
+                buckets,
+                groupKey,
+                groupId.id(),
+                appId);
+
+        groupService.addGroup(groupDescription);
+
+        return groupId;
+    }
+
+    /**
+     * Returns group buckets for a given device.
+     *
+     * @param deviceId device id
+     * @param tunnelId tunnel id
+     * @param hosts list of host
+     * @return group buckets
+     */
+    private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId, Map<Host, IpAddress> hosts) {
+        List<GroupBucket> buckets = Lists.newArrayList();
+
+        for (Map.Entry<Host, IpAddress> entry : hosts.entrySet()) {
+            Host host = entry.getKey();
+            Ip4Address remoteIp = entry.getValue().getIp4Address();
+            DeviceId hostDevice = host.location().deviceId();
+
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
+                    .builder()
+                    .setEthDst(host.mac());
+
+            if (deviceId.equals(hostDevice)) {
+                tBuilder.setOutput(host.location().port());
+            } else {
+                ExtensionTreatment tunnelDst = getTunnelDst(deviceId, remoteIp);
+                if (tunnelDst == null) {
+                    continue;
+                }
+
+                tBuilder.extension(tunnelDst, deviceId)
+                        .setTunnelId(tunnelId)
+                        .setOutput(getTunnelPort(hostDevice));
+            }
+
+            buckets.add(DefaultGroupBucket.createSelectGroupBucket(tBuilder.build()));
+        }
+
+        return new GroupBuckets(buckets);
+    }
+
+    /**
+     * Returns globally unique group ID.
+     *
+     * @param serviceId service id
+     * @param deviceId device id
+     * @return group id
+     */
+    private GroupId getGroupId(CordServiceId serviceId, DeviceId deviceId) {
+        return new DefaultGroupId(Objects.hash(serviceId, deviceId));
+    }
+
+    /**
+     * Returns group key of a service.
+     *
+     * @param serviceId service id
+     * @return group key
+     */
+    private GroupKey getGroupKey(CordServiceId serviceId) {
+        return new DefaultGroupKey(serviceId.id().getBytes());
+    }
+
+    /**
      * Returns extension instruction to set tunnel destination.
      *
      * @param deviceId device id