[ONOS-7951] Use Stateful SNAT to handle N-S traffic in openstack

Change-Id: Ife7284d2ebd3ade7ce968005a69dff98857a65f3
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
index 2fbc09a..212db1a 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -15,12 +15,14 @@
  */
 package org.onosproject.openstacknetworking.impl;
 
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
 import org.onlab.packet.TCP;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
@@ -89,6 +91,8 @@
 
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -97,7 +101,6 @@
 
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
 import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
@@ -112,7 +115,9 @@
 import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.USE_STATEFUL_SNAT_DEFAULT;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.externalIpFromSubnet;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.externalPeerRouterFromSubnet;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getExternalIp;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.tunnelPortNumByNetType;
+import static org.onosproject.openstacknetworking.util.RulePopulatorUtil.CT_NAT_SRC_FLAG;
 import static org.onosproject.openstacknetworking.util.RulePopulatorUtil.buildExtension;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
@@ -134,7 +139,7 @@
     private static final String ERR_PACKET_IN = "Failed to handle packet in: ";
     private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
     private static final long TIME_OUT_SNAT_PORT_MS = 120L * 1000L;
-    private static final int TP_PORT_MINIMUM_NUM = 30000;
+    private static final int TP_PORT_MINIMUM_NUM = 1025;
     private static final int TP_PORT_MAXIMUM_NUM = 65535;
     private static final int VM_PREFIX = 32;
 
@@ -211,18 +216,16 @@
 
         allocatedPortNumMap = storageService.<Integer, Long>consistentMapBuilder()
                 .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
-                .withName("openstackrouting-allocatedportnummap")
+                .withName("openstackrouting-allocated-portnummap")
                 .withApplicationId(appId)
                 .build();
 
         unUsedPortNumSet = storageService.<Integer>setBuilder()
-                .withName("openstackrouting-unusedportnumset")
+                .withName("openstackrouting-unused-portnumset")
                 .withSerializer(Serializer.using(KryoNamespaces.API))
                 .build()
                 .asDistributedSet();
 
-        initializeUnusedPortNumSet();
-
         localNodeId = clusterService.getLocalNode().id();
         leadershipService.runForLeadership(appId.name());
         packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
@@ -232,6 +235,8 @@
         osRouterService.addListener(osRouterListener);
         osNodeService.addListener(osNodeListener);
 
+        eventExecutor.execute(this::initializeUnusedPortNumSet);
+
         log.info("Started");
     }
 
@@ -287,8 +292,8 @@
             return;
         }
 
-        ExternalPeerRouter externalPeerRouter =
-                externalPeerRouterFromSubnet(srcSubnet, osRouterService, osNetworkService);
+        ExternalPeerRouter externalPeerRouter = externalPeerRouterFromSubnet(
+                srcSubnet, osRouterService, osNetworkService);
         if (externalPeerRouter == null) {
             return;
         }
@@ -325,11 +330,11 @@
 
         if (osNet == null) {
             final String error = String.format("%s network %s not found",
-                                        ERR_PACKET_IN, srcInstPort.networkId());
+                    ERR_PACKET_IN, srcInstPort.networkId());
             throw new IllegalStateException(error);
         }
 
