add an ARP reply emulator for public IP to vBNG

Change-Id: Id142ef278363d39330e1c1f061f33d23e6b5e790
diff --git a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfiguration.java b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfiguration.java
index 31eae8b..6957db0 100644
--- a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfiguration.java
+++ b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfiguration.java
@@ -23,6 +23,7 @@
 
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 
 /**
  * Contains the configuration data for virtual BNG that has been read from a
@@ -32,6 +33,7 @@
 
     private final List<IpPrefix> localPublicIpPrefixes;
     private final IpAddress nextHopIpAddress;
+    private final MacAddress publicFacingMac;
 
     /**
      * Default constructor.
@@ -39,6 +41,7 @@
     private VbngConfiguration() {
         localPublicIpPrefixes = null;
         nextHopIpAddress = null;
+        publicFacingMac = null;
     }
 
     /**
@@ -46,14 +49,19 @@
      *
      * @param nextHopIpAddress the IP address of the next hop
      * @param prefixes the public IP prefix list for local SDN network
+     * @param publicFacingMac the MAC address configured for all local
+     *        public IP addresses
      */
     @JsonCreator
     public VbngConfiguration(@JsonProperty("localPublicIpPrefixes")
                              List<IpPrefix> prefixes,
                              @JsonProperty("nextHopIpAddress")
-                             IpAddress nextHopIpAddress) {
+                             IpAddress nextHopIpAddress,
+                             @JsonProperty("publicFacingMac")
+                             MacAddress publicFacingMac) {
         localPublicIpPrefixes = prefixes;
         this.nextHopIpAddress = nextHopIpAddress;
+        this.publicFacingMac = publicFacingMac;
     }
 
     /**
@@ -73,4 +81,13 @@
     public IpAddress getNextHopIpAddress() {
         return nextHopIpAddress;
     }
+
+    /**
+     * Gets the MAC address configured for all the public IP addresses.
+     *
+     * @return the MAC address
+     */
+    public MacAddress getPublicFacingMac() {
+        return publicFacingMac;
+    }
 }
diff --git a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationManager.java b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationManager.java
index c65bf6b..3a8dace 100644
--- a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationManager.java
+++ b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationManager.java
@@ -31,6 +31,7 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,6 +59,7 @@
             new ConcurrentHashMap<>();
 
     private IpAddress nextHopIpAddress;
+    private MacAddress macOfPublicIpAddresses;
 
     @Activate
     public void activate() {
@@ -96,6 +98,7 @@
                 localPublicIpPrefixes.put(prefix, true);
             }
             nextHopIpAddress = config.getNextHopIpAddress();
+            macOfPublicIpAddresses = config.getPublicFacingMac();
 
         } catch (FileNotFoundException e) {
             log.warn("Configuration file not found: {}", configFileName);
@@ -109,6 +112,11 @@
         return nextHopIpAddress;
     }
 
+    @Override
+    public MacAddress getPublicFacingMac() {
+        return macOfPublicIpAddresses;
+    }
+
     // TODO handle the case: the number of public IP addresses is not enough
     // for 1:1 mapping from public IP to private IP.
     @Override
@@ -169,6 +177,11 @@
         return ipAddressMap.get(privateIpAddress);
     }
 
+    @Override
+    public boolean isAssignedPublicIpAddress(IpAddress ipAddress) {
+        return ipAddressMap.containsValue(ipAddress);
+    }
+
     /**
      * Generates a new IP address base on a given IP address plus a number to
      * increase.
diff --git a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationService.java b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationService.java
index 644c229..382f522 100644
--- a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationService.java
+++ b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngConfigurationService.java
@@ -16,6 +16,7 @@
 package org.onosproject.virtualbng;
 
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 
 /**
  * Provides information about the virtual BNG configuration.
@@ -30,6 +31,22 @@
     public IpAddress getNextHopIpAddress();
 
     /**
+     * Gets the MAC address configured for all the public IP addresses.
+     *
+     * @return the MAC address
+     */
+    public MacAddress getPublicFacingMac();
+
+    /**
+     * Evaluates whether an IP address is an assigned public IP address.
+     *
+     * @param ipAddress the IP address to evaluate
+     * @return true if the input IP address is an assigned public IP address,
+     *         otherwise false
+     */
+    public boolean isAssignedPublicIpAddress(IpAddress ipAddress);
+
+    /**
      * Gets an available public IP address from local public IP prefixes.
      *
      * @param privateIpAddress a private IP address
diff --git a/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VirtualPublicHosts.java b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VirtualPublicHosts.java
new file mode 100644
index 0000000..35a9d81
--- /dev/null
+++ b/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VirtualPublicHosts.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.virtualbng;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.nio.ByteBuffer;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+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.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * When the upstream gateway which is outside local SDN network wants to send
+ * packets to our local public IP addresses, it will send out ARP requests to
+ * get the MAC address of each public IP address. Actually, there are no hosts
+ * configured with those public IP addresses, so this class is to emulate the
+ * behavior of the non-existed hosts and return ARP replies.
+ * <p>
+ * Since we will rewrite the destination MAC address in the switch before
+ * traffic packets go to the destination, so the MAC address can be any number.
+ * We manually configured a random MAC address for this purpose in the vBNG
+ * configuration file.
+ * </p>
+ */
+@Component(immediate = true)
+public class VirtualPublicHosts {
+    private final Logger log = getLogger(getClass());
+
+    private static final String APP_NAME =
+            "org.onosproject.virtualbng.VirtualPublicHosts";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected VbngConfigurationService vbngConfigService;
+
+    private ApplicationId appId;
+    private ArpRequestProcessor processor = new ArpRequestProcessor();
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+
+        packetService.addProcessor(processor,
+                                   PacketProcessor.ADVISOR_MAX + 6);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        // Only IPv4 is supported in current vBNG.
+        selector.matchEthType(Ethernet.TYPE_ARP);
+        packetService.requestPackets(selector.build(),
+                                   PacketPriority.REACTIVE, appId);
+
+        log.info("vBNG virtual public hosts started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("vBNG virtual public hosts Stopped");
+    }
+
+    /**
+     * This class filters out the ARP request packets, generates the ARP
+     * reply packets, and emits those packets.
+     */
+    private class ArpRequestProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+
+            // Only handle the ARP packets
+            if (ethPkt == null || ethPkt.getEtherType() != Ethernet.TYPE_ARP) {
+                return;
+            }
+            ARP arpPacket = (ARP) ethPkt.getPayload();
+            // Only handle ARP request packets
+            if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+                return;
+            }
+
+            Ip4Address targetIpAddress = Ip4Address
+                    .valueOf(arpPacket.getTargetProtocolAddress());
+
+            // Only handle an ARP request when the target IP address inside is
+            // an assigned public IP address
+            if (!vbngConfigService.isAssignedPublicIpAddress(targetIpAddress)) {
+                return;
+            }
+
+            MacAddress virtualHostMac =
+                    vbngConfigService.getPublicFacingMac();
+            if (virtualHostMac == null) {
+                return;
+            }
+
+            ConnectPoint srcConnectPoint = pkt.receivedFrom();
+            Ethernet eth = ARP.buildArpReply(targetIpAddress,
+                                             virtualHostMac,
+                                             ethPkt);
+
+            TrafficTreatment.Builder builder =
+                    DefaultTrafficTreatment.builder();
+            builder.setOutput(srcConnectPoint.port());
+            packetService.emit(new DefaultOutboundPacket(
+                    srcConnectPoint.deviceId(),
+                    builder.build(),
+                    ByteBuffer.wrap(eth.serialize())));
+        }
+    }
+}