add a virtual gateway for reactive routing

  There is no physical gateway in SDN network.
  However a host needs a gateway when it tries to communicate with a remote host.
  So we designed a virtual gateway for SDN network.
  The virtual gateway can have multiple IP addresses.
  Each IP address is used as the default gateway address of an IP prefix.
  We only configure one MAC address to the virtual gateway.
  You can choose any MAC address from the BGP speakers as the virtual gateway MAC address.
  We configure this MAC address staticly in the sdnip.json configuration file.

Change-Id: I2a72bef797fc55d25bb5473e8fca624ad659e1d1
diff --git a/apps/reactive-routing/pom.xml b/apps/reactive-routing/pom.xml
index d7bde79..a5059ca 100644
--- a/apps/reactive-routing/pom.xml
+++ b/apps/reactive-routing/pom.xml
@@ -41,6 +41,11 @@
       <artifactId>onos-app-routing</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.onosproject</groupId>
+      <artifactId>onos-api</artifactId>
+    </dependency>
   </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
index a74387f..46f7fd9 100644
--- a/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
+++ b/apps/reactive-routing/src/main/java/org/onosproject/reactive/routing/SdnIpReactiveRouting.java
@@ -16,13 +16,17 @@
 package org.onosproject.reactive.routing;
 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.IPv4;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onosproject.core.ApplicationId;
