CORD-416 Implemented ARP proxy for service IPs

Added ARP proxy which sends fake ARP reply for service IPs.

Change-Id: I0583ee994def2a429701c0375af5203bdfaa39c5
diff --git a/src/main/java/org/onosproject/cordvtn/CordVtn.java b/src/main/java/org/onosproject/cordvtn/CordVtn.java
index 127c7c9..5b3f8aa 100644
--- a/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -23,6 +23,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
 import org.onlab.util.ItemNotFoundException;
 import org.onlab.packet.IpAddress;
@@ -56,6 +57,9 @@
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
 import org.onosproject.openstackswitching.OpenstackNetwork;
 import org.onosproject.openstackswitching.OpenstackPort;
 import org.onosproject.openstackswitching.OpenstackSubnet;
@@ -135,6 +139,9 @@
     protected FlowRuleService flowRuleService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OvsdbController controller;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -154,6 +161,7 @@
 
     private final DeviceListener deviceListener = new InternalDeviceListener();
     private final HostListener hostListener = new InternalHostListener();
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
 
     private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
     private final BridgeHandler bridgeHandler = new BridgeHandler();
@@ -163,6 +171,7 @@
     private ConsistentMap<CordVtnNode, NodeState> nodeStore;
     private Map<HostId, OpenstackNetwork> hostNetMap = Maps.newHashMap();
     private CordVtnRuleInstaller ruleInstaller;
+    private CordVtnArpProxy arpProxy;
 
     private enum NodeState {
 
@@ -223,6 +232,10 @@
                                                  mastershipService,
                                                  DEFAULT_TUNNEL);
 
+        arpProxy = new CordVtnArpProxy(appId, packetService);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+        arpProxy.requestPacket();
+
         deviceService.addListener(deviceListener);
         hostService.addListener(hostListener);
 
@@ -233,6 +246,7 @@
     protected void deactivate() {
         deviceService.removeListener(deviceListener);
         hostService.removeListener(hostListener);
+        packetService.removeProcessor(packetProcessor);
 
         eventExecutor.shutdown();
         nodeStore.clear();
@@ -920,17 +934,18 @@
             log.info("VM {} is detected", host.id());
             hostNetMap.put(host.id(), vNet);
 
+            CordService service = getCordService(vNet);
+            if (service != null) {
+                // TODO check if the service needs an update on its group buckets after done CORD-433
+                ruleInstaller.updateServiceGroup(service);
+                arpProxy.addServiceIp(service.serviceIp());
+            }
+
             ruleInstaller.populateBasicConnectionRules(
                     host,
                     hostIp,
                     checkNotNull(getRemoteIp(host.location().deviceId())).getIp4Address(),
                     vNet);
-
-            CordService service = getCordService(vNet);
-            // TODO check if the service needs an update on its group buckets after done CORD-433
-            if (service != null) {
-                ruleInstaller.updateServiceGroup(service);
-            }
         }
 
         @Override
@@ -950,12 +965,37 @@
             ruleInstaller.removeBasicConnectionRules(host);
 
             CordService service = getCordService(vNet);
-            // TODO check if the service needs an update on its group buckets after done CORD-433
             if (service != null) {
+                // TODO check if the service needs an update on its group buckets after done CORD-433
                 ruleInstaller.updateServiceGroup(service);
+
+                if (getHostsWithOpenstackNetwork(vNet).isEmpty()) {
+                    arpProxy.removeServiceIp(service.serviceIp());
+                }
             }
 
             hostNetMap.remove(host.id());
         }
     }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            Ethernet ethPacket = context.inPacket().parsed();
+            if (ethPacket == null) {
+                return;
+            }
+
+            if (ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+                return;
+            }
+
+            arpProxy.processArpPacket(context, ethPacket);
+        }
+    }
 }
diff --git a/src/main/java/org/onosproject/cordvtn/CordVtnArpProxy.java b/src/main/java/org/onosproject/cordvtn/CordVtnArpProxy.java
new file mode 100644
index 0000000..4cef148
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/CordVtnArpProxy.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn;
+
+import com.google.common.collect.Sets;
+import org.onlab.packet.ARP;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+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.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles ARP requests for virtual network service IPs.
+ */
+public class CordVtnArpProxy {
+    protected final Logger log = getLogger(getClass());
+    // TODO make gateway MAC address configurable
+    private static final MacAddress DEFAULT_GATEWAY_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+
+    private final ApplicationId appId;
+    private final PacketService packetService;
+
+    private Set<Ip4Address> serviceIPs = Sets.newHashSet();
+
+    /**
+     * Default constructor.
+     *
+     * @param appId application id
+     * @param packetService packet service
+     */
+    public CordVtnArpProxy(ApplicationId appId, PacketService packetService) {
+        this.appId = appId;
+        this.packetService = packetService;
+    }
+
+    /**
+     * Requests ARP packet.
+     */
+    public void requestPacket() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .build();
+
+        packetService.requestPackets(selector,
+                                     PacketPriority.CONTROL,
+                                     appId,
+                                     Optional.empty());
+    }
+
+    /**
+     * Cancels ARP packet.
+     */
+    public void cancelPacket() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .build();
+
+        packetService.cancelPackets(selector,
+                                    PacketPriority.CONTROL,
+                                    appId,
+                                    Optional.empty());
+    }
+
+    /**
+     * Adds a given service IP address to be served.
+     *
+     * @param serviceIp service ip
+     */
+    public void addServiceIp(IpAddress serviceIp) {
+        checkNotNull(serviceIp);
+        serviceIPs.add(serviceIp.getIp4Address());
+    }
+
+    /**
+     * Removes a given service IP address from this ARP proxy.
+     *
+     * @param serviceIp service ip
+     */
+    public void removeServiceIp(IpAddress serviceIp) {
+        checkNotNull(serviceIp);
+        serviceIPs.remove(serviceIp.getIp4Address());
+    }
+
+    /**
+     * Emits ARP reply with fake MAC address for a given ARP request.
+     * It only handles requests for the registered service IPs, and the other
+     * requests can be handled by other ARP handlers like openstackSwitching or
+     * proxyArp, for example.
+     *
+     * @param context packet context
+     * @param ethPacket ethernet packet
+     */
+    public void processArpPacket(PacketContext context, Ethernet ethPacket) {
+        ARP arpPacket = (ARP) ethPacket.getPayload();
+        Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+
+        if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+           return;
+        }
+
+        if (!serviceIPs.contains(targetIp)) {
+            return;
+        }
+
+        Ethernet ethReply = ARP.buildArpReply(
+                targetIp,
+                DEFAULT_GATEWAY_MAC,
+                ethPacket);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(context.inPacket().receivedFrom().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                context.inPacket().receivedFrom().deviceId(),
+                treatment,
+                ByteBuffer.wrap(ethReply.serialize())));
+
+        context.block();
+    }
+}