Refactored OpenstackRouting to support multiple gateway nodes

Change-Id: I6870ca9a4fd6f6b1cf2d2be72f52ef87827e1d2c
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIPHandler.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIPHandler.java
deleted file mode 100644
index 3d1c013..0000000
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIPHandler.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2016-present 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.openstacknetworking.routing;
-
-import org.onosproject.net.Host;
-import org.onosproject.openstackinterface.OpenstackFloatingIP;
-
-/**
- * Handle FloatingIP Event for Managing Flow Rules In Openstack Nodes.
- */
-public class OpenstackFloatingIPHandler implements Runnable {
-
-    public enum Action {
-        ASSOCIATE,
-        DISSASSOCIATE
-    }
-
-    private final OpenstackFloatingIP floatingIP;
-    private final OpenstackRoutingRulePopulator rulePopulator;
-    private final Host host;
-    private final Action action;
-
-
-    OpenstackFloatingIPHandler(OpenstackRoutingRulePopulator rulePopulator,
-                               OpenstackFloatingIP openstackFloatingIP, Action action, Host host) {
-        this.floatingIP = openstackFloatingIP;
-        this.rulePopulator = rulePopulator;
-        this.action = action;
-        this.host = host;
-    }
-
-    @Override
-    public void run() {
-        if (action == Action.ASSOCIATE) {
-            rulePopulator.populateFloatingIpRules(floatingIP);
-        } else {
-            rulePopulator.removeFloatingIpRules(floatingIP, host);
-        }
-    }
-}
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java
new file mode 100644
index 0000000..ba60fe8
--- /dev/null
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackFloatingIpManager.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2016-present 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.openstacknetworking.routing;
+
+import com.google.common.base.Strings;
+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.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.device.DeviceService;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstackinterface.OpenstackFloatingIP;
+import org.onosproject.openstacknetworking.Constants;
+import org.onosproject.openstacknetworking.OpenstackFloatingIpService;
+import org.onosproject.openstacknetworking.RulePopulatorUtil;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.Constants.*;
+import static org.onosproject.openstacknetworking.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
+
+
+@Service
+@Component(immediate = true)
+public class OpenstackFloatingIpManager implements OpenstackFloatingIpService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    private static final String NOT_ASSOCIATED = "null";
+    private static final KryoNamespace.Builder FLOATING_IP_SERIALIZER =
+            KryoNamespace.newBuilder().register(KryoNamespaces.API);
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
+    private ConsistentMap<IpAddress, Host> floatingIpMap;
+
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        nodeService.addListener(nodeListener);
+        floatingIpMap = storageService.<IpAddress, Host>consistentMapBuilder()
+                .withSerializer(Serializer.using(FLOATING_IP_SERIALIZER.build()))
+                .withName("openstackrouting-floatingip")
+                .withApplicationId(appId)
+                .build();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        nodeService.removeListener(nodeListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void createFloatingIp(OpenstackFloatingIP floatingIp) {
+    }
+
+    @Override
+    public void updateFloatingIp(OpenstackFloatingIP floatingIp) {
+        if (Strings.isNullOrEmpty(floatingIp.portId()) ||
+                floatingIp.portId().equals(NOT_ASSOCIATED)) {
+            eventExecutor.execute(() -> disassociateFloatingIp(floatingIp));
+        } else {
+            eventExecutor.execute(() -> associateFloatingIp(floatingIp));
+        }
+    }
+
+    @Override
+    public void deleteFloatingIp(String floatingIpId) {
+    }
+
+    private void associateFloatingIp(OpenstackFloatingIP floatingIp) {
+        Optional<Host> associatedVm = Tools.stream(hostService.getHosts())
+                .filter(host -> Objects.equals(
+                        host.annotations().value(PORT_ID),
+                        floatingIp.portId()))
+                .findAny();
+        if (!associatedVm.isPresent()) {
+            log.warn("Failed to associate floating IP({}) to port:{}",
+                     floatingIp.floatingIpAddress(),
+                     floatingIp.portId());
+            return;
+        }
+
+        floatingIpMap.put(floatingIp.floatingIpAddress(), associatedVm.get());
+        populateFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.get());
+
+        log.info("Associated floating IP {} to fixed IP {}",
+                 floatingIp.floatingIpAddress(), floatingIp.fixedIpAddress());
+    }
+
+    private void disassociateFloatingIp(OpenstackFloatingIP floatingIp) {
+        Versioned<Host> associatedVm = floatingIpMap.remove(floatingIp.floatingIpAddress());
+        if (associatedVm == null) {
+            log.warn("Failed to disassociate floating IP({})",
+                     floatingIp.floatingIpAddress());
+            // No VM is actually associated with the floating IP, do nothing
+            return;
+        }
+
+        removeFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.value());
+        log.info("Disassociated floating IP {} from fixed IP {}",
+                 floatingIp.floatingIpAddress(),
+                 associatedVm.value().ipAddresses());
+    }
+
+    private void populateFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+        populateFloatingIpIncomingRules(floatingIp, associatedVm);
+        populateFloatingIpOutgoingRules(floatingIp, associatedVm);
+    }
+
+    private void removeFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+        Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+        if (!fixedIp.isPresent()) {
+            log.warn("Failed to remove floating IP({}) from {}",
+                     floatingIp, associatedVm);
+            return;
+        }
+
+        TrafficSelector.Builder sOutgoingBuilder = DefaultTrafficSelector.builder();
+        TrafficSelector.Builder sIncomingBuilder = DefaultTrafficSelector.builder();
+
+        sOutgoingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                .matchIPSrc(fixedIp.get().toIpPrefix());
+
+        sIncomingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floatingIp.toIpPrefix());
+
+        gatewayService.getGatewayDeviceIds().stream().forEach(deviceId -> {
+            RulePopulatorUtil.removeRule(
+                    flowObjectiveService,
+                    appId,
+                    deviceId,
+                    sOutgoingBuilder.build(),
+                    ForwardingObjective.Flag.VERSATILE,
+                    FLOATING_RULE_PRIORITY);
+
+            RulePopulatorUtil.removeRule(
+                    flowObjectiveService,
+                    appId,
+                    deviceId,
+                    sIncomingBuilder.build(),
+                    ForwardingObjective.Flag.VERSATILE,
+                    FLOATING_RULE_PRIORITY);
+        });
+    }
+
+    private void populateFloatingIpIncomingRules(IpAddress floatingIp, Host associatedVm) {
+        DeviceId cnodeId = associatedVm.location().deviceId();
+        Optional<IpAddress> dataIp = nodeService.dataIp(cnodeId);
+        Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+
+        if (!fixedIp.isPresent() || !dataIp.isPresent()) {
+            log.warn("Failed to associate floating IP({})", floatingIp);
+            return;
+        }
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(floatingIp.toIpPrefix())
+                .build();
+
+        gatewayService.getGatewayDeviceIds().stream().forEach(gnodeId -> {
+            TrafficTreatment treatment =  DefaultTrafficTreatment.builder()
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(associatedVm.mac())
+                    .setIpDst(associatedVm.ipAddresses().stream().findFirst().get())
+                    .setTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                    .extension(buildExtension(deviceService, cnodeId, dataIp.get().getIp4Address()),
+                               cnodeId)
+                    .setOutput(nodeService.tunnelPort(gnodeId).get())
+                    .build();
+
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(FLOATING_RULE_PRIORITY)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gnodeId, fo);
+        });
+    }
+
+    private void populateFloatingIpOutgoingRules(IpAddress floatingIp, Host associatedVm) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+                .matchIPSrc(associatedVm.ipAddresses().stream().findFirst().get().toIpPrefix())
+                .build();
+
+        gatewayService.getGatewayDeviceIds().stream().forEach(gnodeId -> {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setIpSrc(floatingIp)
+                    .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+                    .setEthDst(Constants.DEFAULT_EXTERNAL_ROUTER_MAC)
+                    .setOutput(gatewayService.getUplinkPort(gnodeId))
+                    .build();
+
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(FLOATING_RULE_PRIORITY)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gnodeId, fo);
+        });
+    }
+
+    private void reloadFloatingIpRules() {
+        floatingIpMap.entrySet().stream().forEach(entry -> {
+            IpAddress floatingIp = entry.getKey();
+            Host associatedVm = entry.getValue().value();
+
+            populateFloatingIpRules(floatingIp, associatedVm);
+            log.debug("Reload floating IP {} mapped to {}",
+                      floatingIp, associatedVm.ipAddresses());
+        });
+    }
+
+    // TODO apply existing floating IPs on service start-up by handling host event
+    // TODO consider the case that port with associated floating IP is attached to a VM
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+
+            switch (event.type()) {
+                case COMPLETE:
+                    if (node.type() == GATEWAY) {
+                        log.info("GATEWAY node {} detected", node.hostname());
+                        GatewayNode gnode = GatewayNode.builder()
+                                .gatewayDeviceId(node.intBridge())
+                                .dataIpAddress(node.dataIp().getIp4Address())
+                                .uplinkIntf(node.externalPortName().get())
+                                .build();
+                        gatewayService.addGatewayNode(gnode);
+                        eventExecutor.execute(OpenstackFloatingIpManager.this::reloadFloatingIpRules);
+                    }
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                case INCOMPLETE:
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackIcmpHandler.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackIcmpHandler.java
index 7db84bb..b6bf09a 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackIcmpHandler.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackIcmpHandler.java
@@ -16,322 +16,319 @@
 package org.onosproject.openstacknetworking.routing;
 
 import com.google.common.collect.Maps;
+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.Ethernet;
 import org.onlab.packet.ICMP;
 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;
+import org.onosproject.core.CoreService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.device.DeviceService;
 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.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 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.onosproject.openstackinterface.OpenstackRouter;
 import org.onosproject.openstacknetworking.Constants;
 import org.onosproject.openstackinterface.OpenstackInterfaceService;
 import org.onosproject.openstackinterface.OpenstackPort;
 import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
 import org.onosproject.openstacknode.OpenstackNodeService;
 import org.onosproject.scalablegateway.api.ScalableGatewayService;
 import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.ExecutorService;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.Constants.*;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
 /**
- * Handle ICMP packet sent from Openstack Gateway nodes.
+ * Handle ICMP packet sent from OpenStack Gateway nodes.
+ * For a request to any private network gateway IPs, it generates fake reply.
+ * For a request to the external network, it does source NAT with a public IP and
+ * forward the request to the external only if the request instance has external
+ * connection setups.
  */
+@Component(immediate = true)
 public class OpenstackIcmpHandler {
     protected final Logger log = getLogger(getClass());
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
 
-    private static final String NETWORK_ROUTER_INTERFACE = "network:router_interface";
-    private static final String PORTNAME = "portName";
-    private static final String NETWORK_ROUTER_GATEWAY = "network:router_gateway";
-    private static final String NETWORK_FLOATING_IP = "network:floatingip";
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
 
-    private final PacketService packetService;
-    private final DeviceService deviceService;
-    private final ScalableGatewayService gatewayService;
-    private final HostService hostService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
     private final Map<String, Host> icmpInfoMap = Maps.newHashMap();
-    private final OpenstackInterfaceService openstackService;
-    private final OpenstackNodeService nodeService;
 
-    /**
-     * Default constructor.
-     *
-     * @param packetService             packet service
-     * @param deviceService             device service
-     * @param openstackService          openstackInterface service
-     */
-    OpenstackIcmpHandler(PacketService packetService,
-                         DeviceService deviceService,
-                         HostService hostService,
-                         OpenstackInterfaceService openstackService,
-                         OpenstackNodeService nodeService,
-                         ScalableGatewayService gatewayService
-                         ) {
-        this.packetService = packetService;
-        this.deviceService = deviceService;
-        this.hostService = hostService;
-        this.openstackService = checkNotNull(openstackService);
-        this.nodeService = nodeService;
-        this.gatewayService = gatewayService;
+    ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        nodeService.addListener(nodeListener);
+        requestPacket(appId);
+
+        log.info("Started");
     }
 
-    /**
-     * Requests ICMP packet.
-     *
-     * @param appId Application Id
-     */
-    public void requestPacket(ApplicationId appId) {
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
+    }
+
+    private void requestPacket(ApplicationId appId) {
         TrafficSelector icmpSelector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
                 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
                 .build();
 
-       // TODO: Return the correct gateway node
-        Optional<OpenstackNode> gwNode =  nodeService.nodes().stream()
-                .filter(n -> n.type().equals(OpenstackNodeService.NodeType.GATEWAY))
-                .findFirst();
-
-        if (!gwNode.isPresent()) {
-            log.warn("No Gateway is defined.");
-            return;
-        }
-
-        packetService.requestPackets(icmpSelector,
-                PacketPriority.CONTROL,
-                appId,
-                Optional.of(gwNode.get().intBridge()));
+        gatewayService.getGatewayDeviceIds().stream().forEach(gateway -> {
+            packetService.requestPackets(icmpSelector,
+                                         PacketPriority.CONTROL,
+                                         appId,
+                                         Optional.of(gateway));
+            log.debug("Requested ICMP packet on {}", gateway);
+        });
     }
 
-    /**
-     * Handles ICMP packet.
-     *
-     * @param context  packet context
-     * @param ethernet ethernet
-     */
-    public void processIcmpPacket(PacketContext context, Ethernet ethernet) {
-        checkNotNull(context, "context can not be null");
-        checkNotNull(ethernet, "ethernet can not be null");
-
+    private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
         IPv4 ipPacket = (IPv4) ethernet.getPayload();
-
-        log.debug("icmpEvent called from ip {}, mac {}", Ip4Address.valueOf(ipPacket.getSourceAddress()).toString(),
-                ethernet.getSourceMAC().toString());
+        log.trace("Processing ICMP packet from ip {}, mac {}",
+                  Ip4Address.valueOf(ipPacket.getSourceAddress()),
+                  ethernet.getSourceMAC());
 
         ICMP icmp = (ICMP) ipPacket.getPayload();
         short icmpId = getIcmpId(icmp);
 
-        DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
-        PortNumber portNumber = context.inPacket().receivedFrom().port();
-        if (icmp.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
-            //TODO: Considers icmp between internal subnets which are belonged to the same router.
-            Optional<Host> host = hostService.getHostsByMac(ethernet.getSourceMAC()).stream().findFirst();
-            if (!host.isPresent()) {
-                log.warn("No host found for MAC {}", ethernet.getSourceMAC());
-                return;
-            }
+        DeviceId srcDevice = context.inPacket().receivedFrom().deviceId();
+        switch (icmp.getIcmpType()) {
+            case ICMP.TYPE_ECHO_REQUEST:
+                Optional<Host> reqHost = hostService.getHostsByMac(ethernet.getSourceMAC())
+                        .stream().findFirst();
+                if (!reqHost.isPresent()) {
+                    log.warn("No host found for MAC {}", ethernet.getSourceMAC());
+                    return;
+                }
 
-            IpAddress gatewayIp = IpAddress.valueOf(host.get().annotations().value(Constants.GATEWAY_IP));
-            if (ipPacket.getDestinationAddress() == gatewayIp.getIp4Address().toInt()) {
-                processIcmpPacketSentToGateway(ipPacket, icmp, host.get());
-            } else {
-                Ip4Address pNatIpAddress = pNatIpForPort(host.get());
-                checkNotNull(pNatIpAddress, "pNatIpAddress can not be null");
+                // TODO Considers icmp between internal subnets belong to the same router.
+                // TODO do we have to support ICMP reply for non-existing gateway?
+                Ip4Address gatewayIp = Ip4Address.valueOf(
+                        reqHost.get().annotations().value(Constants.GATEWAY_IP));
+                if (Objects.equals(ipPacket.getDestinationAddress(), gatewayIp.toInt())) {
+                    processRequestToGateway(ipPacket, reqHost.get());
+                } else {
+                    Optional<Ip4Address> srcNatIp = getSrcNatIp(reqHost.get());
+                    if (!srcNatIp.isPresent()) {
+                        log.trace("VM {} has no external connection", reqHost.get());
+                        return;
+                    }
 
-                sendRequestPacketToExt(ipPacket, icmp, deviceId, pNatIpAddress);
-
+                    sendRequestToExternal(ipPacket, srcDevice, srcNatIp.get());
+                    String icmpInfoKey = String.valueOf(icmpId)
+                            .concat(String.valueOf(srcNatIp.get().toInt()))
+                            .concat(String.valueOf(ipPacket.getDestinationAddress()));
+                    icmpInfoMap.putIfAbsent(icmpInfoKey, reqHost.get());
+                }
+                break;
+            case ICMP.TYPE_ECHO_REPLY:
                 String icmpInfoKey = String.valueOf(icmpId)
-                        .concat(String.valueOf(pNatIpAddress.toInt()))
-                        .concat(String.valueOf(ipPacket.getDestinationAddress()));
-                icmpInfoMap.putIfAbsent(icmpInfoKey, host.get());
-            }
-        } else if (icmp.getIcmpType() == ICMP.TYPE_ECHO_REPLY) {
-            String icmpInfoKey = String.valueOf(icmpId)
-                    .concat(String.valueOf(ipPacket.getDestinationAddress()))
-                    .concat(String.valueOf(ipPacket.getSourceAddress()));
+                        .concat(String.valueOf(ipPacket.getDestinationAddress()))
+                        .concat(String.valueOf(ipPacket.getSourceAddress()));
 
-            processResponsePacketFromExternalToHost(ipPacket, icmp, icmpInfoMap.get(icmpInfoKey));
-
-            icmpInfoMap.remove(icmpInfoKey);
+                processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey));
+                icmpInfoMap.remove(icmpInfoKey);
+                break;
+            default:
+                break;
         }
     }
 
-    private void processIcmpPacketSentToExtenal(IPv4 icmpRequestIpv4, ICMP icmpRequest,
-                                                int destAddr, MacAddress destMac,
-                                                DeviceId deviceId, PortNumber portNumber) {
-        icmpRequest.setChecksum((short) 0);
-        icmpRequest.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
-        icmpRequestIpv4.setSourceAddress(icmpRequestIpv4.getDestinationAddress())
-                .setDestinationAddress(destAddr).resetChecksum();
-        icmpRequestIpv4.setPayload(icmpRequest);
-        Ethernet icmpResponseEth = new Ethernet();
-        icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
-                // TODO: Get the correct GW MAC
-                .setSourceMACAddress(Constants.GW_EXT_INT_MAC)
-                .setDestinationMACAddress(destMac).setPayload(icmpRequestIpv4);
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(portNumber).build();
-        OutboundPacket packet = new DefaultOutboundPacket(deviceId,
-                treatment, ByteBuffer.wrap(icmpResponseEth.serialize()));
-        packetService.emit(packet);
+    // TODO do we have to handle the request to the fake gateway?
+    private void processRequestToGateway(IPv4 ipPacket, Host reqHost) {
+        ICMP icmpReq = (ICMP) ipPacket.getPayload();
+        icmpReq.setChecksum((short) 0);
+        icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
+
+        ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
+                .setDestinationAddress(ipPacket.getSourceAddress())
+                .resetChecksum();
+
+        ipPacket.setPayload(icmpReq);
+        Ethernet icmpReply = new Ethernet();
+        icmpReply.setEtherType(Ethernet.TYPE_IPV4)
+                .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(reqHost.mac())
+                .setPayload(icmpReq);
+
+        sendReply(icmpReply, reqHost);
     }
 
-    private void processIcmpPacketSentToGateway(IPv4 icmpRequestIpv4, ICMP icmpRequest,
-                                                Host host) {
-        icmpRequest.setChecksum((short) 0);
-        icmpRequest.setIcmpType(ICMP.TYPE_ECHO_REPLY)
-                .resetChecksum();
-
-        Ip4Address ipAddress = host.ipAddresses().stream().findAny().get().getIp4Address();
-        icmpRequestIpv4.setSourceAddress(icmpRequestIpv4.getDestinationAddress())
-                .setDestinationAddress(ipAddress.toInt())
-                .resetChecksum();
-
-        icmpRequestIpv4.setPayload(icmpRequest);
-
-        Ethernet icmpResponseEth = new Ethernet();
-
-        icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
-                .setSourceMACAddress(Constants.GATEWAY_MAC)
-                .setDestinationMACAddress(host.mac())
-                .setPayload(icmpRequestIpv4);
-
-        sendResponsePacketToHost(icmpResponseEth, host);
-    }
-
-    private void sendRequestPacketToExt(IPv4 icmpRequestIpv4, ICMP icmpRequest, DeviceId deviceId,
-                                        Ip4Address pNatIpAddress) {
-        icmpRequest.resetChecksum();
-        icmpRequestIpv4.setSourceAddress(pNatIpAddress.toInt())
-                .resetChecksum();
-        icmpRequestIpv4.setPayload(icmpRequest);
+    private void sendRequestToExternal(IPv4 ipPacket, DeviceId srcDevice, Ip4Address srcNatIp) {
+        ICMP icmpReq = (ICMP) ipPacket.getPayload();
+        icmpReq.resetChecksum();
+        ipPacket.setSourceAddress(srcNatIp.toInt()).resetChecksum();
+        ipPacket.setPayload(icmpReq);
 
         Ethernet icmpRequestEth = new Ethernet();
-
         icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
-                // TODO: Get the correct one - Scalable Gateway ...
-                .setSourceMACAddress(Constants.GW_EXT_INT_MAC)
-                .setDestinationMACAddress(Constants.PHY_ROUTER_MAC)
-                .setPayload(icmpRequestIpv4);
-
-        // TODO: Return the correct gateway node
-        Optional<OpenstackNode> gwNode =  nodeService.nodes().stream()
-                .filter(n -> n.type().equals(OpenstackNodeService.NodeType.GATEWAY))
-                .findFirst();
-
-        if (!gwNode.isPresent()) {
-            log.warn("No Gateway is defined.");
-            return;
-        }
+                .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
+                .setPayload(ipPacket);
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                // FIXME: please double check this.
-                .setOutput(getPortForAnnotationPortName(gwNode.get().intBridge(),
-                        // FIXME: please double check this.
-                        org.onosproject.openstacknode.Constants.PATCH_INTG_BRIDGE))
+                .setOutput(gatewayService.getUplinkPort(srcDevice))
                 .build();
 
-        OutboundPacket packet = new DefaultOutboundPacket(deviceId,
-                treatment, ByteBuffer.wrap(icmpRequestEth.serialize()));
+        OutboundPacket packet = new DefaultOutboundPacket(
+                srcDevice,
+                treatment,
+                ByteBuffer.wrap(icmpRequestEth.serialize()));
 
         packetService.emit(packet);
     }
 
-    private void processResponsePacketFromExternalToHost(IPv4 icmpResponseIpv4, ICMP icmpResponse,
-                                                         Host host) {
-        icmpResponse.resetChecksum();
+    private void processReplyFromExternal(IPv4 ipPacket, Host dstHost) {
+        ICMP icmpReply = (ICMP) ipPacket.getPayload();
+        icmpReply.resetChecksum();
 
-        Ip4Address ipAddress = host.ipAddresses().stream().findFirst().get().getIp4Address();
-        icmpResponseIpv4.setDestinationAddress(ipAddress.toInt())
+        Ip4Address ipAddress = dstHost.ipAddresses().stream().findFirst().get().getIp4Address();
+        ipPacket.setDestinationAddress(ipAddress.toInt())
                 .resetChecksum();
-        icmpResponseIpv4.setPayload(icmpResponse);
+        ipPacket.setPayload(icmpReply);
 
         Ethernet icmpResponseEth = new Ethernet();
-
         icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
-                .setSourceMACAddress(Constants.GATEWAY_MAC)
-                .setDestinationMACAddress(host.mac())
-                .setPayload(icmpResponseIpv4);
+                .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+                .setDestinationMACAddress(dstHost.mac())
+                .setPayload(ipPacket);
 
-        sendResponsePacketToHost(icmpResponseEth, host);
+        sendReply(icmpResponseEth, dstHost);
     }
 
-    private void sendResponsePacketToHost(Ethernet icmpResponseEth, Host host) {
-
+    private void sendReply(Ethernet icmpReply, Host dstHost) {
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(host.location().port())
+                .setOutput(dstHost.location().port())
                 .build();
 
-        OutboundPacket packet = new DefaultOutboundPacket(host.location().deviceId(),
-                treatment, ByteBuffer.wrap(icmpResponseEth.serialize()));
+        OutboundPacket packet = new DefaultOutboundPacket(
+                dstHost.location().deviceId(),
+                treatment,
+                ByteBuffer.wrap(icmpReply.serialize()));
 
         packetService.emit(packet);
     }
 
+    private Optional<Ip4Address> getSrcNatIp(Host host) {
+        // TODO cache external gateway IP for each network because
+        // asking Neutron for every ICMP request is a bad idea
+        Optional<OpenstackPort> osPort = openstackService.ports().stream()
+                .filter(port -> port.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                        Objects.equals(host.annotations().value(NETWORK_ID),
+                                       port.networkId()))
+                .findAny();
+        if (!osPort.isPresent()) {
+            return Optional.empty();
+        }
+
+        OpenstackRouter osRouter = openstackService.router(osPort.get().deviceId());
+        if (osRouter == null) {
+            return Optional.empty();
+        }
+
+        return osRouter.gatewayExternalInfo().externalFixedIps()
+                .values().stream().findAny();
+    }
+
     private short getIcmpId(ICMP icmp) {
         return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
     }
 
-    private Ip4Address pNatIpForPort(Host host) {
+    private class InternalPacketProcessor implements PacketProcessor {
 
-        OpenstackPort openstackPort = openstackService.ports().stream()
-                .filter(p -> p.deviceOwner().equals(NETWORK_ROUTER_INTERFACE) &&
-                        p.networkId().equals(host.annotations().value(Constants.NETWORK_ID)))
-                .findAny().orElse(null);
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().contains(
+                    context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
 
-        checkNotNull(openstackPort, "openstackPort can not be null");
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                return;
+            }
 
-        return openstackService.router(openstackPort.deviceId())
-                .gatewayExternalInfo().externalFixedIps().values()
-                .stream().findAny().orElse(null);
-    }
-
-    private PortNumber getPortForAnnotationPortName(DeviceId deviceId, String match) {
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> p.annotations().value(PORTNAME).equals(match))
-                .findAny().orElse(null);
-
-        checkNotNull(port, "port cannot be null");
-
-        return port.number();
-    }
-
-    private boolean requestToOpenstackRoutingNetwork(int destAddr) {
-        OpenstackPort port = openstackService.ports().stream()
-                .filter(p -> p.deviceOwner().equals(NETWORK_ROUTER_GATEWAY) ||
-                        p.deviceOwner().equals(NETWORK_FLOATING_IP))
-                .filter(p -> p.fixedIps().containsValue(
-                        Ip4Address.valueOf(destAddr)))
-                .findAny().orElse(null);
-        if (port == null) {
-            return false;
+            IPv4 iPacket = (IPv4) ethernet.getPayload();
+            if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
+                    eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
+            }
         }
-        return true;
     }
-    private Map<DeviceId, PortNumber> getExternalInfo() {
-        Map<DeviceId, PortNumber> externalInfoMap = Maps.newHashMap();
-        gatewayService.getGatewayDeviceIds().forEach(deviceId ->
-                externalInfoMap.putIfAbsent(deviceId, gatewayService.getUplinkPort(deviceId)));
-        return externalInfoMap;
+
+    private class InternalNodeListener implements OpenstackNodeListener {
+
+        @Override
+        public void event(OpenstackNodeEvent event) {
+            OpenstackNode node = event.node();
+
+            switch (event.type()) {
+                case COMPLETE:
+                    if (node.type() == GATEWAY) {
+                        log.info("GATEWAY node {} detected", node.hostname());
+                        eventExecutor.execute(() -> requestPacket(appId));
+                    }
+                    break;
+                case INIT:
+                case DEVICE_CREATED:
+                case INCOMPLETE:
+                default:
+                    break;
+            }
+        }
     }
 }
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackPnatHandler.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackPnatHandler.java
index 5af208d..3b40aee 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackPnatHandler.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackPnatHandler.java
@@ -15,109 +15,156 @@
  */
 package org.onosproject.openstacknetworking.routing;
 
+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.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
+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;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.Port;
+import org.onosproject.net.Host;
+import org.onosproject.net.device.DeviceService;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.openstackinterface.OpenstackInterfaceService;
 import org.onosproject.openstackinterface.OpenstackPort;
 import org.onosproject.openstackinterface.OpenstackRouter;
-import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.openstacknetworking.RulePopulatorUtil;
+import org.onosproject.openstacknode.OpenstackNodeService;
 import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onlab.osgi.DefaultServiceDirectory.getService;
-
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.Constants.*;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Handle NAT packet processing for Managing Flow Rules In Openstack Nodes.
+ * Handle NAT packet processing for managing flow rules in openstack nodes.
  */
-public class OpenstackPnatHandler implements Runnable {
+@Component(immediate = true)
+public class OpenstackPnatHandler {
+    private final Logger log = getLogger(getClass());
 
-    volatile PacketContext context;
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
 
-    private final OpenstackRoutingRulePopulator rulePopulator;
-    private final int portNum;
-    private final OpenstackPort openstackPort;
-    private final Port port;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
 
-    private static final String DEVICE_OWNER_ROUTER_INTERFACE = "network:router_interface";
-    private static final String EXTERNAL_PORT_NULL = "There is no external port in this deviceId []";
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
 
-    OpenstackPnatHandler(OpenstackRoutingRulePopulator rulePopulator, PacketContext context,
-                         int portNum, OpenstackPort openstackPort, Port port) {
-        this.rulePopulator = checkNotNull(rulePopulator);
-        this.context = checkNotNull(context);
-        this.portNum = checkNotNull(portNum);
-        this.openstackPort = checkNotNull(openstackPort);
-        this.port = checkNotNull(port);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
+            .register(KryoNamespaces.API);
+
+    private static final int PNAT_PORT_EXPIRE_TIME = 1200 * 1000;
+    private static final int TP_PORT_MINIMUM_NUM = 1024;
+    private static final int TP_PORT_MAXIMUM_NUM = 65535;
+
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    private ConsistentMap<Integer, String> tpPortNumMap;
+    private ApplicationId appId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        tpPortNumMap = storageService.<Integer, String>consistentMapBuilder()
+                .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
+                .withName("openstackrouting-tpportnum")
+                .withApplicationId(appId)
+                .build();
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
     }
 
-    @Override
-    public void run() {
-        InboundPacket inboundPacket = context.inPacket();
-        Ethernet ethernet = checkNotNull(inboundPacket.parsed());
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
+    }
 
-        //TODO: Considers IPV6
-        if (ethernet.getEtherType() != Ethernet.TYPE_IPV4) {
-            log.warn("Now, we just consider IP version 4");
+    private void processPnatPacket(PacketContext context, Ethernet ethernet) {
+        IPv4 iPacket = (IPv4) ethernet.getPayload();
+        InboundPacket inboundPacket = context.inPacket();
+
+        int srcPort = getPortNum(ethernet.getSourceMAC(), iPacket.getDestinationAddress());
+        OpenstackPort osPort = getOpenstackPort(ethernet.getSourceMAC());
+        if (osPort == null) {
+            return;
+        }
+        Ip4Address externalGatewayIp = getExternalGatewayIp(osPort);
+        if (externalGatewayIp == null) {
             return;
         }
 
-        OpenstackRouter router = getOpenstackRouter(openstackPort);
+        populatePnatFlowRules(context.inPacket(),
+                osPort,
+                TpPort.tpPort(srcPort),
+                externalGatewayIp);
 
-        MacAddress externalMac = MacAddress.NONE;
-        MacAddress routerMac = MacAddress.NONE;
-
-        rulePopulator.populatePnatFlowRules(inboundPacket, openstackPort, portNum,
-                getExternalIp(router), externalMac, routerMac);
-
-        packetOut((Ethernet) ethernet.clone(), inboundPacket.receivedFrom().deviceId(), portNum, router);
+        packetOut((Ethernet) ethernet.clone(),
+                  inboundPacket.receivedFrom().deviceId(),
+                  srcPort,
+                  externalGatewayIp);
     }
 
-    private OpenstackRouter getOpenstackRouter(OpenstackPort openstackPort) {
-        OpenstackInterfaceService networkingService = getService(OpenstackInterfaceService.class);
-
-        OpenstackPort port = networkingService.ports()
-                .stream()
-                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
-                .filter(p -> checkSameSubnet(p, openstackPort))
-                .findAny()
-                .orElse(null);
-
-        return checkNotNull(networkingService.router(port.deviceId()));
-    }
-
-    private boolean checkSameSubnet(OpenstackPort p, OpenstackPort openstackPort) {
-        String key1 = checkNotNull(p.fixedIps().keySet().stream().findFirst().orElse(null)).toString();
-        String key2 = checkNotNull(openstackPort.fixedIps().keySet().stream().findFirst().orElse(null)).toString();
-        return key1.equals(key2) ? true : false;
-    }
-
-    private Ip4Address getExternalIp(OpenstackRouter router) {
-        return router.gatewayExternalInfo().externalFixedIps().values().stream().findAny().orElse(null);
-    }
-
-    private void packetOut(Ethernet ethernet, DeviceId deviceId, int portNum, OpenstackRouter router) {
-        PacketService packetService = getService(PacketService.class);
-
+    private void packetOut(Ethernet ethernet, DeviceId deviceId, int portNum, Ip4Address externalIp) {
         IPv4 iPacket = (IPv4) ethernet.getPayload();
-
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
 
         switch (iPacket.getProtocol()) {
@@ -136,26 +183,251 @@
                 iPacket.setPayload(udpPacket);
                 break;
             default:
-                log.error("Temporally, this method can process UDP and TCP protocol.");
+                log.trace("Temporally, this method can process UDP and TCP protocol.");
                 return;
         }
 
-        iPacket.setSourceAddress(getExternalIp(router).toString());
+        iPacket.setSourceAddress(externalIp.toString());
         iPacket.resetChecksum();
         iPacket.setParent(ethernet);
         ethernet.setPayload(iPacket);
 
-        ScalableGatewayService gatewayService = getService(ScalableGatewayService.class);
-        GatewayNode gatewayNode = gatewayService.getGatewayNode(deviceId);
-        if (gatewayNode.getUplinkIntf() == null) {
-            log.error(EXTERNAL_PORT_NULL, deviceId.toString());
+        treatment.setOutput(gatewayService.getUplinkPort(deviceId));
+        ethernet.resetChecksum();
+        packetService.emit(new DefaultOutboundPacket(
+                deviceId,
+                treatment.build(),
+                ByteBuffer.wrap(ethernet.serialize())));
+    }
+
+    private int getPortNum(MacAddress sourceMac, int destinationAddress) {
+        int portNum = findUnusedPortNum();
+        if (portNum == 0) {
+            clearPortNumMap();
+            portNum = findUnusedPortNum();
+        }
+        tpPortNumMap.put(portNum, sourceMac.toString().concat(":").concat(String.valueOf(destinationAddress)));
+        return portNum;
+    }
+
+    private int findUnusedPortNum() {
+        for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
+            if (!tpPortNumMap.containsKey(i)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    private void clearPortNumMap() {
+        tpPortNumMap.entrySet().forEach(e -> {
+            if (System.currentTimeMillis() - e.getValue().creationTime() > PNAT_PORT_EXPIRE_TIME) {
+                tpPortNumMap.remove(e.getKey());
+            }
+        });
+    }
+
+    // TODO there can be multiple routers connected to a particular openstack port
+    // TODO cache router information
+    private Ip4Address getExternalGatewayIp(OpenstackPort osPort) {
+        Optional<OpenstackPort> routerPort = openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
+                .filter(p -> checkSameSubnet(p, osPort))
+                .findAny();
+        if (!routerPort.isPresent()) {
+            log.warn("No router is connected to network {}", osPort.networkId());
+            return null;
+        }
+
+        OpenstackRouter osRouter = openstackService.router(routerPort.get().deviceId());
+        if (osRouter == null) {
+            log.warn("Failed to get OpenStack router {}",
+                     routerPort.get().deviceId());
+            return null;
+        }
+
+        return osRouter.gatewayExternalInfo().externalFixedIps().values()
+                .stream().findAny().orElse(null);
+    }
+
+    private OpenstackPort getOpenstackPort(MacAddress srcMac) {
+        Optional<Host> host = hostService.getHostsByMac(srcMac).stream()
+                .filter(h -> h.annotations().value(PORT_ID) != null)
+                .findAny();
+        if (!host.isPresent()) {
+            log.warn("Failed to find a host with MAC:{}", srcMac);
+            return null;
+        }
+        return openstackService.port(host.get().annotations().value(PORT_ID));
+    }
+
+    private boolean checkSameSubnet(OpenstackPort osPortA, OpenstackPort osPortB) {
+        return osPortA.fixedIps().keySet().stream()
+                .anyMatch(subnetId -> osPortB.fixedIps().keySet().contains(subnetId));
+    }
+
+    private void populatePnatFlowRules(InboundPacket inboundPacket,
+                                      OpenstackPort osPort,
+                                      TpPort patPort,
+                                      Ip4Address externalIp) {
+        long vni = getVni(osPort.networkId());
+        populatePnatIncomingFlowRules(vni, externalIp, patPort, inboundPacket);
+        populatePnatOutgoingFlowRules(vni, externalIp, patPort, inboundPacket);
+    }
+
+    private long getVni(String netId) {
+        // TODO remove this and use host vxlan annotation if applicable
+        return Long.parseLong(openstackService.network(netId).segmentId());
+    }
+
+    private void populatePnatOutgoingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
+                                               InboundPacket inboundPacket) {
+        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchTunnelId(vni)
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
+                .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
+                        .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
+                tBuilder.setTcpSrc(patPort)
+                        .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
+                        .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
+                tBuilder.setUdpSrc(patPort)
+                        .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
+
+                break;
+            default:
+                log.debug("Unsupported IPv4 protocol {}");
+                break;
+        }
+
+        tBuilder.setIpSrc(externalIp);
+        gatewayService.getGatewayNodes().stream().forEach(gateway -> {
+            TrafficTreatment.Builder tmpBuilder = tBuilder;
+            tmpBuilder.setOutput(gatewayService.getUplinkPort(gateway.getGatewayDeviceId()));
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(sBuilder.build())
+                    .withTreatment(tmpBuilder.build())
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(PNAT_RULE_PRIORITY)
+                    .makeTemporary(PNAT_TIMEOUT)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(gateway.getGatewayDeviceId(), fo);
+        });
+    }
+
+    private void populatePnatIncomingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
+                                               InboundPacket inboundPacket) {
+        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
+        IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(iPacket.getProtocol())
+                .matchIPDst(IpPrefix.valueOf(externalIp, 32))
+                .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.setTunnelId(vni)
+                .setEthDst(inboundPacket.parsed().getSourceMAC())
+                .setIpDst(internalIp);
+
+        switch (iPacket.getProtocol()) {
+            case IPv4.PROTOCOL_TCP:
+                TCP tcpPacket = (TCP) iPacket.getPayload();
+                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
+                        .matchTcpDst(patPort);
+                tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
+                break;
+            case IPv4.PROTOCOL_UDP:
+                UDP udpPacket = (UDP) iPacket.getPayload();
+                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
+                        .matchUdpDst(patPort);
+                tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
+                break;
+            default:
+                break;
+        }
+
+        Optional<Host> srcVm = Tools.stream(hostService.getHostsByIp(internalIp))
+                .filter(host -> Objects.equals(
+                        host.annotations().value(VXLAN_ID),
+                        String.valueOf(vni)))
+                .findFirst();
+        if (!srcVm.isPresent()) {
+            log.warn("Failed to find source VM with IP {}", internalIp);
             return;
         }
-        treatment.setOutput(gatewayService.getUplinkPort(deviceId));
 
-        ethernet.resetChecksum();
+        gatewayService.getGatewayDeviceIds().stream().forEach(deviceId -> {
+            DeviceId srcDeviceId = srcVm.get().location().deviceId();
+            TrafficTreatment.Builder tmpBuilder = tBuilder;
+            tmpBuilder.extension(RulePopulatorUtil.buildExtension(
+                    deviceService,
+                    deviceId,
+                    nodeService.dataIp(srcDeviceId).get().getIp4Address()), deviceId)
+                    .setOutput(nodeService.tunnelPort(deviceId).get());
 
-        packetService.emit(new DefaultOutboundPacket(deviceId, treatment.build(),
-                ByteBuffer.wrap(ethernet.serialize())));
+            ForwardingObjective fo = DefaultForwardingObjective.builder()
+                    .withSelector(sBuilder.build())
+                    .withTreatment(tmpBuilder.build())
+                    .withFlag(ForwardingObjective.Flag.VERSATILE)
+                    .withPriority(PNAT_RULE_PRIORITY)
+                    .makeTemporary(PNAT_TIMEOUT)
+                    .fromApp(appId)
+                    .add();
+
+            flowObjectiveService.forward(deviceId, fo);
+        });
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().contains(
+                    context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                return;
+            }
+
+            IPv4 iPacket = (IPv4) ethernet.getPayload();
+            switch (iPacket.getProtocol()) {
+                case IPv4.PROTOCOL_ICMP:
+                    break;
+                case IPv4.PROTOCOL_UDP:
+                    UDP udpPacket = (UDP) iPacket.getPayload();
+                    if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
+                            udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
+                        // don't process DHCP
+                        break;
+                    }
+                default:
+                    eventExecutor.execute(() -> processPnatPacket(context, ethernet));
+                    break;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingArpHandler.java
index 2c9a304..e31f410 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingArpHandler.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingArpHandler.java
@@ -15,99 +15,74 @@
  */
 package org.onosproject.openstacknetworking.routing;
 
-import com.google.common.collect.Lists;
+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.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.DeviceId;
-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.onosproject.openstackinterface.OpenstackInterfaceService;
 import org.onosproject.openstackinterface.OpenstackPort;
 import org.onosproject.scalablegateway.api.ScalableGatewayService;
 import org.onosproject.openstacknetworking.Constants;
-import org.onosproject.openstacknode.OpenstackNodeService;
 import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Optional;
+import java.util.concurrent.ExecutorService;
 
-import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.Constants.DEVICE_OWNER_FLOATING_IP;
+import static org.onosproject.openstacknetworking.Constants.DEVICE_OWNER_ROUTER_GATEWAY;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Handle ARP packet sent from Openstack Gateway nodes.
+ * Handle ARP, ICMP and NAT packets from gateway nodes.
  */
+@Component(immediate = true)
 public class OpenstackRoutingArpHandler {
-    protected final Logger log = getLogger(getClass());
+    private final Logger log = getLogger(getClass());
 
-    private final PacketService packetService;
-    private final OpenstackInterfaceService openstackService;
-    private final ScalableGatewayService gatewayService;
-    private final OpenstackNodeService nodeService;
-    private static final String NETWORK_ROUTER_GATEWAY = "network:router_gateway";
-    private static final String NETWORK_FLOATING_IP = "network:floatingip";
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
 
-    /**
-     * Default constructor.
-     *
-     * @param packetService packet service
-     * @param openstackService openstackInterface service
-     * @param gatewayService gateway service
-     * @param nodeService openstackNodeService
-     */
-    OpenstackRoutingArpHandler(PacketService packetService, OpenstackInterfaceService openstackService,
-                               OpenstackNodeService nodeService, ScalableGatewayService gatewayService) {
-        this.packetService = packetService;
-        this.openstackService = checkNotNull(openstackService);
-        this.nodeService = nodeService;
-        this.gatewayService = gatewayService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackInterfaceService openstackService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ScalableGatewayService gatewayService;
+
+    private final ExecutorService executorService =
+            newSingleThreadExecutor(groupedThreads("onos/openstackrouting", "packet-event", log));
+
+    private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    @Activate
+    protected void activate() {
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        log.info("Started");
     }
 
-    /**
-     * Requests ARP packet to GatewayNode.
-     *
-     * @param appId application id
-     */
-    public void requestPacket(ApplicationId appId) {
-
-        TrafficSelector arpSelector = DefaultTrafficSelector.builder()
-                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
-                .build();
-
-        getExternalInfo().forEach(deviceId ->
-                packetService.requestPackets(arpSelector,
-                        PacketPriority.CONTROL,
-                        appId,
-                        Optional.of(deviceId))
-        );
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(packetProcessor);
+        log.info("Stopped");
     }
 
-    /**
-     * Handles ARP packet.
-     *
-     * @param context packet context
-     * @param ethernet ethernet
-     */
-    public void processArpPacketFromRouter(PacketContext context, Ethernet ethernet) {
-        checkNotNull(context, "context can not be null");
-        checkNotNull(ethernet, "ethernet can not be null");
-
-
+    private void processArpPacket(PacketContext context, Ethernet ethernet) {
         ARP arp = (ARP) ethernet.getPayload();
-
-        log.debug("arpEvent called from {} to {}",
+        log.trace("arpEvent called from {} to {}",
                 Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
                 Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
 
@@ -116,13 +91,11 @@
         }
 
         IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
-
         if (getTargetMacForTargetIp(targetIp.getIp4Address()) == MacAddress.NONE) {
-                return;
+            return;
         }
-        // FIXME: Set the correct gateway
-        MacAddress targetMac = Constants.GW_EXT_INT_MAC;
 
+        MacAddress targetMac = Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
         Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
                 targetMac, ethernet);
 
@@ -136,22 +109,35 @@
                 ByteBuffer.wrap(ethReply.serialize())));
     }
 
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            } else if (!gatewayService.getGatewayDeviceIds().contains(
+                    context.inPacket().receivedFrom().deviceId())) {
+                // return if the packet is not from gateway nodes
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet != null &&
+                    ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                executorService.execute(() -> processArpPacket(context, ethernet));
+            }
+        }
+    }
+
+    // TODO make a cache for the MAC, not a good idea to REST call every time it gets ARP request
     private MacAddress getTargetMacForTargetIp(Ip4Address targetIp) {
         OpenstackPort port = openstackService.ports().stream()
-                .filter(p -> p.deviceOwner().equals(NETWORK_ROUTER_GATEWAY) ||
-                             p.deviceOwner().equals(NETWORK_FLOATING_IP))
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_GATEWAY) ||
+                        p.deviceOwner().equals(DEVICE_OWNER_FLOATING_IP))
                 .filter(p -> p.fixedIps().containsValue(targetIp.getIp4Address()))
                 .findAny().orElse(null);
 
-        if (port == null) {
-            return MacAddress.NONE;
-        }
-        return port.macAddress();
-    }
-
-    private List<DeviceId> getExternalInfo() {
-        List<DeviceId> externalInfoList = Lists.newArrayList();
-        gatewayService.getGatewayDeviceIds().forEach(externalInfoList::add);
-        return externalInfoList;
+        return port == null ? MacAddress.NONE : port.macAddress();
     }
 }
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
index 9f19a28..09b4b8a 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingManager.java
@@ -15,8 +15,7 @@
  */
 package org.onosproject.openstacknetworking.routing;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -24,82 +23,62 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 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.onlab.packet.UDP;
-import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
 import org.onosproject.core.ApplicationId;
-import org.onosproject.core.CoreService;
-import org.onosproject.mastership.MastershipService;
-import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.core.GroupId;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
-import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.DriverService;
+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.flowobjective.DefaultForwardingObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
-import org.onosproject.net.host.DefaultHostDescription;
-import org.onosproject.net.host.HostDescription;
-import org.onosproject.net.host.HostEvent;
-import org.onosproject.net.host.HostListener;
-import org.onosproject.net.host.HostProvider;
-import org.onosproject.net.host.HostProviderRegistry;
-import org.onosproject.net.host.HostProviderService;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.packet.InboundPacket;
-import org.onosproject.net.packet.PacketContext;
-import org.onosproject.net.packet.PacketProcessor;
-import org.onosproject.net.packet.PacketService;
-import org.onosproject.net.provider.AbstractProvider;
-import org.onosproject.net.provider.ProviderId;
-import org.onosproject.openstackinterface.OpenstackFloatingIP;
+import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
 import org.onosproject.openstackinterface.OpenstackPort;
 import org.onosproject.openstackinterface.OpenstackRouter;
 import org.onosproject.openstackinterface.OpenstackRouterInterface;
-import org.onosproject.openstacknetworking.OpenstackRoutingService;
-import org.onosproject.scalablegateway.api.ScalableGatewayService;
-import org.onosproject.openstacknetworking.routing.OpenstackFloatingIPHandler.Action;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.onosproject.openstacknetworking.AbstractVmHandler;
 import org.onosproject.openstacknetworking.Constants;
+import org.onosproject.openstacknetworking.OpenstackRoutingService;
+import org.onosproject.openstacknetworking.RulePopulatorUtil;
 import org.onosproject.openstacknode.OpenstackNode;
 import org.onosproject.openstacknode.OpenstackNodeEvent;
 import org.onosproject.openstacknode.OpenstackNodeListener;
 import org.onosproject.openstacknode.OpenstackNodeService;
-import org.onosproject.store.serializers.KryoNamespaces;
-import org.onosproject.store.service.ConsistentMap;
-import org.onosproject.store.service.Serializer;
-import org.onosproject.store.service.StorageService;
+import org.onosproject.scalablegateway.api.GatewayNode;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collection;
-import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
-import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknetworking.Constants.*;
+import static org.onosproject.openstacknetworking.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
 
 @Component(immediate = true)
 @Service
-/**
- * Populates flow rules about L3 functionality for VMs in Openstack.
- */
-public class OpenstackRoutingManager implements OpenstackRoutingService {
+public class OpenstackRoutingManager extends AbstractVmHandler implements OpenstackRoutingService {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CoreService coreService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected PacketService packetService;
+    protected FlowObjectiveService flowObjectiveService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
@@ -108,620 +87,392 @@
     protected OpenstackInterfaceService openstackService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected FlowObjectiveService flowObjectiveService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected DriverService driverService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected StorageService storageService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostService hostService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostProviderRegistry hostProviderRegistry;
+    protected OpenstackNodeService nodeService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ScalableGatewayService gatewayService;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected OpenstackNodeService nodeService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MastershipService mastershipService;
+    private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+    private final InternalNodeListener nodeListener = new InternalNodeListener();
 
     private ApplicationId appId;
-    private ConsistentMap<Integer, String> tpPortNumMap; // Map<PortNum, allocated VM`s Mac & destionation Ip address>
-    private ConsistentMap<String, OpenstackFloatingIP> floatingIpMap; // Map<FloatingIp`s Id, FloatingIp object>
-    // Map<RouterInterface`s portId, Corresponded port`s network id>
-    private ConsistentMap<String, String> routerInterfaceMap;
-    private static final ProviderId PID = new ProviderId("of", "org.onosproject.openstackroutering", true);
-    private static final String APP_ID = "org.onosproject.openstackrouting";
-    private static final String DEVICE_OWNER_ROUTER_INTERFACE = "network:router_interface";
-    private static final String FLOATING_IP_MAP_NAME = "openstackrouting-floatingip";
-    private static final String TP_PORT_MAP_NAME = "openstackrouting-tpportnum";
-    private static final String ROUTER_INTERFACE_MAP_NAME = "openstackrouting-routerinterface";
-    private static final String COLON = ":";
-    private static final int PNAT_PORT_EXPIRE_TIME = 1200 * 1000;
-    private static final int TP_PORT_MINIMUM_NUM = 1024;
-    private static final int TP_PORT_MAXIMUM_NUM = 65535;
-
-    private static final KryoNamespace.Builder FLOATING_IP_SERIALIZER = KryoNamespace.newBuilder()
-            .register(KryoNamespaces.API)
-            .register(OpenstackFloatingIP.FloatingIpStatus.class)
-            .register(OpenstackFloatingIP.class);
-
-    private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
-            .register(KryoNamespaces.API);
-
-    private static final KryoNamespace.Builder ROUTER_INTERFACE_SERIALIZER = KryoNamespace.newBuilder()
-            .register(KryoNamespaces.API);
-
-    private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor();
-    private InternalHostListener internalHostListener = new InternalHostListener();
-    private InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
-    private ExecutorService l3EventExecutorService =
-            Executors.newSingleThreadExecutor(groupedThreads("onos/openstackrouting", "L3-event"));
-    private ExecutorService icmpEventExecutorService =
-            Executors.newSingleThreadExecutor(groupedThreads("onos/openstackrouting", "icmp-event"));
-    private ExecutorService arpEventExecutorService =
-            Executors.newSingleThreadExecutor(groupedThreads("onos/openstackrouting", "arp-event"));
-    private OpenstackIcmpHandler openstackIcmpHandler;
-    private OpenstackRoutingArpHandler openstackArpHandler;
-    private OpenstackRoutingRulePopulator rulePopulator;
-
-    private HostProviderService hostProviderService;
-    private final HostProvider hostProvider = new InternalHostProvider();
 
     @Activate
     protected void activate() {
-        appId = coreService.registerApplication(APP_ID);
-        hostService.addListener(internalHostListener);
-        nodeService.addListener(internalNodeListener);
-        hostProviderService = hostProviderRegistry.register(hostProvider);
-
-        floatingIpMap = storageService.<String, OpenstackFloatingIP>consistentMapBuilder()
-                .withSerializer(Serializer.using(FLOATING_IP_SERIALIZER.build()))
-                .withName(FLOATING_IP_MAP_NAME)
-                .withApplicationId(appId)
-                .build();
-        tpPortNumMap = storageService.<Integer, String>consistentMapBuilder()
-                .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
-                .withName(TP_PORT_MAP_NAME)
-                .withApplicationId(appId)
-                .build();
-        routerInterfaceMap = storageService.<String, String>consistentMapBuilder()
-                .withSerializer(Serializer.using(ROUTER_INTERFACE_SERIALIZER.build()))
-                .withName(ROUTER_INTERFACE_MAP_NAME)
-                .withApplicationId(appId)
-                .build();
-
-        log.info("started");
+        super.activate();
+        appId = coreService.registerApplication(ROUTING_APP_ID);
+        nodeService.addListener(nodeListener);
     }
 
     @Deactivate
     protected void deactivate() {
-        packetService.removeProcessor(internalPacketProcessor);
-        hostService.removeListener(internalHostListener);
-        nodeService.removeListener(internalNodeListener);
-
-        l3EventExecutorService.shutdown();
-        icmpEventExecutorService.shutdown();
-        arpEventExecutorService.shutdown();
-
-        floatingIpMap.clear();
-        tpPortNumMap.clear();
-        routerInterfaceMap.clear();
-
+        nodeService.removeListener(nodeListener);
         log.info("stopped");
     }
 
-
     @Override
-    public void createFloatingIP(OpenstackFloatingIP openstackFloatingIp) {
-        floatingIpMap.put(openstackFloatingIp.id(), openstackFloatingIp);
+    public void createRouter(OpenstackRouter osRouter) {
     }
 
     @Override
-    public void updateFloatingIP(OpenstackFloatingIP openstackFloatingIp) {
-        if (!floatingIpMap.containsKey(openstackFloatingIp.id())) {
-            log.warn("There`s no information about {} in FloatingIpMap", openstackFloatingIp.id());
+    public void updateRouter(OpenstackRouter osRouter) {
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            openstackService.ports().stream()
+                    .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                            osPort.deviceId().equals(osRouter.id()))
+                    .forEach(osPort -> setExternalConnection(osRouter, osPort.networkId()));
+
+            log.info("Connected external gateway {} to router {}",
+                     osRouter.gatewayExternalInfo().externalFixedIps(),
+                     osRouter.name());
+        } else {
+            openstackService.ports().stream()
+                    .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                            osPort.deviceId().equals(osRouter.id()))
+                    .forEach(osPort -> unsetExternalConnection(osRouter, osPort.networkId()));
+
+            log.info("Disconnected external gateway from router {}",
+                     osRouter.name());
+        }
+    }
+
+    @Override
+    public void removeRouter(String osRouterId) {
+        // TODO implement this
+    }
+
+    @Override
+    public void addRouterInterface(OpenstackRouterInterface routerIface) {
+        OpenstackRouter osRouter = openstackRouter(routerIface.id());
+        OpenstackPort osPort = openstackService.port(routerIface.portId());
+        if (osRouter == null || osPort == null) {
+            log.warn("Failed to add router interface {}", routerIface);
             return;
         }
-        if (openstackFloatingIp.portId() == null || openstackFloatingIp.portId().equals("null")) {
-            OpenstackFloatingIP floatingIp = floatingIpMap.get(openstackFloatingIp.id()).value();
-            // XXX When the VM has been removed, host information has been removed or not ???
-            Optional<Host> host = hostService.getHostsByIp(openstackFloatingIp.fixedIpAddress().getIp4Address())
-                    .stream()
-                    .findFirst();
-            if (!host.isPresent()) {
-                log.warn("No Host info with the VM IP the Floating IP address {} is found",
-                        openstackFloatingIp.floatingIpAddress());
-                return;
-            }
-            l3EventExecutorService.execute(
-                    new OpenstackFloatingIPHandler(rulePopulator, floatingIp, Action.DISSASSOCIATE, host.get()));
-            floatingIpMap.replace(floatingIp.id(), openstackFloatingIp);
-            registerFloatingIpToHostService(openstackFloatingIp, Action.DISSASSOCIATE);
-        } else {
-            floatingIpMap.put(openstackFloatingIp.id(), openstackFloatingIp);
-            l3EventExecutorService.execute(
-                    new OpenstackFloatingIPHandler(rulePopulator, openstackFloatingIp, Action.ASSOCIATE, null));
-            registerFloatingIpToHostService(openstackFloatingIp, Action.ASSOCIATE);
+
+        setRoutes(osRouter, Optional.empty());
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            setExternalConnection(osRouter, osPort.networkId());
         }
+        log.info("Connected {} to router {}", osPort.fixedIps(), osRouter.name());
     }
 
     @Override
-    public void deleteFloatingIP(String id) {
-        floatingIpMap.remove(id);
-    }
-
-    @Override
-    public void createRouter(OpenstackRouter openstackRouter) {
-    }
-
-    @Override
-    public void updateRouter(OpenstackRouter openstackRouter) {
-        if (openstackRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
-            checkExternalConnection(openstackRouter, getOpenstackRouterInterface(openstackRouter));
-        } else {
-            unsetExternalConnection();
-        }
-    }
-
-    private void unsetExternalConnection() {
-        Collection<OpenstackRouter> internalRouters = getExternalRouter(false);
-        internalRouters.forEach(r ->
-                getOpenstackRouterInterface(r).forEach(i -> rulePopulator.removeExternalRules(i)));
-    }
-
-    private Collection<OpenstackRouter> getExternalRouter(boolean externalConnection) {
-        List<OpenstackRouter> routers;
-        if (externalConnection) {
-            routers = openstackService.routers()
-                    .stream()
-                    .filter(r -> (r.gatewayExternalInfo().externalFixedIps().size() > 0))
-                    .collect(Collectors.toList());
-        } else {
-            routers = openstackService.routers()
-                    .stream()
-                    .filter(r -> (r.gatewayExternalInfo().externalFixedIps().size() == 0))
-                    .collect(Collectors.toList());
-        }
-        return routers;
-    }
-
-    @Override
-    public void deleteRouter(String id) {
-        //TODO : In now, there`s nothing to do for deleteRouter process. It is reserved.
-    }
-
-    @Override
-    public void updateRouterInterface(OpenstackRouterInterface routerInterface) {
-        List<OpenstackRouterInterface> routerInterfaces = Lists.newArrayList();
-        routerInterfaces.add(routerInterface);
-        checkExternalConnection(getOpenstackRouter(routerInterface.id()), routerInterfaces);
-        setL3Connection(getOpenstackRouter(routerInterface.id()), null);
-        routerInterfaceMap.put(routerInterface.portId(), openstackService.port(routerInterface.portId()).networkId());
-    }
-
-    /**
-     * Set flow rules for traffic between two different subnets when more than one subnets
-     * connected to a router.
-     *
-     * @param openstackRouter OpenstackRouter Info
-     * @param openstackPort OpenstackPort Info
-     */
-    private void setL3Connection(OpenstackRouter openstackRouter, OpenstackPort openstackPort) {
-        Collection<OpenstackRouterInterface> interfaceList = getOpenstackRouterInterface(openstackRouter);
-
-        if (interfaceList.size() < 2) {
+    public void removeRouterInterface(OpenstackRouterInterface routerIface) {
+        OpenstackRouter osRouter = openstackService.router(routerIface.id());
+        if (osRouter == null) {
+            log.warn("Failed to remove router interface {}", routerIface);
             return;
         }
-        if (openstackPort == null) {
-            interfaceList.forEach(i -> {
-                OpenstackPort interfacePort = openstackService.port(i.portId());
-                openstackService.ports()
-                        .stream()
-                        .filter(p -> p.networkId().equals(interfacePort.networkId())
-                                && !p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
-                        .forEach(p -> rulePopulator.populateL3Rules(p,
-                                    getL3ConnectionList(p.networkId(), interfaceList)));
 
-            });
-        } else {
-            rulePopulator.populateL3Rules(openstackPort, getL3ConnectionList(openstackPort.networkId(), interfaceList));
+        OpenstackSubnet osSubnet = openstackService.subnet(routerIface.subnetId());
+        OpenstackNetwork osNet = openstackService.network(osSubnet.networkId());
+
+        unsetRoutes(osRouter, osNet);
+        if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+            unsetExternalConnection(osRouter, osNet.id());
         }
-
+        log.info("Disconnected {} from router {}", osSubnet.cidr(), osRouter.name());
     }
 
-    private List<OpenstackRouterInterface> getL3ConnectionList(String networkId,
-                                                               Collection<OpenstackRouterInterface> interfaceList) {
-        List<OpenstackRouterInterface> targetList = Lists.newArrayList();
-        interfaceList.forEach(i -> {
-            OpenstackPort port = openstackService.port(i.portId());
-            if (!port.networkId().equals(networkId)) {
-                targetList.add(i);
-            }
-        });
-        return targetList;
-    }
-
-    @Override
-    public void removeRouterInterface(OpenstackRouterInterface routerInterface) {
-        OpenstackRouter router = openstackService.router(routerInterface.id());
-        Collection<OpenstackRouterInterface> interfaceList = getOpenstackRouterInterface(router);
-        if (interfaceList.size() == 1) {
-            List<OpenstackRouterInterface> newList = Lists.newArrayList();
-            newList.add(routerInterface);
-            interfaceList.forEach(i -> removeL3RulesForRouterInterface(i, router, newList));
-        }
-        removeL3RulesForRouterInterface(routerInterface, router, null);
-        rulePopulator.removeExternalRules(routerInterface);
-        routerInterfaceMap.remove(routerInterface.portId());
-    }
-
-    private void removeL3RulesForRouterInterface(OpenstackRouterInterface routerInterface, OpenstackRouter router,
-                                                 List<OpenstackRouterInterface> newList) {
-        if (!routerInterfaceMap.containsKey(routerInterface.portId())) {
-            log.warn("No router interface information found for {}", routerInterface.portId());
+    private void setExternalConnection(OpenstackRouter osRouter, String osNetId) {
+        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
+            log.debug("Source NAT is disabled");
             return;
         }
-        openstackService.ports(routerInterfaceMap.get(routerInterface.portId()).value()).forEach(p -> {
-                    Ip4Address vmIp = (Ip4Address) p.fixedIps().values().toArray()[0];
-                    if (newList == null) {
-                        rulePopulator.removeL3Rules(vmIp,
-                                getL3ConnectionList(p.networkId(), getOpenstackRouterInterface(router)));
-                    } else {
-                        rulePopulator.removeL3Rules(vmIp, newList);
-                    }
-                }
-        );
+
+        // FIXME router interface is subnet specific, not network
+        OpenstackNetwork osNet = openstackService.network(osNetId);
+        populateExternalRules(osNet);
     }
 
-    @Override
-    public String networkIdForRouterInterface(String portId) {
-        return routerInterfaceMap.get(portId).value();
-    }
-
-    private Collection<OpenstackFloatingIP> associatedFloatingIps() {
-        List<OpenstackFloatingIP> fIps = Lists.newArrayList();
-        floatingIpMap.values()
-                .stream()
-                .filter(fIp -> fIp.value().portId() != null)
-                .forEach(fIp -> fIps.add(fIp.value()));
-        return fIps;
-    }
-
-    private void reloadInitL3Rules() {
-
-        l3EventExecutorService.execute(() ->
-                openstackService.ports()
-                        .stream()
-                        .forEach(p ->
-                        {
-                            if (p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE)) {
-                                updateRouterInterface(portToRouterInterface(p));
-                            } else {
-                                Optional<Ip4Address> vmIp = p.fixedIps().values().stream().findAny();
-                                if (vmIp.isPresent()) {
-                                    OpenstackFloatingIP floatingIP = getOpenstackFloatingIp(vmIp.get());
-                                    if (floatingIP != null) {
-                                        updateFloatingIP(floatingIP);
-                                    }
-                                }
-                            }
-                        })
-        );
-    }
-
-    private OpenstackRouterInterface portToRouterInterface(OpenstackPort p) {
-        OpenstackRouterInterface.Builder osBuilder = new OpenstackRouterInterface.Builder()
-                .id(checkNotNull(p.deviceId()))
-                .tenantId(checkNotNull(openstackService.network(p.networkId()).tenantId()))
-                .subnetId(checkNotNull(p.fixedIps().keySet().stream().findFirst().orElse(null)).toString())
-                .portId(checkNotNull(p.id()));
-
-        return osBuilder.build();
-    }
-
-    private class InternalPacketProcessor implements PacketProcessor {
-
-        @Override
-        public void process(PacketContext context) {
-
-            DeviceId senderDeviceId = context.inPacket().receivedFrom().deviceId();
-            if (!nodeService.routerBridge(senderDeviceId).isPresent()) {
-                log.warn("No router bridge for {} is found.", senderDeviceId);
-                return;
-            }
-            if (context.isHandled()) {
-                return;
-            } else if (!checkGatewayNode(context.inPacket().receivedFrom().deviceId())) {
-                return;
-            }
-
-            InboundPacket pkt = context.inPacket();
-            Ethernet ethernet = pkt.parsed();
-
-            //TODO: Considers IPv6 later.
-            if (ethernet == null) {
-                return;
-            } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) {
-                IPv4 iPacket = (IPv4) ethernet.getPayload();
-                switch (iPacket.getProtocol()) {
-                    case IPv4.PROTOCOL_ICMP:
-
-                        icmpEventExecutorService.execute(() ->
-                                openstackIcmpHandler.processIcmpPacket(context, ethernet));
-                        break;
-                    case IPv4.PROTOCOL_UDP:
-                        // don't process DHCP
-                        UDP udpPacket = (UDP) iPacket.getPayload();
-                        if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
-                                udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
-                            break;
-                        }
-                    default:
-                        int portNum = getPortNum(ethernet.getSourceMAC(), iPacket.getDestinationAddress());
-                        DeviceId deviceId = pkt.receivedFrom().deviceId();
-                        Port port = null;
-                        port = deviceService.getPort(deviceId,
-                                gatewayService.getUplinkPort(deviceId));
-                        if (port != null) {
-                            OpenstackPort openstackPort = getOpenstackPort(ethernet.getSourceMAC(),
-                                    Ip4Address.valueOf(iPacket.getSourceAddress()));
-                            l3EventExecutorService.execute(new OpenstackPnatHandler(rulePopulator, context,
-                                    portNum, openstackPort, port));
-
-                        } else {
-                            log.warn("There`s no external interface");
-                        }
-
-                        break;
-                }
-            } else if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
-                arpEventExecutorService.execute(() ->
-                        openstackArpHandler.processArpPacketFromRouter(context, ethernet));
-            }
+    private void unsetExternalConnection(OpenstackRouter osRouter, String osNetId) {
+        if (!osRouter.gatewayExternalInfo().isEnablePnat()) {
+            log.debug("Source NAT is disabled");
+            return;
         }
 
-        private int getPortNum(MacAddress sourceMac, int destinationAddress) {
-            int portNum = findUnusedPortNum();
-            if (portNum == 0) {
-                clearPortNumMap();
-                portNum = findUnusedPortNum();
-            }
-            tpPortNumMap.put(portNum, sourceMac.toString().concat(COLON).concat(String.valueOf(destinationAddress)));
-            return portNum;
-        }
-
-        private int findUnusedPortNum() {
-            for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
-                if (!tpPortNumMap.containsKey(i)) {
-                    return i;
-                }
-            }
-            return 0;
-        }
-
+        // FIXME router interface is subnet specific, not network
+        OpenstackNetwork osNet = openstackService.network(osNetId);
+        removeExternalRules(osNet);
     }
 
-    private boolean checkGatewayNode(DeviceId deviceId) {
-        return gatewayService.getGatewayDeviceIds().contains(deviceId);
+    private void setRoutes(OpenstackRouter osRouter, Optional<Host> host) {
+        Set<OpenstackNetwork> routableNets = routableNetworks(osRouter.id());
+        if (routableNets.size() < 2) {
+            // no other subnet interface is connected to this router, do nothing
+            return;
+        }
+
+        // FIXME router interface is subnet specific, not network
+        Set<String> routableNetIds = routableNets.stream()
+                .map(OpenstackNetwork::id)
+                .collect(Collectors.toSet());
+
+        Set<Host> hosts = host.isPresent() ? ImmutableSet.of(host.get()) :
+                Tools.stream(hostService.getHosts())
+                        .filter(h -> routableNetIds.contains(h.annotations().value(NETWORK_ID)))
+                        .collect(Collectors.toSet());
+
+        hosts.stream().forEach(h -> populateRoutingRules(h, routableNets));
     }
 
-    private void clearPortNumMap() {
-        tpPortNumMap.entrySet().forEach(e -> {
-            if (System.currentTimeMillis() - e.getValue().creationTime() > PNAT_PORT_EXPIRE_TIME) {
-                tpPortNumMap.remove(e.getKey());
-            }
+    private void unsetRoutes(OpenstackRouter osRouter, OpenstackNetwork osNet) {
+        // FIXME router interface is subnet specific, not network
+        Set<OpenstackNetwork> routableNets = routableNetworks(osRouter.id());
+        Tools.stream(hostService.getHosts())
+                .filter(h -> Objects.equals(
+                        h.annotations().value(NETWORK_ID), osNet.id()))
+                .forEach(h -> removeRoutingRules(h, routableNets));
+
+        routableNets.stream().forEach(n -> {
+            Tools.stream(hostService.getHosts())
+                    .filter(h -> Objects.equals(
+                            h.annotations().value(NETWORK_ID),
+                            n.id()))
+                    .forEach(h -> removeRoutingRules(h, ImmutableSet.of(osNet)));
+            log.debug("Removed between {} to {}", n.name(), osNet.name());
         });
     }
 
-    private Optional<Port> getExternalPort(DeviceId deviceId, String interfaceName) {
-        return deviceService.getPorts(deviceId)
-                .stream()
-                .filter(p -> p.annotations().value(PORT_NAME).equals(interfaceName))
-                .findAny();
-    }
-
-    private void checkExternalConnection(OpenstackRouter router,
-                                         Collection<OpenstackRouterInterface> interfaces) {
-        checkNotNull(router, "Router can not be null");
-        checkNotNull(interfaces, "routerInterfaces can not be null");
-        Ip4Address externalIp = router.gatewayExternalInfo().externalFixedIps()
-                .values().stream().findFirst().orElse(null);
-        if ((externalIp == null) || (!router.gatewayExternalInfo().isEnablePnat())) {
-            log.debug("Not satisfied to set pnat configuration");
-            return;
-        }
-        interfaces.forEach(this::initiateL3Rule);
-    }
-
-    private Optional<OpenstackRouter> getRouterfromExternalIp(Ip4Address externalIp) {
-        return getExternalRouter(true)
-                .stream()
-                .filter(r -> r.gatewayExternalInfo()
-                        .externalFixedIps()
-                        .values()
-                        .stream()
-                        .findAny()
-                        .get()
-                        .equals(externalIp))
-                .findAny();
-    }
-
-    private void initiateL3Rule(OpenstackRouterInterface routerInterface) {
-        long vni = Long.parseLong(openstackService.network(openstackService
-                .port(routerInterface.portId()).networkId()).segmentId());
-        rulePopulator.populateExternalRules(vni);
-    }
-
-    private Collection<OpenstackRouterInterface> getOpenstackRouterInterface(OpenstackRouter router) {
-        List<OpenstackRouterInterface> interfaces = Lists.newArrayList();
-        openstackService.ports()
-                .stream()
-                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE)
-                        && p.deviceId().equals(router.id()))
-                .forEach(p -> interfaces.add(portToRouterInterface(p)));
-        return interfaces;
-    }
-
-    private OpenstackRouter getOpenstackRouter(String id) {
+    private OpenstackRouter openstackRouter(String routerId) {
         return openstackService.routers().stream().filter(r ->
-                r.id().equals(id)).iterator().next();
+                r.id().equals(routerId)).iterator().next();
     }
 
-    private OpenstackPort getOpenstackPort(MacAddress sourceMac, Ip4Address ip4Address) {
-        OpenstackPort openstackPort = openstackService.ports().stream()
-                .filter(p -> p.macAddress().equals(sourceMac)).iterator().next();
-        return openstackPort.fixedIps().values().stream().filter(ip ->
-                ip.equals(ip4Address)).count() > 0 ? openstackPort : null;
-    }
-
-    private OpenstackFloatingIP getOpenstackFloatingIp(Ip4Address vmIp) {
-        Optional<OpenstackFloatingIP> floatingIp = floatingIpMap.asJavaMap().values().stream()
-                .filter(f -> f.portId() != null && f.fixedIpAddress().equals(vmIp))
-                .findAny();
-
-        if (floatingIp.isPresent()) {
-            return floatingIp.get();
-        }
-        log.debug("There is no floating IP information for VM IP {}", vmIp);
-
-        return null;
-    }
-
-    private Optional<OpenstackPort> getRouterInterfacePort(String networkId) {
-
-        return openstackService.ports()
-                .stream()
-                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE)
-                        && p.networkId().equals(networkId))
+    private Optional<OpenstackPort> routerIfacePort(String osNetId) {
+        // FIXME router interface is subnet specific, not network
+        return openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                        p.networkId().equals(osNetId))
                 .findAny();
     }
 
-    // TODO: Remove the function and the related codes when vRouter is running on different ONOS instance.
-    private void registerFloatingIpToHostService(OpenstackFloatingIP openstackFloatingIp, Action action) {
+    private Set<OpenstackNetwork> routableNetworks(String osRouterId) {
+        // FIXME router interface is subnet specific, not network
+        return openstackService.ports().stream()
+                .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
+                        p.deviceId().equals(osRouterId))
+                .map(p -> openstackService.network(p.networkId()))
+                .collect(Collectors.toSet());
+    }
 
-        Optional<Host> hostOptional = hostService.getHostsByIp(openstackFloatingIp.fixedIpAddress())
-                .stream()
-                .findFirst();
-        if (!hostOptional.isPresent()) {
-            log.warn("No host with IP {} is registered and cannot add the floating IP. ",
-                    openstackFloatingIp.floatingIpAddress());
+    private void populateExternalRules(OpenstackNetwork osNet) {
+        populateCnodeToGateway(Long.valueOf(osNet.segmentId()));
+        populateGatewayToController(Long.valueOf(osNet.segmentId()));
+    }
+
+    private void removeExternalRules(OpenstackNetwork osNet) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.valueOf(osNet.segmentId()))
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        nodeService.completeNodes().stream().forEach(node -> {
+            ForwardingObjective.Flag flag = node.type().equals(GATEWAY) ?
+                    ForwardingObjective.Flag.VERSATILE :
+                    ForwardingObjective.Flag.SPECIFIC;
+
+            RulePopulatorUtil.removeRule(
+                    flowObjectiveService,
+                    appId,
+                    node.intBridge(),
+                    sBuilder.build(),
+                    flag,
+                    ROUTING_RULE_PRIORITY);
+        });
+    }
+
+    private void populateRoutingRules(Host host, Set<OpenstackNetwork> osNets) {
+        String osNetId = host.annotations().value(NETWORK_ID);
+        if (osNetId == null) {
             return;
         }
 
-        Host host = hostOptional.get();
-        Set<IpAddress> ipAddresses = Sets.newHashSet();
-        if (action == Action.ASSOCIATE) {
-            ipAddresses.add(openstackFloatingIp.floatingIpAddress());
+        DeviceId localDevice = host.location().deviceId();
+        PortNumber localPort = host.location().port();
+        if (!nodeService.dataIp(localDevice).isPresent()) {
+            log.warn("Failed to populate L3 rules");
+            return;
         }
 
-        HostDescription hostDescription =
-                new DefaultHostDescription(host.mac(), host.vlan(), host.location(), ipAddresses,
-                        (DefaultAnnotations) host.annotations());
+        // TODO improve pipeline, do we have to install access rules between networks
+        // for every single VMs?
+        osNets.stream().filter(osNet -> !osNet.id().equals(osNetId)).forEach(osNet -> {
+            populateRoutingRulestoSameNode(
+                    host.ipAddresses().stream().findFirst().get().getIp4Address(),
+                    host.mac(),
+                    localPort, localDevice,
+                    Long.valueOf(osNet.segmentId()));
 
-        hostProviderService.hostDetected(host.id(), hostDescription, false);
+            nodeService.completeNodes().stream()
+                    .filter(node -> node.type().equals(COMPUTE))
+                    .filter(node -> !node.intBridge().equals(localDevice))
+                    .forEach(node -> populateRoutingRulestoDifferentNode(
+                            host.ipAddresses().stream().findFirst().get().getIp4Address(),
+                            Long.valueOf(osNet.segmentId()),
+                            node.intBridge(),
+                            nodeService.dataIp(localDevice).get().getIp4Address()));
+        });
     }
 
-    private class InternalHostListener implements HostListener {
-
-        private void hostDetected(Host host) {
-            String portId = host.annotations().value(Constants.PORT_ID);
-            OpenstackPort openstackPort = openstackService.port(portId);
-            if (openstackPort == null) {
-                log.warn("No OpenstackPort information found from OpenStack for port ID {}", portId);
-                return;
-            }
-
-            Optional<OpenstackPort> routerPort = getRouterInterfacePort(openstackPort.networkId());
-            if (routerPort.isPresent()) {
-                OpenstackRouterInterface routerInterface = portToRouterInterface(routerPort.get());
-                l3EventExecutorService.execute(() ->
-                        setL3Connection(getOpenstackRouter(routerInterface.id()), openstackPort));
-
-            }
+    private void removeRoutingRules(Host host, Set<OpenstackNetwork> osNets) {
+        String osNetId = host.annotations().value(NETWORK_ID);
+        if (osNetId == null) {
+            return;
         }
 
-        private void hostRemoved(Host host) {
-            String portId = host.annotations().value(Constants.PORT_ID);
-            OpenstackPort openstackPort = openstackService.port(portId);
-            if (openstackPort == null) {
-                log.warn("No OpenstackPort information found from OpenStack for port ID {}", portId);
-                return;
-            }
+        osNets.stream().filter(osNet -> !osNet.id().equals(osNetId)).forEach(osNet -> {
+            TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+            sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(host.ipAddresses().stream().findFirst().get().toIpPrefix())
+                    .matchTunnelId(Long.valueOf(osNet.segmentId()));
 
-            Optional<OpenstackPort> routerPort = getRouterInterfacePort(openstackPort.networkId());
-            if (routerPort.isPresent()) {
-                OpenstackRouterInterface routerInterface = portToRouterInterface(routerPort.get());
-                IpAddress ipAddress = host.ipAddresses().stream().findFirst().get();
-                l3EventExecutorService.execute(() -> rulePopulator.removeL3Rules(ipAddress.getIp4Address(),
-                        getL3ConnectionList(host.annotations().value(Constants.NETWORK_ID),
-                                getOpenstackRouterInterface(getOpenstackRouter(routerInterface.id())))));
-            }
-        }
-
-        private boolean isValidHost(Host host) {
-            return !host.ipAddresses().isEmpty() &&
-                    host.annotations().value(Constants.VXLAN_ID) != null &&
-                    host.annotations().value(Constants.NETWORK_ID) != null &&
-                    host.annotations().value(Constants.TENANT_ID) != null &&
-                    host.annotations().value(Constants.PORT_ID) != null;
-        }
-
-        @Override
-        public void event(HostEvent event) {
-            Host host = event.subject();
-            if (!mastershipService.isLocalMaster(host.location().deviceId())) {
-                // do not allow to proceed without mastership
-                return;
-            }
-
-            if (!isValidHost(host)) {
-                log.debug("Invalid host event, ignore it {}", host);
-                return;
-            }
-
-            switch (event.type()) {
-                case HOST_UPDATED:
-                case HOST_ADDED:
-                    l3EventExecutorService.execute(() -> hostDetected(host));
-                    break;
-                case HOST_REMOVED:
-                    l3EventExecutorService.execute(() -> hostRemoved(host));
-                    break;
-                default:
-                    break;
-            }
-        }
+            nodeService.completeNodes().stream()
+                    .filter(node -> node.type().equals(COMPUTE))
+                    .forEach(node -> RulePopulatorUtil.removeRule(
+                            flowObjectiveService,
+                            appId,
+                            node.intBridge(),
+                            sBuilder.build(),
+                            ForwardingObjective.Flag.SPECIFIC,
+                            ROUTING_RULE_PRIORITY));
+        });
+        log.debug("Removed routing rule from {} to {}", host, osNets);
     }
 
-    private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+    private void populateGatewayToController(long vni) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
-        private void nodeComplete() {
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(vni)
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+        tBuilder.setOutput(PortNumber.CONTROLLER);
 
-            rulePopulator = new OpenstackRoutingRulePopulator(appId, openstackService, flowObjectiveService,
-                    deviceService, driverService, nodeService, gatewayService);
-            openstackIcmpHandler = new OpenstackIcmpHandler(packetService, deviceService, hostService,
-                    openstackService, nodeService, gatewayService);
-            openstackArpHandler = new OpenstackRoutingArpHandler(packetService, openstackService, nodeService,
-                    gatewayService);
+        ForwardingObjective fo = DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withPriority(ROUTING_RULE_PRIORITY)
+                .fromApp(appId)
+                .add();
 
-            // Packet handlers must be started AFTER all initialization processes.
-            packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1));
+        gatewayService.getGatewayDeviceIds().stream()
+                .forEach(deviceId -> flowObjectiveService.forward(deviceId, fo));
+    }
 
-            openstackIcmpHandler.requestPacket(appId);
-            openstackArpHandler.requestPacket(appId);
+    private void populateCnodeToGateway(long vni) {
+        nodeService.completeNodes().stream()
+                .filter(node -> node.type().equals(COMPUTE))
+                .forEach(node -> populateRuleToGateway(
+                        node.intBridge(),
+                        gatewayService.getGatewayGroupId(node.intBridge()),
+                        vni));
+    }
 
-            openstackService.floatingIps().stream()
-                    .forEach(f -> floatingIpMap.put(f.id(), f));
+    private void populateRuleToGateway(DeviceId deviceId, GroupId groupId, long vni) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
-            reloadInitL3Rules();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(vni)
+                .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+        tBuilder.group(groupId);
+        ForwardingObjective fo = DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .withPriority(ROUTING_RULE_PRIORITY)
+                .fromApp(appId)
+                .add();
+
+        flowObjectiveService.forward(deviceId, fo);
+    }
+
+    private void populateRoutingRulestoDifferentNode(Ip4Address vmIp, long vni,
+                                                     DeviceId deviceId, Ip4Address hostIp) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(vni)
+                .matchIPDst(vmIp.toIpPrefix());
+        tBuilder.extension(buildExtension(deviceService, deviceId, hostIp), deviceId)
+                .setOutput(nodeService.tunnelPort(deviceId).get());
+
+        ForwardingObjective fo = DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withPriority(ROUTING_RULE_PRIORITY)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .fromApp(appId)
+                .add();
+
+        flowObjectiveService.forward(deviceId, fo);
+    }
+
+    private void populateRoutingRulestoSameNode(Ip4Address vmIp, MacAddress vmMac,
+                                                PortNumber port, DeviceId deviceId, long vni) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(vmIp.toIpPrefix())
+                .matchTunnelId(vni);
+
+        tBuilder.setEthDst(vmMac)
+                .setOutput(port);
+
+        ForwardingObjective fo = DefaultForwardingObjective.builder()
+                .withSelector(sBuilder.build())
+                .withTreatment(tBuilder.build())
+                .withPriority(ROUTING_RULE_PRIORITY)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .fromApp(appId)
+                .add();
+
+        flowObjectiveService.forward(deviceId, fo);
+    }
+
+    private void reloadRoutingRules() {
+        eventExecutor.execute(() -> openstackService.ports().stream()
+                .filter(osPort -> osPort.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
+                .forEach(osPort -> {
+                    OpenstackRouter osRouter = openstackRouter(osPort.deviceId());
+                    setRoutes(osRouter, Optional.empty());
+                    if (osRouter.gatewayExternalInfo().externalFixedIps().size() > 0) {
+                        setExternalConnection(osRouter, osPort.networkId());
+                    }
+                }));
+    }
+
+    @Override
+    protected void hostDetected(Host host) {
+        String osNetId = host.annotations().value(NETWORK_ID);
+        Optional<OpenstackPort> routerIface = routerIfacePort(osNetId);
+        if (!routerIface.isPresent()) {
+            return;
         }
+        eventExecutor.execute(() -> setRoutes(
+                openstackRouter(routerIface.get().deviceId()),
+                Optional.of(host)));
+    }
+
+    @Override
+    protected void hostRemoved(Host host) {
+        String osNetId = host.annotations().value(NETWORK_ID);
+        Optional<OpenstackPort> routerIface = routerIfacePort(osNetId);
+        if (!routerIface.isPresent()) {
+            return;
+        }
+        Set<OpenstackNetwork> routableNets = routableNetworks(routerIface.get().deviceId());
+        eventExecutor.execute(() -> removeRoutingRules(host, routableNets));
+    }
+
+    private class InternalNodeListener implements OpenstackNodeListener {
 
         @Override
         public void event(OpenstackNodeEvent event) {
@@ -730,28 +481,22 @@
             switch (event.type()) {
                 case COMPLETE:
                     log.info("COMPLETE node {} detected", node.hostname());
-                    l3EventExecutorService.execute(() -> nodeComplete());
+                    if (node.type() == GATEWAY) {
+                        GatewayNode gnode = GatewayNode.builder()
+                                .gatewayDeviceId(node.intBridge())
+                                .dataIpAddress(node.dataIp().getIp4Address())
+                                .uplinkIntf(node.externalPortName().get())
+                                .build();
+                        gatewayService.addGatewayNode(gnode);
+                    }
+                    eventExecutor.execute(OpenstackRoutingManager.this::reloadRoutingRules);
                     break;
+                case INIT:
+                case DEVICE_CREATED:
                 case INCOMPLETE:
-                    break;
                 default:
                     break;
             }
         }
     }
-
-    private class InternalHostProvider extends AbstractProvider implements HostProvider {
-
-        /**
-         * Creates a provider with the supplier identifier.
-         */
-        protected InternalHostProvider() {
-            super(PID);
-        }
-
-        @Override
-        public void triggerProbe(Host host) {
-            // nothing to do
-        }
-    }
 }
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingRulePopulator.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingRulePopulator.java
deleted file mode 100644
index 875cf98..0000000
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/OpenstackRoutingRulePopulator.java
+++ /dev/null
@@ -1,693 +0,0 @@
-/*
- * Copyright 2016-present 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.openstacknetworking.routing;
-
-import com.google.common.collect.Lists;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.IPv4;
-import org.onlab.packet.Ip4Address;
-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;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.core.GroupId;
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Host;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.DefaultDriverData;
-import org.onosproject.net.driver.DefaultDriverHandler;
-import org.onosproject.net.driver.Driver;
-import org.onosproject.net.driver.DriverHandler;
-import org.onosproject.net.driver.DriverService;
-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.flow.instructions.ExtensionPropertyException;
-import org.onosproject.net.flow.instructions.ExtensionTreatment;
-import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
-import org.onosproject.net.flowobjective.DefaultForwardingObjective;
-import org.onosproject.net.flowobjective.FlowObjectiveService;
-import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.packet.InboundPacket;
-import org.onosproject.openstackinterface.OpenstackInterfaceService;
-import org.onosproject.openstackinterface.OpenstackPort;
-import org.onosproject.openstackinterface.OpenstackRouterInterface;
-import org.onosproject.openstackinterface.OpenstackSubnet;
-import org.onosproject.openstackinterface.OpenstackFloatingIP;
-import org.onosproject.openstacknetworking.Constants;
-import org.onosproject.openstacknetworking.OpenstackRoutingService;
-import org.onosproject.scalablegateway.api.ScalableGatewayService;
-import org.onosproject.openstacknode.OpenstackNode;
-import org.onosproject.openstacknode.OpenstackNodeService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.StreamSupport;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onlab.osgi.DefaultServiceDirectory.getService;
-import static org.onosproject.net.AnnotationKeys.PORT_NAME;
-
-/**
- * Populates Routing Flow Rules.
- */
-public class OpenstackRoutingRulePopulator {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
-    private final ApplicationId appId;
-    private final FlowObjectiveService flowObjectiveService;
-    private final OpenstackInterfaceService openstackService;
-    private final DeviceService deviceService;
-    private final DriverService driverService;
-    private final ScalableGatewayService gatewayService;
-    private final OpenstackNodeService nodeService;
-
-    private static final String PORTNAME_PREFIX_TUNNEL = "vxlan";
-    private static final String PORTNAME_PREFIX_VM = "tap";
-
-    private static final String PORTNOTNULL = "Port can not be null";
-    private static final String DEVICENOTNULL = "Device can not be null";
-    private static final String TUNNEL_DESTINATION = "tunnelDst";
-    private static final int ROUTING_RULE_PRIORITY = 25000;
-    private static final int FLOATING_RULE_PRIORITY = 42000;
-    private static final int PNAT_RULE_PRIORITY = 26000;
-    private static final int PNAT_TIMEOUT = 120;
-    private static final int PREFIX_LENGTH = 32;
-
-    private InboundPacket inboundPacket;
-    private OpenstackPort openstackPort;
-    private int portNum;
-    private MacAddress externalInterface;
-    private MacAddress externalRouter;
-
-    /**
-     * The constructor of openstackRoutingRulePopulator.
-     *
-     * @param appId Caller`s appId
-     * @param openstackService Opestack REST request handler
-     * @param flowObjectiveService FlowObjectiveService
-     * @param deviceService DeviceService
-     * @param driverService DriverService
-     * @param nodeService openstack node service
-     * @param gatewayService scalable gateway service
-     */
-    public OpenstackRoutingRulePopulator(ApplicationId appId,
-                                         OpenstackInterfaceService openstackService,
-                                         FlowObjectiveService flowObjectiveService,
-                                         DeviceService deviceService,
-                                         DriverService driverService,
-                                         OpenstackNodeService nodeService,
-                                         ScalableGatewayService gatewayService) {
-        this.appId = appId;
-        this.flowObjectiveService = flowObjectiveService;
-        this.openstackService = checkNotNull(openstackService);
-        this.deviceService = deviceService;
-        this.driverService = driverService;
-        this.gatewayService = gatewayService;
-        this.nodeService = nodeService;
-    }
-
-    /**
-     * Populates flow rules for Pnat configurations.
-     *
-     * @param inboundPacket Packet-in event packet
-     * @param openstackPort Target VM information
-     * @param portNum Pnat port number
-     * @param externalIp external ip address
-     * @param externalInterfaceMacAddress Gateway external interface macaddress
-     * @param externalRouterMacAddress Outer(physical) router`s macaddress
-     */
-    public void populatePnatFlowRules(InboundPacket inboundPacket, OpenstackPort openstackPort, int portNum,
-                                      Ip4Address externalIp, MacAddress externalInterfaceMacAddress,
-                                      MacAddress externalRouterMacAddress) {
-        this.inboundPacket = inboundPacket;
-        this.openstackPort = openstackPort;
-        this.portNum = portNum;
-        this.externalInterface = externalInterfaceMacAddress;
-        this.externalRouter = externalRouterMacAddress;
-
-        long vni = getVni(openstackPort.networkId());
-
-        populatePnatIncomingFlowRules(vni, externalIp);
-        populatePnatOutgoingFlowRules(vni, externalIp);
-    }
-
-    private void populatePnatOutgoingFlowRules(long vni, Ip4Address externalIp) {
-        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
-
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(iPacket.getProtocol())
-                .matchTunnelId(vni)
-                .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
-                .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
-
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        switch (iPacket.getProtocol()) {
-            case IPv4.PROTOCOL_TCP:
-                TCP tcpPacket = (TCP) iPacket.getPayload();
-                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
-                        .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
-                tBuilder.setTcpSrc(TpPort.tpPort(portNum));
-                break;
-            case IPv4.PROTOCOL_UDP:
-                UDP udpPacket = (UDP) iPacket.getPayload();
-                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
-                        .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
-                tBuilder.setUdpSrc(TpPort.tpPort(portNum));
-                break;
-            default:
-                log.debug("Unsupported IPv4 protocol {}");
-                break;
-        }
-
-        tBuilder.setIpSrc(externalIp);
-        gatewayService.getGatewayNodes().forEach(node -> {
-            tBuilder.setOutput(gatewayService.getUplinkPort(node.getGatewayDeviceId()));
-            ForwardingObjective fo = DefaultForwardingObjective.builder()
-                    .withSelector(sBuilder.build())
-                    .withTreatment(tBuilder.build())
-                    .withFlag(ForwardingObjective.Flag.VERSATILE)
-                    .withPriority(PNAT_RULE_PRIORITY)
-                    .makeTemporary(PNAT_TIMEOUT)
-                    .fromApp(appId)
-                    .add();
-
-            flowObjectiveService.forward(node.getGatewayDeviceId(), fo);
-        });
-
-    }
-
-    private Port getPortOfExternalInterface() {
-        return deviceService.getPorts(getGatewayNode().id()).stream()
-                .filter(p -> p.annotations().value(PORT_NAME)
-                        .equals(org.onosproject.openstacknode.Constants.PATCH_INTG_BRIDGE))
-                .findAny().orElse(null);
-    }
-
-
-    private void populatePnatIncomingFlowRules(long vni, Ip4Address externalIp) {
-        IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
-
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(iPacket.getProtocol())
-                .matchIPDst(IpPrefix.valueOf(externalIp, 32))
-                .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
-
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-        tBuilder.setTunnelId(vni)
-                .setEthDst(inboundPacket.parsed().getSourceMAC())
-                .setIpDst(IpAddress.valueOf(iPacket.getSourceAddress()));
-
-        switch (iPacket.getProtocol()) {
-            case IPv4.PROTOCOL_TCP:
-                TCP tcpPacket = (TCP) iPacket.getPayload();
-                sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
-                        .matchTcpDst(TpPort.tpPort(portNum));
-                tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
-                break;
-            case IPv4.PROTOCOL_UDP:
-                UDP udpPacket = (UDP) iPacket.getPayload();
-                sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
-                        .matchUdpDst(TpPort.tpPort(portNum));
-                tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
-                break;
-            default:
-                break;
-        }
-
-        getGatewayNodeList().forEach(node -> {
-            DeviceId deviceId = node.id();
-            tBuilder.extension(buildNiciraExtenstion(deviceId,
-                    getHostIpfromOpenstackPort(openstackPort).getIp4Address()), deviceId)
-                    .setOutput(getTunnelPort(deviceId));
-
-            ForwardingObjective fo = DefaultForwardingObjective.builder()
-                    .withSelector(sBuilder.build())
-                    .withTreatment(tBuilder.build())
-                    .withFlag(ForwardingObjective.Flag.VERSATILE)
-                    .withPriority(PNAT_RULE_PRIORITY)
-                    .makeTemporary(PNAT_TIMEOUT)
-                    .fromApp(appId)
-                    .add();
-
-            flowObjectiveService.forward(deviceId, fo);
-        });
-    }
-
-    private List<Device> getGatewayNodeList() {
-        List<Device> devices = Lists.newArrayList();
-        gatewayService.getGatewayDeviceIds().forEach(deviceId ->
-                devices.add(checkNotNull(deviceService.getDevice(deviceId))));
-        return devices;
-    }
-
-    private IpAddress getHostIpfromOpenstackPort(OpenstackPort openstackPort) {
-        Device device = getDevicefromOpenstackPort(openstackPort);
-
-        Optional<IpAddress> ipAddress = nodeService.dataIp(device.id());
-        if (!ipAddress.isPresent()) {
-            log.warn("No IP address found for device {}", device.id());
-            return null;
-        }
-
-        return ipAddress.get();
-    }
-
-    private Device getDevicefromOpenstackPort(OpenstackPort openstackPort) {
-        String openstackPortName = PORTNAME_PREFIX_VM + openstackPort.id().substring(0, 11);
-        Device device = StreamSupport.stream(deviceService.getDevices().spliterator(), false)
-                .filter(d -> findPortinDevice(d.id(), openstackPortName))
-                .iterator()
-                .next();
-        checkNotNull(device, DEVICENOTNULL);
-        return device;
-    }
-
-    private boolean findPortinDevice(DeviceId deviceId, String openstackPortName) {
-        Port port = deviceService.getPorts(deviceId)
-                .stream()
-                .filter(p -> p.isEnabled() && p.annotations().value(PORT_NAME).equals(openstackPortName))
-                .findAny()
-                .orElse(null);
-        return port != null;
-    }
-
-    /**
-     * Builds Nicira extension for tagging remoteIp of vxlan.
-     *
-     * @param deviceId Device Id of vxlan source device
-     * @param hostIp Remote Ip of vxlan destination device
-     * @return NiciraExtension Treatment
-     */
-    public ExtensionTreatment buildNiciraExtenstion(DeviceId deviceId, Ip4Address hostIp) {
-        Driver driver = driverService.getDriver(deviceId);
-        DriverHandler driverHandler = new DefaultDriverHandler(new DefaultDriverData(driver, deviceId));
-        ExtensionTreatmentResolver resolver = driverHandler.behaviour(ExtensionTreatmentResolver.class);
-
-        ExtensionTreatment extensionInstruction =
-                resolver.getExtensionInstruction(
-                        ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type());
-
-        try {
-            extensionInstruction.setPropertyValue(TUNNEL_DESTINATION, hostIp);
-        } catch (ExtensionPropertyException e) {
-            log.error("Error setting Nicira extension setting {}", e);
-        }
-
-        return extensionInstruction;
-    }
-
-    /**
-     * Returns port number of vxlan tunnel.
-     *
-     * @param deviceId Target Device Id
-     * @return PortNumber
-     */
-    public PortNumber getTunnelPort(DeviceId deviceId) {
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> p.annotations().value(PORT_NAME).equals(PORTNAME_PREFIX_TUNNEL))
-                .findAny().orElse(null);
-
-        if (port == null) {
-            log.error("No TunnelPort was created.");
-            return null;
-        }
-        return port.number();
-
-    }
-
-    /**
-     * Populates flow rules from openstackComputeNode to GatewayNode.
-     *
-     * @param vni Target network
-     */
-    public void populateExternalRules(long vni) {
-
-        // 1. computeNode to gateway
-        populateComputeNodeRules(vni);
-        // 2. gatewayNode to controller
-        populateRuleGatewaytoController(vni);
-    }
-
-    private void populateRuleGatewaytoController(long vni) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(vni)
-                .matchEthDst(Constants.GATEWAY_MAC);
-        tBuilder.setOutput(PortNumber.CONTROLLER);
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .fromApp(appId)
-                .add();
-
-        getGatewayNodeList().forEach(device -> flowObjectiveService.forward(device.id(), fo));
-    }
-
-    private void populateComputeNodeRules(long vni) {
-        StreamSupport.stream(deviceService.getDevices().spliterator(), false)
-                .filter(d -> isTypeOf(d.id(), OpenstackNodeService.NodeType.COMPUTE))
-                .forEach(d -> populateRuleToGatewayBySgw(d.id(),
-                        gatewayService.getGatewayGroupId(d.id()), vni));
-    }
-
-    private void populateRuleToGatewayBySgw(DeviceId deviceId, GroupId groupId, long vni) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(vni)
-                .matchEthDst(Constants.GATEWAY_MAC);
-
-        tBuilder.group(groupId);
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(deviceId, fo);
-    }
-
-    private Device getGatewayNode() {
-
-        // TODO Return the correct gateway node
-        Optional<OpenstackNode> gwNode =  nodeService.nodes().stream()
-                .filter(n -> n.type().equals(OpenstackNodeService.NodeType.GATEWAY))
-                .findFirst();
-
-        if (!gwNode.isPresent()) {
-            log.warn("No Gateway is defined.");
-            return null;
-        }
-
-        return deviceService.getDevice(gwNode.get().intBridge());
-    }
-
-    private boolean isTypeOf(DeviceId deviceId, OpenstackNodeService.NodeType type) {
-
-        Optional<OpenstackNode> node = nodeService.nodes().stream()
-                .filter(n -> n.intBridge().equals(deviceId) ||
-                        (n.routerBridge().isPresent() && n.routerBridge().get().equals(deviceId)))
-                .filter(n -> n.type().equals(type))
-                .findFirst();
-
-        if (node.isPresent()) {
-            return true;
-        }
-
-        return false;
-    }
-
-    private long getVni(String netId) {
-        return Long.parseLong(openstackService.network(netId).segmentId());
-    }
-
-    /**
-     * Remove flow rules for external connection.
-     *
-     * @param routerInterface Corresponding routerInterface
-     */
-    public void removeExternalRules(OpenstackRouterInterface routerInterface) {
-        OpenstackSubnet openstackSubnet = openstackService.subnet(routerInterface.subnetId());
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(getVni(openstackSubnet.networkId()))
-                .matchEthDst(Constants.GATEWAY_MAC);
-
-        StreamSupport.stream(deviceService.getDevices().spliterator(), false)
-                .forEach(d -> {
-                    ForwardingObjective.Flag flag = isTypeOf(d.id(), OpenstackNodeService.NodeType.GATEWAY) ?
-                            ForwardingObjective.Flag.VERSATILE :
-                            ForwardingObjective.Flag.SPECIFIC;
-                    removeRule(d.id(), sBuilder, flag, ROUTING_RULE_PRIORITY);
-                });
-
-    }
-
-    private void removeRule(DeviceId deviceId, TrafficSelector.Builder sBuilder,
-                            ForwardingObjective.Flag flag, int priority) {
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(flag)
-                .withPriority(priority)
-                .fromApp(appId)
-                .remove();
-
-        flowObjectiveService.forward(deviceId, fo);
-    }
-
-    /**
-     * Populates flow rules for floatingIp configuration.
-     *
-     * @param floatingIP Corresponding floating ip information
-     */
-    public void populateFloatingIpRules(OpenstackFloatingIP floatingIP) {
-        OpenstackPort port = openstackService.port(floatingIP.portId());
-        //1. incoming rules
-        populateFloatingIpIncomingRules(floatingIP, port);
-        //2. outgoing rules
-        populateFloatingIpOutgoingRules(floatingIP, port);
-    }
-
-    private void populateFloatingIpIncomingRules(OpenstackFloatingIP floatingIP, OpenstackPort port) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(IpPrefix.valueOf(floatingIP.floatingIpAddress(), PREFIX_LENGTH));
-
-        DeviceId gatewayDeviceId = DeviceId.deviceId(port.deviceId());
-        Optional<IpAddress> ipAddress = nodeService.dataIp(gatewayDeviceId);
-        if (!ipAddress.isPresent()) {
-            log.warn("No IP address found for device {}", port.deviceId());
-            return;
-        }
-        tBuilder.setEthSrc(Constants.GATEWAY_MAC)
-                .setEthDst(port.macAddress())
-                .setIpDst(floatingIP.fixedIpAddress())
-                .setTunnelId(getVni(port.networkId()))
-                .extension(buildNiciraExtenstion(gatewayDeviceId,
-                        ipAddress.get().getIp4Address()), gatewayDeviceId)
-                .setOutput(getTunnelPort(gatewayDeviceId));
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(FLOATING_RULE_PRIORITY)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(getGatewayNode().id(), fo);
-    }
-
-    private void populateFloatingIpOutgoingRules(OpenstackFloatingIP floatingIP, OpenstackPort port) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(getVni(port.networkId()))
-                .matchIPSrc(IpPrefix.valueOf(floatingIP.fixedIpAddress(), 32));
-
-        getGatewayNodeList().forEach(device -> {
-            DeviceId deviceId = device.id();
-            tBuilder.setIpSrc(floatingIP.floatingIpAddress())
-                    .setEthSrc(Constants.GW_EXT_INT_MAC)
-                    .setEthDst(Constants.PHY_ROUTER_MAC)
-                    .setOutput(getExternalPortNum(deviceId));
-
-            ForwardingObjective fo = DefaultForwardingObjective.builder()
-                    .withSelector(sBuilder.build())
-                    .withTreatment(tBuilder.build())
-                    .withFlag(ForwardingObjective.Flag.VERSATILE)
-                    .withPriority(FLOATING_RULE_PRIORITY)
-                    .fromApp(appId)
-                    .add();
-
-            flowObjectiveService.forward(deviceId, fo);
-        });
-    }
-
-    private PortNumber getExternalPortNum(DeviceId deviceId) {
-        return checkNotNull(gatewayService.getUplinkPort(deviceId), PORTNOTNULL);
-    }
-
-    /**
-     * Removes flow rules for floating ip configuration.
-     *
-     * @param floatingIp Corresponding floating ip information
-     * @param host host information for vm to remove
-     */
-    public void removeFloatingIpRules(OpenstackFloatingIP floatingIp, Host host) {
-        TrafficSelector.Builder sOutgoingBuilder = DefaultTrafficSelector.builder();
-        TrafficSelector.Builder sIncomingBuilder = DefaultTrafficSelector.builder();
-
-        // XXX FloatingIp.tenant_id() == host.vxlan_id ???
-        sOutgoingBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(Integer.parseInt(host.annotations().value(Constants.VXLAN_ID)))
-                .matchIPSrc(IpPrefix.valueOf(floatingIp.fixedIpAddress(), PREFIX_LENGTH));
-
-        sIncomingBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(IpPrefix.valueOf(floatingIp.floatingIpAddress(), PREFIX_LENGTH));
-
-        getGatewayNodeList().forEach(device -> {
-            removeRule(device.id(), sOutgoingBuilder, ForwardingObjective.Flag.VERSATILE, FLOATING_RULE_PRIORITY);
-            removeRule(device.id(), sIncomingBuilder, ForwardingObjective.Flag.VERSATILE, FLOATING_RULE_PRIORITY);
-        });
-    }
-
-    /**
-     * Populates L3 rules for east to west traffic.
-     *
-     * @param openstackPort target VM
-     * @param targetList target openstackRouterInterfaces
-     */
-    public void populateL3Rules(OpenstackPort openstackPort, List<OpenstackRouterInterface> targetList) {
-        Device device = getDevicefromOpenstackPort(openstackPort);
-        Port port = getPortFromOpenstackPort(device, openstackPort);
-        Ip4Address vmIp = openstackPort.fixedIps().values().iterator().next();
-
-        if (port == null) {
-            return;
-        }
-
-        targetList.forEach(routerInterface -> {
-            long vni = getVni(openstackService.port(routerInterface.portId()).networkId());
-
-            if (vmIp == null) {
-                return;
-            }
-
-            populateL3RulestoSameNode(vmIp, openstackPort, port, device, vni);
-
-            deviceService.getAvailableDevices().forEach(d -> {
-                if (!d.equals(device) && !d.equals(getGatewayNode())) {
-                    populateL3RulestoDifferentNode(vmIp, vni, d.id(),
-                            getHostIpfromOpenstackPort(openstackPort).getIp4Address());
-                }
-            });
-        });
-    }
-
-    private void populateL3RulestoDifferentNode(Ip4Address vmIp, long vni, DeviceId deviceId, Ip4Address hostIp) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchTunnelId(vni)
-                .matchIPDst(vmIp.toIpPrefix());
-        tBuilder.extension(buildNiciraExtenstion(deviceId, hostIp), deviceId)
-                .setOutput(getTunnelPort(deviceId));
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(deviceId, fo);
-    }
-
-    private void populateL3RulestoSameNode(Ip4Address vmIp, OpenstackPort p, Port port, Device device, long vni) {
-        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-
-        sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(vmIp.toIpPrefix())
-                .matchTunnelId(vni);
-
-        tBuilder.setEthDst(p.macAddress())
-                .setOutput(port.number());
-
-        ForwardingObjective fo = DefaultForwardingObjective.builder()
-                .withSelector(sBuilder.build())
-                .withTreatment(tBuilder.build())
-                .withPriority(ROUTING_RULE_PRIORITY)
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .fromApp(appId)
-                .add();
-
-        flowObjectiveService.forward(device.id(), fo);
-    }
-
-    private Port getPortFromOpenstackPort(Device device, OpenstackPort p) {
-        String openstackPortName = PORTNAME_PREFIX_VM + p.id().substring(0, 11);
-        return  deviceService.getPorts(device.id())
-                .stream()
-                .filter(pt -> pt.annotations().value(PORT_NAME).equals(openstackPortName))
-                .findAny()
-                .orElse(null);
-    }
-
-    /**
-     * Removes L3 rules for routerInterface events.
-     *
-     * @param vmIp Corresponding Vm ip
-     * @param routerInterfaces Corresponding routerInterfaces
-     */
-    public void removeL3Rules(Ip4Address vmIp, List<OpenstackRouterInterface> routerInterfaces) {
-        if (vmIp == null) {
-            return;
-        }
-
-        OpenstackRoutingService routingService = getService(OpenstackRoutingService.class);
-
-        deviceService.getAvailableDevices().forEach(d -> {
-            if (isTypeOf(d.id(), OpenstackNodeService.NodeType.COMPUTE)) {
-                routerInterfaces.forEach(routerInterface -> {
-                    String networkId = routingService.networkIdForRouterInterface(routerInterface.portId());
-                    long vni = getVni(networkId);
-
-                    TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
-
-                    sBuilder.matchEthType(Ethernet.TYPE_IPV4)
-                            .matchIPDst(vmIp.toIpPrefix())
-                            .matchTunnelId(vni);
-
-                    removeRule(d.id(), sBuilder, ForwardingObjective.Flag.SPECIFIC, ROUTING_RULE_PRIORITY);
-                });
-            }
-        });
-    }
-}
diff --git a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/package-info.java b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/package-info.java
index 78d05a9..67fd1a5 100644
--- a/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/package-info.java
+++ b/apps/openstacknetworking/routing/src/main/java/org/onosproject/openstacknetworking/routing/package-info.java
@@ -15,6 +15,8 @@
  */
 
 /**
- * Application for OpenstackRouting.
+ * Implements OpenStack L3 service plugin, which routes packets between subnets,
+ * forwards packets from internal networks to external ones, and accesses instances
+ * from external networks through floating IPs.
  */
-package org.onosproject.openstacknetworking.routing;
\ No newline at end of file
+package org.onosproject.openstacknetworking.routing;