@@ -40,6 +44,7 @@
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.RoutingConfigurationService;
 import org.slf4j.Logger;
 
 /**
@@ -64,6 +69,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected RoutingService routingService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RoutingConfigurationService config;
+
     private ApplicationId appId;
 
     private ReactiveRoutingProcessor processor =
@@ -80,6 +88,9 @@
         selector.matchEthType(Ethernet.TYPE_IPV4);
         packetService.requestPackets(selector.build(),
                                      PacketPriority.REACTIVE, appId);
+        selector.matchEthType(Ethernet.TYPE_ARP);
+        packetService.requestPackets(selector.build(),
+                                     PacketPriority.REACTIVE, appId);
 
         log.info("SDN-IP Reactive Routing Started");
     }
@@ -100,31 +111,56 @@
             if (ethPkt == null) {
                 return;
             }
-
-            // In theory, we do not need to check whether it is Ethernet
-            // TYPE_IPV4. However, due to the current implementation of the
-            // packetService, we will receive all packets from all subscribers.
-            // Hence, we have to check the Ethernet type again here.
-            if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
-                return;
-            }
-
-            // Parse packet
-            IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
-            IpAddress dstIp =
-                    IpAddress.valueOf(ipv4Packet.getDestinationAddress());
-            IpAddress srcIp =
-                    IpAddress.valueOf(ipv4Packet.getSourceAddress());
             ConnectPoint srcConnectPoint = pkt.receivedFrom();
-            MacAddress srcMac = ethPkt.getSourceMAC();
-            routingService.packetReactiveProcessor(dstIp, srcIp,
-                                                   srcConnectPoint, srcMac);
 
-            // TODO emit packet first or packetReactiveProcessor first
-            ConnectPoint egressConnectPoint = null;
-            egressConnectPoint = routingService.getEgressConnectPoint(dstIp);
-            if (egressConnectPoint != null) {
-                forwardPacketToDst(context, egressConnectPoint);
+            switch (ethPkt.getEtherType()) {
+            case Ethernet.TYPE_ARP:
+                ARP arpPacket = (ARP) ethPkt.getPayload();
+                Ip4Address targetIpAddress = Ip4Address
+                        .valueOf(arpPacket.getTargetProtocolAddress());
+                // Only when it is an ARP request packet and the target IP
+                // address is a virtual gateway IP address, then it will be
+                // processed.
+                if (arpPacket.getOpCode() == ARP.OP_REQUEST
+                        && config.isVirtualGatewayIpAddress(targetIpAddress)) {
+                    MacAddress gatewayMacAddress =
+                            config.getVirtualGatewayMacAddress();
+                    if (gatewayMacAddress == null) {
+                        break;
+                    }
+                    Ethernet eth = ARP.buildArpReply(targetIpAddress,
+                                                     gatewayMacAddress,
+                                                     ethPkt);
+
+                    TrafficTreatment.Builder builder =
+                            DefaultTrafficTreatment.builder();
+                    builder.setOutput(srcConnectPoint.port());
+                    packetService.emit(new DefaultOutboundPacket(
+                            srcConnectPoint.deviceId(),
+                            builder.build(),
+                            ByteBuffer.wrap(eth.serialize())));
+                }
+                break;
+            case Ethernet.TYPE_IPV4:
+                // Parse packet
+                IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+                IpAddress dstIp =
+                        IpAddress.valueOf(ipv4Packet.getDestinationAddress());
+                IpAddress srcIp =
+                        IpAddress.valueOf(ipv4Packet.getSourceAddress());
+                MacAddress srcMac = ethPkt.getSourceMAC();
+                routingService.packetReactiveProcessor(dstIp, srcIp,
+                                                       srcConnectPoint, srcMac);
+
+                // TODO emit packet first or packetReactiveProcessor first
+                ConnectPoint egressConnectPoint = null;
+                egressConnectPoint = routingService.getEgressConnectPoint(dstIp);
+                if (egressConnectPoint != null) {
+                    forwardPacketToDst(context, egressConnectPoint);
+                }
+                break;
+            default:
+                break;
             }
         }
     }
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/LocalIpPrefixEntry.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/LocalIpPrefixEntry.java
index a2f3c8c..3f62db4 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/config/LocalIpPrefixEntry.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/LocalIpPrefixEntry.java
@@ -20,6 +20,7 @@
 
 import java.util.Objects;
 
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 
 /**
@@ -28,6 +29,7 @@
 public class LocalIpPrefixEntry {
     private final IpPrefix ipPrefix;
     private final IpPrefixType type;
+    private final IpAddress gatewayIpAddress;
 
     /**
      * Specifies the type of local IP prefix.
@@ -55,9 +57,12 @@
      * @param type an IP prefix type as an IpPrefixType
      */
     public LocalIpPrefixEntry(@JsonProperty("ipPrefix") String ipPrefix,
-                              @JsonProperty("type") IpPrefixType type) {
+                              @JsonProperty("type") IpPrefixType type,
+                              @JsonProperty("gatewayIp") IpAddress
+                              gatewayIpAddress) {
         this.ipPrefix = IpPrefix.valueOf(ipPrefix);
         this.type = type;
+        this.gatewayIpAddress = gatewayIpAddress;
     }
 
     /**
@@ -79,6 +84,15 @@
     }
 
     /**
+     * Gets the gateway IP address of the IP prefix entry.
+     *
+     * @return the gateway IP address
+     */
+    public IpAddress getGatewayIpAddress() {
+        return gatewayIpAddress;
+    }
+
+    /**
      * Tests whether the IP version of this entry is IPv4.
      *
      * @return true if the IP version of this entry is IPv4, otherwise false.
diff --git a/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutingConfigurationService.java b/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutingConfigurationService.java
index a306328..a500ff4 100644
--- a/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutingConfigurationService.java
+++ b/apps/routing-api/src/main/java/org/onosproject/routing/config/RoutingConfigurationService.java
@@ -17,6 +17,7 @@
 
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onosproject.net.ConnectPoint;
 
 import java.util.Map;
@@ -42,6 +43,21 @@
     public Map<IpAddress, BgpPeer> getBgpPeers();
 
     /**
+     * Gets the MAC address configured for virtual gateway in SDN network.
+     *
+     * @return the MAC address of virtual gateway
+     */
+    public MacAddress getVirtualGatewayMacAddress();
+
+    /**
+     * Evaluates whether an IP address is a virtual gateway IP address.
+     *
+     * @param ipAddress the IP address to evaluate
+     * @return true if the IP address is a virtual gateway address, otherwise false
+     */
+    public boolean isVirtualGatewayIpAddress(IpAddress ipAddress);
+
+    /**
      * Evaluates whether an IP address belongs to local SDN network.
      *
      * @param ipAddress the IP address to evaluate
diff --git a/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java b/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java
index 76f1df0..d5a9e29 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java
@@ -17,6 +17,7 @@
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import org.onlab.packet.MacAddress;
 import org.onosproject.routing.config.BgpPeer;
 import org.onosproject.routing.config.BgpSpeaker;
 import org.onosproject.routing.config.LocalIpPrefixEntry;
@@ -33,6 +34,7 @@
     // the BGP routers outside our SDN network the BGP peers.
     private List<BgpSpeaker> bgpSpeakers;
     private List<BgpPeer> peers;
+    private MacAddress virtualGatewayMacAddress;
 
     // All IP prefixes from the configuration are local
     private List<LocalIpPrefixEntry> localIp4PrefixEntries =
@@ -77,7 +79,7 @@
     }
 
     /**
-     * Sets a list of BGP peers we are configured to peer with.
+     * Sets a list of BGP peers we configured to peer with.
      *
      * @param peers the list of BGP peers
      */
