[SONA] Add a configuration to choose the SNATing mechanism between OVS SNAT and Controller SNAT.

Change-Id: Idd82a7c2e0d220d52e5445b3ca1d166663cf3b4a
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
index bcd31d0..a24a85e 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
@@ -34,7 +34,7 @@
     public static final int PRIORITY_TUNNEL_TAG_RULE = 30000;
     public static final int PRIORITY_FLOATING_INTERNAL = 42000;
     public static final int PRIORITY_FLOATING_EXTERNAL = 41000;
-    public static final int PRIORITY_OVS_SNAT_RULE = 40500;
+    public static final int PRIORITY_STATEFUL_SNAT_RULE = 40500;
     public static final int PRIORITY_ICMP_RULE = 43000;
     public static final int PRIORITY_INTERNAL_ROUTING_RULE = 28000;
     public static final int PRIORITY_EXTERNAL_ROUTING_RULE = 25000;
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
index fafd667..adf4b8c 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
@@ -20,14 +20,19 @@
 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.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
 import org.onosproject.cluster.NodeId;
@@ -65,9 +70,11 @@
 import org.openstack4j.model.network.Router;
 import org.openstack4j.model.network.RouterInterface;
 import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Dictionary;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -84,7 +91,7 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_ROUTING_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ICMP_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_INTERNAL_ROUTING_RULE;
-import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_OVS_SNAT_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SWITCHING_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
 import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
@@ -103,6 +110,11 @@
     private static final String MSG_DISABLED = "Disabled ";
     private static final String ERR_SET_FLOWS = "Failed to set flows for router %s:";
     private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
+    private static final boolean USE_STATEFUL_SNAT = false;
+
+    @Property(name = "useStatefulSnat", boolValue = USE_STATEFUL_SNAT,
+            label = "Use Stateful SNAT for source NATing")
+    private boolean useStatefulSnat = USE_STATEFUL_SNAT;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -137,6 +149,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DriverService driverService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService configService;
+
     private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
@@ -154,6 +169,7 @@
         osNodeService.addListener(osNodeListener);
         osRouterService.addListener(osRouterListener);
         instancePortService.addListener(instancePortListener);
+        configService.registerProperties(getClass());
 
         log.info("Started");
     }
@@ -164,11 +180,30 @@
         osNodeService.removeListener(osNodeListener);
         instancePortService.removeListener(instancePortListener);
         leadershipService.withdraw(appId.name());
+        configService.unregisterProperties(getClass(), false);
         eventExecutor.shutdown();
 
         log.info("Stopped");
     }
 
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, "useStatefulSnat");
+        if (flag == null) {
+            log.info("useStatefulSnat is not configured, " +
+                    "using current value of {}", useStatefulSnat);
+        } else {
+            useStatefulSnat = flag;
+            log.info("Configured. useStatefulSnat is {}",
+                    useStatefulSnat ? "enabled" : "disabled");
+        }
+
+        resetSnatRules();
+    }
+
     private void routerUpdated(Router osRouter) {
         ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
         if (exGateway == null) {
@@ -229,7 +264,29 @@
                     install);
         });
 
-        IpAddress natAddress = getGatewayIpAddress(osRouter);
+        if (useStatefulSnat) {
+            setStatefulSnatRules(routerIface, install);
+        } else {
+            setReactiveSnatRules(routerIface, install);
+        }
+
+        final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+        log.info(updateStr + "external access for subnet({})", osSubnet.getCidr());
+    }
+
+    private void setStatefulSnatRules(RouterInterface routerIface, boolean install) {
+        Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+        Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+        Optional<Router> osRouter = osRouterService.routers().stream()
+                .filter(router -> osRouterService.routerInterfaces(routerIface.getId()) != null)
+                .findAny();
+
+        if (!osRouter.isPresent()) {
+            log.error("Cannot find a router for router interface {} ", routerIface);
+            return;
+        }
+        IpAddress natAddress = getGatewayIpAddress(osRouter.get());
         if (natAddress == null) {
             return;
         }
@@ -251,9 +308,19 @@
                                 natAddress, Long.parseLong(osNet.getProviderSegID()),
                                 gwNode.patchPortNum(), install);
                 });
+    }
 
-        final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
-        log.info(updateStr + "external access for subnet({})", osSubnet.getCidr());
+    private void setReactiveSnatRules(RouterInterface routerIface, boolean install) {
+        Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+        Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+        osNodeService.completeNodes(GATEWAY)
+                .forEach(gwNode -> setRulesToController(
+                        gwNode.intgBridge(),
+                        osNet.getProviderSegID(),
+                        IpPrefix.valueOf(osSubnet.getCidr()),
+                        osNet.getNetworkType(),
+                        install));
     }
 
     private IpAddress getGatewayIpAddress(Router osRouter) {
@@ -271,6 +338,24 @@
         return IpAddress.valueOf(extSubnet.get().getGateway());
     }
 
+    private void resetSnatRules() {
+        if (useStatefulSnat) {
+            osRouterService.routerInterfaces().forEach(
+                    routerIface -> {
+                        setReactiveSnatRules(routerIface, false);
+                        setStatefulSnatRules(routerIface, true);
+                    }
+            );
+        } else {
+            osRouterService.routerInterfaces().forEach(
+                    routerIface -> {
+                        setStatefulSnatRules(routerIface, false);
+                        setReactiveSnatRules(routerIface, true);
+                    }
+            );
+        }
+    }
+
     private void setGatewayIcmp(Subnet osSubnet, boolean install) {
         if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
             // do nothing if no gateway is set
@@ -628,7 +713,7 @@
                 deviceId,
                 selector,
                 treatment,
-                PRIORITY_OVS_SNAT_RULE,
+                PRIORITY_STATEFUL_SNAT_RULE,
                 GW_COMMON_TABLE,
                 install);
     }
@@ -660,11 +745,72 @@
                 deviceId,
                 selector,
                 treatment,
-                PRIORITY_OVS_SNAT_RULE,
+                PRIORITY_STATEFUL_SNAT_RULE,
                 GW_COMMON_TABLE,
                 install);
     }
 
+    private void setRulesToController(DeviceId deviceId, String segmentId, IpPrefix srcSubnet,
+                                      NetworkType networkType, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(srcSubnet);
+
+        switch (networkType) {
+            case VXLAN:
+                sBuilder.matchTunnelId(Long.parseLong(segmentId))
+                        .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+                break;
+            case VLAN:
+                sBuilder.matchVlanId(VlanId.vlanId(segmentId))
+                        .matchEthDst(osNodeService.node(deviceId).vlanPortMac());
+                break;
+            default:
+                final String error = String.format(
+                        ERR_UNSUPPORTED_NET_TYPE + "%s",
+                        networkType.toString());
+                throw new IllegalStateException(error);
+        }
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                .setEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        if (networkType.equals(NetworkType.VLAN)) {
+            tBuilder.popVlan();
+        }
+
+        tBuilder.setOutput(PortNumber.CONTROLLER);
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_EXTERNAL_ROUTING_RULE,
+                GW_COMMON_TABLE,
+                install);
+
+
+        // Sends ICMP response to controller for SNATing ingress traffic
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                treatment,
+                PRIORITY_INTERNAL_ROUTING_RULE,
+                GW_COMMON_TABLE,
+                install);
+    }
 
     private class InternalRouterEventListener implements OpenstackRouterListener {