Improved openstackSwitching ARP handler

Save REST calls by checking if the target IP is owned by a known host first.

Change-Id: Id1ac0e5e13d635b5216d50c7cafaed1179a7410e
diff --git a/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackArpHandler.java b/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackArpHandler.java
index 944d12a..b0a4c43 100644
--- a/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackArpHandler.java
+++ b/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackArpHandler.java
@@ -18,12 +18,14 @@
 import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.net.Host;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.InboundPacket;
-import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.packet.PacketService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,69 +42,106 @@
             .getLogger(OpenstackArpHandler.class);
     private PacketService packetService;
     private OpenstackRestHandler restHandler;
+    private HostService hostService;
 
     /**
      * Returns OpenstackArpHandler reference.
      *
      * @param restHandler rest API handler reference
      * @param packetService PacketService reference
+     * @param hostService host service
      */
-    public OpenstackArpHandler(OpenstackRestHandler restHandler, PacketService packetService) {
+    public OpenstackArpHandler(OpenstackRestHandler restHandler, PacketService packetService,
+                               HostService hostService) {
         this.restHandler = checkNotNull(restHandler);
         this.packetService = packetService;
+        this.hostService = hostService;
     }
 
     /**
-     * Processes ARP packets.
+     * Processes ARP request packets.
+     * It checks if the target IP is owned by a known host first and then ask to
+     * OpenStack if it's not. This ARP proxy does not support overlapping IP.
      *
      * @param pkt ARP request packet
      */
     public void processPacketIn(InboundPacket pkt) {
-        Ethernet ethernet = pkt.parsed();
-        ARP arp = (ARP) ethernet.getPayload();
+        Ethernet ethRequest = pkt.parsed();
+        ARP arp = (ARP) ethRequest.getPayload();
 
-        if (arp.getOpCode() == ARP.OP_REQUEST) {
-            byte[] srcMacAddress = arp.getSenderHardwareAddress();
-            byte[] srcIPAddress = arp.getSenderProtocolAddress();
-            byte[] dstIPAddress = arp.getTargetProtocolAddress();
+        if (arp.getOpCode() != ARP.OP_REQUEST) {
+            return;
+        }
 
-            //Searches the Dst MAC Address based on openstackPortMap
-            MacAddress macAddress = null;
+        IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+        MacAddress dstMac = getMacFromHostService(targetIp);
+        if (dstMac == null) {
+            dstMac = getMacFromOpenstack(targetIp);
+        }
 
-            OpenstackPort openstackPort = restHandler.getPorts().stream().
-                    filter(e -> e.fixedIps().containsValue(Ip4Address.valueOf(
-                            dstIPAddress))).findAny().orElse(null);
+        if (dstMac == null) {
+            log.debug("Failed to find MAC address for {}", targetIp.toString());
+            return;
+        }
 
-            if (openstackPort != null) {
-                macAddress = openstackPort.macAddress();
-                log.debug("Found MACAddress: {}", macAddress.toString());
-            } else {
-                return;
-            }
+        Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
+                                              dstMac,
+                                              ethRequest);
 
-            //Creates a response packet
-            ARP arpReply = new ARP();
-            arpReply.setOpCode(ARP.OP_REPLY)
-                    .setHardwareAddressLength(arp.getHardwareAddressLength())
-                    .setHardwareType(arp.getHardwareType())
-                    .setProtocolAddressLength(arp.getProtocolAddressLength())
-                    .setProtocolType(arp.getProtocolType())
-                    .setSenderHardwareAddress(macAddress.toBytes())
-                    .setSenderProtocolAddress(dstIPAddress)
-                    .setTargetHardwareAddress(srcMacAddress)
-                    .setTargetProtocolAddress(srcIPAddress);
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(pkt.receivedFrom().port())
+                .build();
 
-            //Sends a response packet
-            ethernet.setDestinationMACAddress(srcMacAddress)
-                    .setSourceMACAddress(macAddress)
-                    .setEtherType(Ethernet.TYPE_ARP)
-                    .setPayload(arpReply);
+        packetService.emit(new DefaultOutboundPacket(
+                pkt.receivedFrom().deviceId(),
+                treatment,
+                ByteBuffer.wrap(ethReply.serialize())));
+    }
 
-            TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
-            builder.setOutput(pkt.receivedFrom().port());
-            OutboundPacket packet = new DefaultOutboundPacket(pkt.receivedFrom().deviceId(),
-                    builder.build(), ByteBuffer.wrap(ethernet.serialize()));
-            packetService.emit(packet);
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * OpenStack. It does not support overlapping IP.
+     *
+     * @param targetIp target ip address
+     * @return mac address, or null if it fails to fetch the mac
+     */
+    private MacAddress getMacFromOpenstack(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        OpenstackPort openstackPort = restHandler.getPorts()
+                .stream()
+                .filter(port -> port.fixedIps().containsValue(targetIp))
+                .findFirst()
+                .orElse(null);
+
+        if (openstackPort != null) {
+            log.debug("Found MAC from OpenStack for {}", targetIp.toString());
+            return openstackPort.macAddress();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns MAC address of a host with a given target IP address by asking to
+     * host service. It does not support overlapping IP.
+     *
+     * @param targetIp target ip
+     * @return mac address, or null if it fails to find the mac
+     */
+    private MacAddress getMacFromHostService(IpAddress targetIp) {
+        checkNotNull(targetIp);
+
+        Host host = hostService.getHostsByIp(targetIp)
+                .stream()
+                .findFirst()
+                .orElse(null);
+
+        if (host != null) {
+            log.debug("Found MAC from host service for {}", targetIp.toString());
+            return host.mac();
+        } else {
+            return null;
         }
     }
 }
diff --git a/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackSwitchingManager.java b/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackSwitchingManager.java
index d5a8c81..09c5197 100644
--- a/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackSwitchingManager.java
+++ b/apps/openstackswitching/app/src/main/java/org/onosproject/openstackswitching/OpenstackSwitchingManager.java
@@ -394,7 +394,7 @@
             InboundPacket pkt = context.inPacket();
             Ethernet ethernet = pkt.parsed();
 
-            if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+            if (ethernet != null && ethernet.getEtherType() == Ethernet.TYPE_ARP) {
                 arpHandler.processPacketIn(pkt);
             }
         }
@@ -483,7 +483,7 @@
             }
             doNotPushFlows = cfg.doNotPushFlows();
             restHandler = new OpenstackRestHandler(cfg);
-            arpHandler = new OpenstackArpHandler(restHandler, packetService);
+            arpHandler = new OpenstackArpHandler(restHandler, packetService, hostService);
             initializeFlowRules();
         }