-        setDownstreamRules(srcInstPort,
+        setStatelessSnatDownstreamRules(srcInstPort,
                 osNet.getProviderSegID(),
                 netType,
                 externalIp,
@@ -337,7 +342,7 @@
                 patPort,
                 packetIn);
 
-        setUpstreamRules(osNet.getProviderSegID(),
+        setStatelessSnatUpstreamRules(osNet.getProviderSegID(),
                 netType,
                 externalIp,
                 externalPeerRouter,
@@ -345,12 +350,13 @@
                 packetIn);
     }
 
-    private void setDownstreamRules(InstancePort srcInstPort, String segmentId,
-                                    Type networkType,
-                                    IpAddress externalIp,
-                                    ExternalPeerRouter externalPeerRouter,
-                                    TpPort patPort,
-                                    InboundPacket packetIn) {
+    private void setStatelessSnatDownstreamRules(InstancePort srcInstPort,
+                                                 String segmentId,
+                                                 Type networkType,
+                                                 IpAddress externalIp,
+                                                 ExternalPeerRouter externalPeerRouter,
+                                                 TpPort patPort,
+                                                 InboundPacket packetIn) {
         IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
         IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
 
@@ -406,7 +412,7 @@
         OpenstackNode srcNode = osNodeService.node(srcInstPort.deviceId());
         osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
             TrafficTreatment treatment =
-                    getDownStreamTreatment(networkType, tBuilder, gNode, srcNode);
+                    getDownstreamTreatment(networkType, tBuilder, gNode, srcNode);
             osFlowRuleService.setRule(
                     appId,
                     gNode.intgBridge(),
@@ -418,7 +424,7 @@
         });
     }
 
-    private TrafficTreatment getDownStreamTreatment(Type networkType,
+    private TrafficTreatment getDownstreamTreatment(Type networkType,
                                                     TrafficTreatment.Builder tBuilder,
                                                     OpenstackNode gNode,
                                                     OpenstackNode srcNode) {
@@ -447,12 +453,12 @@
         return tmpBuilder.build();
     }
 
-    private void setUpstreamRules(String segmentId,
-                                  Type networkType,
-                                  IpAddress externalIp,
-                                  ExternalPeerRouter externalPeerRouter,
-                                  TpPort patPort,
-                                  InboundPacket packetIn) {
+    private void setStatelessSnatUpstreamRules(String segmentId,
+                                               Type networkType,
+                                               IpAddress externalIp,
+                                               ExternalPeerRouter externalPeerRouter,
+                                               TpPort patPort,
+                                               InboundPacket packetIn) {
         IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
 
         TrafficSelector.Builder sBuilder =  DefaultTrafficSelector.builder()
@@ -597,7 +603,8 @@
 
     private void clearPortNumMap() {
         allocatedPortNumMap.entrySet().forEach(e -> {
-            if (System.currentTimeMillis() - e.getValue().value() > TIME_OUT_SNAT_PORT_MS) {
+            if (System.currentTimeMillis() -
+                    e.getValue().value() > TIME_OUT_SNAT_PORT_MS) {
                 allocatedPortNumMap.remove(e.getKey());
                 unUsedPortNumSet.add(e.getKey());
             }
@@ -637,8 +644,8 @@
                                    IpPrefix srcSubnet,
                                    Type networkType,
                                    boolean install) {
-        OpenstackNode sourceNatGateway =
-                osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+        OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY)
+                .stream().findFirst().orElse(null);
 
         if (sourceNatGateway == null) {
             return;
@@ -704,7 +711,8 @@
 
         ExternalPeerRouter externalPeerRouter =
                 osNetworkAdminService.externalPeerRouter(exGateway);
-        VlanId vlanId = externalPeerRouter == null ? VlanId.NONE : externalPeerRouter.vlanId();
+        VlanId vlanId = externalPeerRouter == null ?
+                                    VlanId.NONE : externalPeerRouter.vlanId();
 
         if (exGateway == null) {
             deleteUnassociatedExternalPeerRouter();
@@ -794,36 +802,102 @@
             log.error("Cannot find a router for router interface {} ", routerIface);
             return;
         }
-        IpAddress natAddress = getGatewayIpAddress(osRouter.get());
+
+        IpAddress natAddress = getExternalIp(osRouter.get(), osNetworkService);
         if (natAddress == null) {
             return;
         }
+
+        IpAddress extRouterAddress = getGatewayIpAddress(osRouter.get());
+        if (extRouterAddress == null) {
+            return;
+        }
+
+        ExternalPeerRouter externalPeerRouter =
+                osNetworkService.externalPeerRouter(extRouterAddress);
+        if (externalPeerRouter == null) {
+            return;
+        }
+
         String netId = osNetworkAdminService.subnet(routerIface.getSubnetId()).getNetworkId();
 
+        Map<OpenstackNode, PortRange> gwPortRangeMap = getAssignedPortsForGateway(
+                ImmutableList.copyOf(osNodeService.completeNodes(GATEWAY)));
+
         osNodeService.completeNodes(GATEWAY)
                 .forEach(gwNode -> {
                     instancePortService.instancePorts(netId)
                             .stream()
                             .filter(port -> port.state() == ACTIVE)
-                            .forEach(port -> setRulesForSnatIngressRule(gwNode.intgBridge(),
-                                    Long.parseLong(osNet.getProviderSegID()),
-                                    IpPrefix.valueOf(port.ipAddress(), VM_PREFIX),
-                                    port.deviceId(),
-                                    netType,
-                                    install));
+                            .forEach(port -> setGatewayToInstanceDownstreamRule(
+                                    gwNode, port, install));
 
-                    setOvsNatIngressRule(gwNode.intgBridge(),
-                            IpPrefix.valueOf(natAddress, VM_PREFIX),
-                            Constants.DEFAULT_EXTERNAL_ROUTER_MAC, install);
+                    setStatefulSnatDownstreamRule(gwNode.intgBridge(),
+                            IpPrefix.valueOf(natAddress, VM_PREFIX), install);
 
-                    if (gwNode.patchPortNum() != null) {
-                        setOvsNatEgressRule(gwNode.intgBridge(),
-                                natAddress, Long.parseLong(osNet.getProviderSegID()),
-                                gwNode.patchPortNum(), install);
-                    }
+                    PortRange gwPortRange = gwPortRangeMap.get(gwNode);
+
+                    Map<String, PortRange> netPortRangeMap =
+                            getAssignedPortsForNet(getNetIdByRouterId(routerIface.getId()),
+                                    gwPortRange.min(), gwPortRange.max());
+
+                    PortRange netPortRange = netPortRangeMap.get(osNet.getId());
+
+                    setStatefulSnatUpstreamRule(gwNode, natAddress,
+                            Long.parseLong(osNet.getProviderSegID()),
+                            externalPeerRouter, netPortRange.min(),
+                            netPortRange.max(), install);
                 });
     }
 
+    private List<String> getNetIdByRouterId(String routerId) {
+        return osRouterService.routerInterfaces(routerId)
+                .stream()
+                .filter(ri -> osRouterService.router(ri.getId())
+                                .getExternalGatewayInfo().isEnableSnat())
+                .map(RouterInterface::getSubnetId)
+                .map(si -> osNetworkAdminService.subnet(si))
+                .map(Subnet::getNetworkId)
+                .collect(Collectors.toList());
+    }
+
+    private Map<OpenstackNode, PortRange>
+                        getAssignedPortsForGateway(List<OpenstackNode> gateways) {
+
+        Map<OpenstackNode, PortRange> gwPortRangeMap = Maps.newConcurrentMap();
+
+        int portRangeNumPerGwNode =
+                (TP_PORT_MAXIMUM_NUM - TP_PORT_MINIMUM_NUM + 1) / gateways.size();
+
+        for (int i = 0; i < gateways.size(); i++) {
+            int gwPortRangeMin = TP_PORT_MINIMUM_NUM + i * portRangeNumPerGwNode;
+            int gwPortRangeMax = TP_PORT_MINIMUM_NUM + (i + 1) * portRangeNumPerGwNode - 1;
+
+            gwPortRangeMap.put(gateways.get(i),
+                    new PortRange(gwPortRangeMin, gwPortRangeMax));
+        }
+
+        return gwPortRangeMap;
+    }
+
+    private Map<String, PortRange> getAssignedPortsForNet(List<String> netIds,
+                                                          int min, int max) {
+
+        Map<String, PortRange> netPortRangeMap = Maps.newConcurrentMap();
+
+        int portRangeNumPerNet = (max - min + 1) / netIds.size();
+
+        for (int i = 0; i < netIds.size(); i++) {
+            int netPortRangeMin = min + i * portRangeNumPerNet;
+            int netPortRangeMax = min + (i + 1) * portRangeNumPerNet - 1;
+
+            netPortRangeMap.put(netIds.get(i),
+                    new PortRange(netPortRangeMin, netPortRangeMax));
+        }
+
+        return netPortRangeMap;
+    }
+
     private IpAddress getGatewayIpAddress(Router osRouter) {
 
         if (osRouter.getExternalGatewayInfo() == null) {
@@ -836,7 +910,7 @@
                 .findAny();
 
         if (!extSubnet.isPresent()) {
-            log.error("Cannot find externel subnet for the router");
+            log.error("Cannot find external subnet for the router");
             return null;
         }
 
@@ -857,14 +931,55 @@
                         install));
     }
 
-    private void setOvsNatIngressRule(DeviceId deviceId,
-                                      IpPrefix cidr,
-                                      MacAddress dstMac,
-                                      boolean install) {
+    private void setGatewayToInstanceDownstreamRule(OpenstackNode gwNode,
+                                                    InstancePort instPort,
+                                                    boolean install) {
 
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(cidr)
+                .matchIPDst(IpPrefix.valueOf(instPort.ipAddress(), VM_PREFIX))
+                .build();
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                .setEthDst(instPort.macAddress());
+
+        Type netType = osNetworkAdminService.networkType(instPort.networkId());
+        String segId = osNetworkAdminService.segmentId(instPort.networkId());
+
+        switch (netType) {
+            case VXLAN:
+            case GRE:
+            case GENEVE:
+                tBuilder.setTunnelId(Long.valueOf(segId));
+                break;
+            case VLAN:
+            default:
+                final String error = String.format("%s %s",
+                        ERR_UNSUPPORTED_NET_TYPE, netType.name());
+                throw new IllegalStateException(error);
+        }
+
+        OpenstackNode srcNode = osNodeService.node(instPort.deviceId());
+        TrafficTreatment treatment =
+                    getDownstreamTreatment(netType, tBuilder, gwNode, srcNode);
+
+        osFlowRuleService.setRule(
+                appId,
+                gwNode.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_STATEFUL_SNAT_RULE,
+                GW_COMMON_TABLE,
+                install);
+    }
+
+    private void setStatefulSnatDownstreamRule(DeviceId deviceId,
+                                               IpPrefix gatewayIp,
+                                               boolean install) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(gatewayIp)
                 .build();
 
         ExtensionTreatment natTreatment = RulePopulatorUtil
@@ -875,7 +990,6 @@
                 .build();
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setEthDst(dstMac)
                 .extension(natTreatment, deviceId)
                 .build();
 
@@ -889,11 +1003,13 @@
                 install);
     }
 
-    private void setOvsNatEgressRule(DeviceId deviceId,
-                                     IpAddress natAddress,
-                                     long vni,
-                                     PortNumber output,
-                                     boolean install) {
+    private void setStatefulSnatUpstreamRule(OpenstackNode gwNode,
+                                             IpAddress gatewayIp,
+                                             long vni,
+                                             ExternalPeerRouter extPeerRouter,
+                                             int minPortNum,
+                                             int maxPortNum,
+                                             boolean install) {
 
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
@@ -902,22 +1018,25 @@
                 .build();
 
         ExtensionTreatment natTreatment = RulePopulatorUtil
-                .niciraConnTrackTreatmentBuilder(driverService, deviceId)
+                .niciraConnTrackTreatmentBuilder(driverService, gwNode.intgBridge())
                 .commit(true)
+                .natFlag(CT_NAT_SRC_FLAG)
                 .natAction(true)
-                .natIp(natAddress)
+                .natIp(gatewayIp)
+                .natPortMin(TpPort.tpPort(minPortNum))
+                .natPortMax(TpPort.tpPort(maxPortNum))
                 .build();
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .extension(natTreatment, deviceId)
-                .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
+                .extension(natTreatment, gwNode.intgBridge())
+                .setEthDst(extPeerRouter.macAddress())
                 .setEthSrc(DEFAULT_GATEWAY_MAC)
-                .setOutput(output)
+                .setOutput(gwNode.uplinkPortNum())
                 .build();
 
         osFlowRuleService.setRule(
                 appId,
-                deviceId,
+                gwNode.intgBridge(),
                 selector,
                 treatment,
                 PRIORITY_STATEFUL_SNAT_RULE,
@@ -925,41 +1044,6 @@
                 install);
     }
 
-    private void setRulesForSnatIngressRule(DeviceId deviceId,
-                                            Long vni,
-                                            IpPrefix destVmIp,
-                                            DeviceId dstDeviceId,
-                                            Type networkType,
-                                            boolean install) {
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(destVmIp)
-                .build();
-
-        PortNumber portNum = tunnelPortNumByNetType(networkType,
-                osNodeService.node(deviceId));
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setTunnelId(vni)
-                .extension(buildExtension(
-                        deviceService,
-                        deviceId,
-                        osNodeService.node(dstDeviceId).dataIp().getIp4Address()),
-                        deviceId)
-                .setOutput(portNum)
-                .build();
-
-        osFlowRuleService.setRule(
-                appId,
-                deviceId,
-                selector,
-                treatment,
-                PRIORITY_EXTERNAL_ROUTING_RULE,
-                Constants.GW_COMMON_TABLE,
-                install);
-    }
-
     private void setRulesToController(DeviceId deviceId,
                                       String segmentId,
                                       IpPrefix srcSubnet,
@@ -1084,7 +1168,6 @@
         }
 
         private void instPortDetected(InstancePort instPort) {
-            Network network = osNetworkAdminService.network(instPort.networkId());
             Type netType = osNetworkAdminService.networkType(instPort.networkId());
 
             if (netType == FLAT) {
@@ -1092,17 +1175,12 @@
             }
 
             if (useStatefulSnat) {
-                osNodeService.completeNodes(GATEWAY)
-                        .forEach(gwNode -> setRulesForSnatIngressRule(
-                                gwNode.intgBridge(),
-                                Long.parseLong(network.getProviderSegID()),
-                                IpPrefix.valueOf(instPort.ipAddress(), VM_PREFIX),
-                                instPort.deviceId(), netType, true));
+                osNodeService.completeNodes(GATEWAY).forEach(gwNode ->
+                        setGatewayToInstanceDownstreamRule(gwNode, instPort, true));
             }
         }
 
         private void instPortRemoved(InstancePort instPort) {
-            Network network = osNetworkAdminService.network(instPort.networkId());
             Type netType = osNetworkAdminService.networkType(instPort.networkId());
 
             if (netType == FLAT) {
@@ -1110,12 +1188,8 @@
             }
 
             if (useStatefulSnat) {
-                osNodeService.completeNodes(GATEWAY)
-                        .forEach(gwNode -> setRulesForSnatIngressRule(
-                                gwNode.intgBridge(),
-                                Long.parseLong(network.getProviderSegID()),
-                                IpPrefix.valueOf(instPort.ipAddress(), VM_PREFIX),
-                                instPort.deviceId(), netType, false));
+                osNodeService.completeNodes(GATEWAY).forEach(gwNode ->
+                        setGatewayToInstanceDownstreamRule(gwNode, instPort, false));
             }
         }
     }
@@ -1259,15 +1333,14 @@
             OpenstackNode osNode = event.subject();
             switch (event.type()) {
                 case OPENSTACK_NODE_COMPLETE:
+                    eventExecutor.execute(() -> processGatewayCompletion(osNode));
+                    eventExecutor.execute(() -> reconfigureRouters(osNode));
+                    break;
+                case OPENSTACK_NODE_REMOVED:
+                    eventExecutor.execute(() -> processGatewayRemoval(osNode));
                 case OPENSTACK_NODE_INCOMPLETE:
                 case OPENSTACK_NODE_UPDATED:
-                case OPENSTACK_NODE_REMOVED:
-                    eventExecutor.execute(() -> {
-                        if (!isRelevantHelper()) {
-                            return;
-                        }
-                        reconfigureRouters(osNode);
-                    });
+                    eventExecutor.execute(() -> reconfigureRouters(osNode));
                     break;
                 case OPENSTACK_NODE_CREATED:
                 default:
@@ -1275,7 +1348,33 @@
             }
         }
 
+        private void processGatewayCompletion(OpenstackNode osNode) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            if (useStatefulSnat && osNode.type() == GATEWAY) {
+                instancePortService.instancePorts().forEach(instPort ->
+                        setGatewayToInstanceDownstreamRule(osNode, instPort, true));
+            }
+        }
+
+        private void processGatewayRemoval(OpenstackNode osNode) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            if (useStatefulSnat && osNode.type() == GATEWAY) {
+                instancePortService.instancePorts().forEach(instPort ->
+                        setGatewayToInstanceDownstreamRule(osNode, instPort, false));
+            }
+        }
+
         private void reconfigureRouters(OpenstackNode osNode) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
             osRouterService.routers().forEach(osRouter -> {
                 routerUpdated(osRouter);
                 osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
@@ -1285,4 +1384,64 @@
             log.info("Reconfigure routers for {}", osNode.hostname());
         }
     }
+
+    private class PortRange {
+        private int min;
+        private int max;
+
+        /**
+         * A default constructor.
+         *
+         * @param min min port num
+         * @param max max port num
+         */
+        public PortRange(int min, int max) {
+            this.min = min;
+            this.max = max;
+        }
+
+        /**
+         * Obtains min port num.
+         *
+         * @return min port num
+         */
+        int min() {
+            return min;
+        }
+
+        /**
+         * Obtains max port num.
+         *
+         * @return max port num
+         */
+        int max() {
+            return max;
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                    .add("min", min)
+                    .add("max", max)
+                    .toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            PortRange portRange = (PortRange) o;
+            return min == portRange.min &&
+                    max == portRange.max;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(min, max);
+        }
+    }
 }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
index e180d34..b94a2df 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
@@ -454,7 +454,7 @@
         TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
 
         if (commit == CT_COMMIT || recircTable > 0) {
-            RulePopulatorUtil.NiriraConnTrackTreatmentBuilder natTreatmentBuilder =
+            RulePopulatorUtil.NiciraConnTrackTreatmentBuilder natTreatmentBuilder =
                     niciraConnTrackTreatmentBuilder(driverService, deviceId);
             natTreatmentBuilder.natAction(false);
             if (commit == CT_COMMIT) {
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/RulePopulatorUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/RulePopulatorUtil.java
index b5abaec..dace1ff 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/RulePopulatorUtil.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/RulePopulatorUtil.java
@@ -17,6 +17,7 @@
 
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.ExtensionSelectorResolver;
@@ -53,11 +54,22 @@
     private static final String CT_PRESENT_FLAGS = "presentFlags";
     private static final String CT_IPADDRESS_MIN = "ipAddressMin";
     private static final String CT_IPADDRESS_MAX = "ipAddressMax";
+    private static final String CT_PORT_MIN = "portMin";
+    private static final String CT_PORT_MAX = "portMax";
+    private static final String CT_NESTED_ACTIONS = "nestedActions";
 
-    private static final int ADDRESS_MIN_FLAG = 0;
-    private static final int ADDRESS_MAX_FLAG = 1;
-    private static final int PORT_MIN_FLAG = 2;
-    private static final int PORT_MAX_FLAG = 3;
+    public static final int CT_NAT_SRC_FLAG = 0;
+    public static final int CT_NAT_DST_FLAG = 1;
+    public static final int CT_NAT_PERSISTENT_FLAG = 2;
+    public static final int CT_NAT_PROTO_HASH_FLAG = 3;
+    public static final int CT_NAT_PROTO_RANDOM_FLAG = 4;
+
+    private static final int ADDRESS_V4_MIN_FLAG = 0;
+    private static final int ADDRESS_V4_MAX_FLAG = 1;
+    private static final int ADDRESS_V6_MIN_FLAG = 2;
+    private static final int ADDRESS_V6_MAX_FLAG = 3;
+    private static final int PORT_MIN_FLAG = 4;
+    private static final int PORT_MAX_FLAG = 5;
 
     // Refer to http://openvswitch.org/support/dist-docs/ovs-fields.7.txt for the values
     public static final long CT_STATE_NONE = 0;
@@ -76,9 +88,9 @@
      * @param id DeviceId
      * @return a builder for OVS Connection Tracking feature actions
      */
-    public static NiriraConnTrackTreatmentBuilder
+    public static NiciraConnTrackTreatmentBuilder
                     niciraConnTrackTreatmentBuilder(DriverService ds, DeviceId id) {
-        return new NiriraConnTrackTreatmentBuilder(ds, id);
+        return new NiciraConnTrackTreatmentBuilder(ds, id);
     }
 
     /**
@@ -207,18 +219,21 @@
     /**
      * Builder class for OVS Connection Tracking feature actions.
      */
-    public static final class NiriraConnTrackTreatmentBuilder {
+    public static final class NiciraConnTrackTreatmentBuilder {
 
         private DriverService driverService;
         private DeviceId deviceId;
         private IpAddress natAddress = null;
+        private TpPort natPortMin = null;
+        private TpPort natPortMax = null;
         private int zone;
         private boolean commit;
         private short table = -1;
         private boolean natAction;
+        private int natFlag;
 
-
-        private NiriraConnTrackTreatmentBuilder(DriverService driverService,
+        // private constructor
+        private NiciraConnTrackTreatmentBuilder(DriverService driverService,
                                                 DeviceId deviceId) {
             this.driverService = driverService;
             this.deviceId = deviceId;
@@ -230,7 +245,7 @@
          * @param c true if commit, false if not.
          * @return NiriraConnTrackTreatmentBuilder object
          */
-        public NiriraConnTrackTreatmentBuilder commit(boolean c) {
+        public NiciraConnTrackTreatmentBuilder commit(boolean c) {
             this.commit = c;
             return this;
         }
@@ -241,7 +256,7 @@
          * @param z zone number
          * @return NiriraConnTrackTreatmentBuilder object
          */
-        public NiriraConnTrackTreatmentBuilder zone(int z) {
+        public NiciraConnTrackTreatmentBuilder zone(int z) {
             this.zone = z;
             return this;
         }
@@ -252,7 +267,7 @@
          * @param t table number to restart
          * @return NiriraConnTrackTreatmentBuilder object
          */
-        public NiriraConnTrackTreatmentBuilder table(short t) {
+        public NiciraConnTrackTreatmentBuilder table(short t) {
             this.table = t;
             return this;
         }
@@ -263,18 +278,56 @@
          * @param ip NAT IP address
          * @return NiriraConnTrackTreatmentBuilder object
          */
-        public NiriraConnTrackTreatmentBuilder natIp(IpAddress ip) {
+        public NiciraConnTrackTreatmentBuilder natIp(IpAddress ip) {
             this.natAddress = ip;
             return this;
         }
 
         /**
+         * Sets min port for NAT.
+         *
+         * @param port port number
+         * @return NiciraConnTrackTreatmentBuilder object
+         */
+        public NiciraConnTrackTreatmentBuilder natPortMin(TpPort port) {
+            this.natPortMin = port;
+            return this;
+        }
+
+        /**
+         * Sets max port for NAT.
+         *
+         * @param port port number
+         * @return NiciraConnTrackTreatmentBuilder object
+         */
+        public NiciraConnTrackTreatmentBuilder natPortMax(TpPort port) {
+            this.natPortMax = port;
+            return this;
+        }
+
+        /**
+         * Sets NAT flags.
+         * SRC NAT: 1 << 0
+         * DST NAT: 1 << 1
+         * PERSISTENT NAT: 1 << 2
+         * PROTO_HASH NAT: 1 << 3
+         * PROTO_RANDOM NAT : 1 << 4
+         *
+         * @param flag flag value
+         * @return NiciraConnTrackTreatmentBuilder object
+         */
+        public NiciraConnTrackTreatmentBuilder natFlag(int flag) {
+            this.natFlag = 1 << flag;
+            return this;
+        }
+
+        /**
          * Sets the flag for NAT action.
          *
          * @param nat nat action is included if true, no nat action otherwise
          * @return NiriraConnTrackTreatmentBuilder object
          */
-        public NiriraConnTrackTreatmentBuilder natAction(boolean nat) {
+        public NiciraConnTrackTreatmentBuilder natAction(boolean nat) {
             this.natAction = nat;
             return this;
         }
@@ -286,21 +339,37 @@
          */
         public ExtensionTreatment build() {
             DriverHandler handler = driverService.createHandler(deviceId);
-            ExtensionTreatmentResolver etr = handler.behaviour(ExtensionTreatmentResolver.class);
+            ExtensionTreatmentResolver etr =
+                    handler.behaviour(ExtensionTreatmentResolver.class);
 
             ExtensionTreatment natTreatment = etr.getExtensionInstruction(
                     ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_NAT.type());
             try {
-                if (natAddress != null) {
-                    natTreatment.setPropertyValue(CT_FLAGS, 1);
-                    natTreatment.setPropertyValue(CT_PRESENT_FLAGS,
-                            buildPresentFlag(false, true));
-                    natTreatment.setPropertyValue(CT_IPADDRESS_MIN, natAddress);
-                    natTreatment.setPropertyValue(CT_IPADDRESS_MAX, natAddress);
-                } else {
+
+                if (natAddress == null && natPortMin == null && natPortMax == null) {
                     natTreatment.setPropertyValue(CT_FLAGS, 0);
                     natTreatment.setPropertyValue(CT_PRESENT_FLAGS, 0);
+                } else {
+                    natTreatment.setPropertyValue(CT_FLAGS, this.natFlag);
+
+                    natTreatment.setPropertyValue(CT_PRESENT_FLAGS,
+                            buildPresentFlag((natPortMin != null && natPortMax != null),
+                                    natAddress != null));
                 }
+
+                if (natAddress != null) {
+                    natTreatment.setPropertyValue(CT_IPADDRESS_MIN, natAddress);
+                    natTreatment.setPropertyValue(CT_IPADDRESS_MAX, natAddress);
+                }
+
+                if (natPortMin != null) {
+                    natTreatment.setPropertyValue(CT_PORT_MIN, natPortMin.toInt());
+                }
+
+                if (natPortMax != null) {
+                    natTreatment.setPropertyValue(CT_PORT_MAX, natPortMax.toInt());
+                }
+
             } catch (Exception e) {
                 log.error("Failed to set NAT due to error : {}", e);
                 return null;
@@ -316,7 +385,7 @@
                 ctTreatment.setPropertyValue(CT_FLAGS, commit ? 1 : 0);
                 ctTreatment.setPropertyValue(CT_ZONE, zone);
                 ctTreatment.setPropertyValue(CT_TABLE, table > -1 ? table : 0xff);
-                ctTreatment.setPropertyValue("nestedActions", nat);
+                ctTreatment.setPropertyValue(CT_NESTED_ACTIONS, nat);
             } catch (Exception e) {
                 log.error("Failed to set CT due to error : {}", e);
                 return null;
@@ -330,11 +399,12 @@
             int presentFlag = 0;
 
             if (isPortPresent) {
-                presentFlag = presentFlag | 1 << PORT_MIN_FLAG | 1 << PORT_MAX_FLAG;
+                presentFlag = 1 << PORT_MIN_FLAG | 1 << PORT_MAX_FLAG;
             }
 
             if (isAddressPresent) {
-                presentFlag =  1 << ADDRESS_MIN_FLAG | 1 << ADDRESS_MAX_FLAG;
+                // TODO: need to support IPv6 address
+                presentFlag =  presentFlag | 1 << ADDRESS_V4_MIN_FLAG | 1 << ADDRESS_V4_MAX_FLAG;
             }
 
             return presentFlag;