@@ -87,6 +89,26 @@
     }
 
     /**
+     * Gets the MAC address we configured for virtual gateway
+     * in SDN network.
+     *
+     * @return the MAC address of virtual gateway
+     */
+    public MacAddress getVirtualGatewayMacAddress() {
+        return virtualGatewayMacAddress;
+    }
+
+    /**
+     * Sets the MAC address for virtual gateway in SDN network.
+     *
+     * @param virtualGatewayMacAddress the MAC address of virtual gateway
+     */
+    @JsonProperty("virtualGatewayMacAddress")
+    public void setVirtualGatewayMacAddress(MacAddress virtualGatewayMacAddress) {
+        this.virtualGatewayMacAddress = virtualGatewayMacAddress;
+    }
+
+    /**
      * Gets a list of local IPv4 prefix entries configured for local
      * SDN network.
      * <p>
diff --git a/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java b/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java
index abb6a13..c45d286 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java
@@ -29,6 +29,7 @@
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.host.HostService;
 import org.onosproject.routing.config.BgpPeer;
@@ -43,6 +44,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -68,6 +70,7 @@
 
     private Map<String, BgpSpeaker> bgpSpeakers = new ConcurrentHashMap<>();
     private Map<IpAddress, BgpPeer> bgpPeers = new ConcurrentHashMap<>();
+    private Set<IpAddress> gatewayIpAddresses = new HashSet<>();
 
     private InvertedRadixTree<LocalIpPrefixEntry>
             localPrefixTable4 = new ConcurrentInvertedRadixTree<>(
@@ -76,6 +79,7 @@
             localPrefixTable6 = new ConcurrentInvertedRadixTree<>(
                     new DefaultByteArrayNodeFactory());
 
+    private MacAddress virtualGatewayMacAddress;
     private HostToInterfaceAdaptor hostAdaptor;
 
     @Activate
@@ -109,12 +113,16 @@
             for (LocalIpPrefixEntry entry : config.getLocalIp4PrefixEntries()) {
                 localPrefixTable4.put(createBinaryString(entry.ipPrefix()),
                                       entry);
+                gatewayIpAddresses.add(entry.getGatewayIpAddress());
             }
             for (LocalIpPrefixEntry entry : config.getLocalIp6PrefixEntries()) {
                 localPrefixTable6.put(createBinaryString(entry.ipPrefix()),
                                       entry);
+                gatewayIpAddresses.add(entry.getGatewayIpAddress());
             }
 
+            virtualGatewayMacAddress = config.getVirtualGatewayMacAddress();
+
         } catch (FileNotFoundException e) {
             log.warn("Configuration file not found: {}", configFileName);
         } catch (IOException e) {
@@ -178,4 +186,14 @@
                 createBinaryString(ipPrefix)) != null);
     }
 
+    @Override
+    public boolean isVirtualGatewayIpAddress(IpAddress ipAddress) {
+        return gatewayIpAddresses.contains(ipAddress);
+    }
+
+    @Override
+    public MacAddress getVirtualGatewayMacAddress() {
+        return virtualGatewayMacAddress;
+    }
+
 }
diff --git a/apps/sdnip/src/main/resources/config-examples/sdnip.json b/apps/sdnip/src/main/resources/config-examples/sdnip.json
index 913687e..c51de68 100644
--- a/apps/sdnip/src/main/resources/config-examples/sdnip.json
+++ b/apps/sdnip/src/main/resources/config-examples/sdnip.json
@@ -66,17 +66,21 @@
     "ip4LocalPrefixes" : [
         {
                 "ipPrefix" : "100.0.0.0/24",
-                "type" : "PUBLIC"
+                "type" : "PUBLIC",
+                "gatewayIp" : "100.0.0.1"
         },
         {
                 "ipPrefix" : "200.0.0.0/8",
-                "type" : "PUBLIC"
+                "type" : "PUBLIC",
+                "gatewayIp" : "200.0.0.3"
         },
         {
                 "ipPrefix" : "192.0.0.0/24",
-                "type" : "PRIVATE"
+                "type" : "PRIVATE",
+                "gatewayIp" : "192.0.0.254"
         }
     ],
     "ip6LocalPrefixes" : [
-    ]
+    ],
+    "virtualGatewayMacAddress" : "00:00:00:00:00:01"
 }