[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));
+    }
 }