Initial implementation of SNAT functionality.

Change-Id: I9094755c6d25a62e527976b9bf275d7c1e2a3f86
(cherry picked from commit b9a220261df1f591b75d59b646aa834c1efdb7f1)
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubevirtListRouterCommand.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubevirtListRouterCommand.java
index 772b841..5c59e0e 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubevirtListRouterCommand.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubevirtListRouterCommand.java
@@ -53,12 +53,12 @@
         routers.sort(Comparator.comparing(KubevirtRouter::name));
 
         String format = genFormatString(ImmutableList.of(CLI_NAME_LENGTH,
-                CLI_FLAG_LENGTH, CLI_IP_ADDRESSES_LENGTH, CLI_IP_ADDRESS_LENGTH));
+                CLI_FLAG_LENGTH, CLI_IP_ADDRESSES_LENGTH, CLI_IP_ADDRESS_LENGTH, CLI_NAME_LENGTH));
 
         if (outputJson()) {
             print("%s", json(routers));
         } else {
-            print(format, "Name", "SNAT", "Internal", "External");
+            print(format, "Name", "SNAT", "Internal", "External", "GatewayNode");
 
             for (KubevirtRouter router : routers) {
                 Set<String> internalCidrs = router.internal();
@@ -66,6 +66,7 @@
 
                 String internal = internalCidrs.size() == 0 ? "[]" : internalCidrs.toString();
                 String external = externalIps.size() == 0 ? "[]" : externalIps.toString();
+                String gwNode = router.electedGateway() == null ? "N/A" : router.electedGateway();
 
                 print(format, StringUtils.substring(router.name(), 0,
                         CLI_NAME_LENGTH - CLI_MARGIN_LENGTH),
@@ -74,7 +75,9 @@
                         StringUtils.substring(internal, 0,
                                 CLI_IP_ADDRESSES_LENGTH - CLI_MARGIN_LENGTH),
                         StringUtils.substring(external, 0,
-                                CLI_IP_ADDRESS_LENGTH - CLI_MARGIN_LENGTH)
+                                CLI_IP_ADDRESS_LENGTH - CLI_MARGIN_LENGTH),
+                        StringUtils.substring(gwNode, 0,
+                                CLI_NAME_LENGTH - CLI_MARGIN_LENGTH)
                 );
             }
         }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodec.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodec.java
index d8529f6..d1239de 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodec.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodec.java
@@ -52,6 +52,7 @@
     private static final String IP_ADDRESS = "ip";
     private static final String MAC_ADDRESS = "mac";
     private static final String NETWORK = "network";
+    private static final String GATEWAY = "gateway";
 
     private static final String MISSING_MESSAGE = " is required in KubevirtRouter";
 
@@ -96,6 +97,10 @@
             result.set(PEER_ROUTER, peerRouter);
         }
 
+        if (router.electedGateway() != null) {
+            result.put(GATEWAY, router.electedGateway());
+        }
+
         return result;
     }
 
@@ -120,6 +125,10 @@
         if (enableSnatJson != null) {
             builder.enableSnat(enableSnatJson.asBoolean());
         }
+        JsonNode electedGwJson = json.get(GATEWAY);
+        if (electedGwJson != null) {
+            builder.electedGateway(electedGwJson.asText());
+        }
 
         ArrayNode internalJson = (ArrayNode) json.get(INTERNAL);
         Set<String> internal = new HashSet<>();
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtRouterStore.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtRouterStore.java
index c11fb2d..93d7cc3 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtRouterStore.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtRouterStore.java
@@ -45,6 +45,7 @@
 
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
@@ -56,6 +57,9 @@
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_FLOATING_IP_DISASSOCIATED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_FLOATING_IP_REMOVED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_FLOATING_IP_UPDATED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_GATEWAY_NODE_ATTACHED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_GATEWAY_NODE_CHANGED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_GATEWAY_NODE_DETACHED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_ROUTER_CREATED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_ROUTER_EXTERNAL_NETWORK_ATTACHED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent.Type.KUBEVIRT_ROUTER_EXTERNAL_NETWORK_DETACHED;
@@ -297,6 +301,27 @@
                         KUBEVIRT_ROUTER_INTERNAL_NETWORKS_DETACHED,
                         router, removed));
             }
+            if (oldValue.electedGateway() == null
+                    && newValue.electedGateway() != null) {
+                notifyDelegate(new KubevirtRouterEvent(
+                        KUBEVIRT_GATEWAY_NODE_ATTACHED,
+                        router, newValue.electedGateway()));
+            }
+
+            if (oldValue.electedGateway() != null
+                    && newValue.electedGateway() == null) {
+                notifyDelegate(new KubevirtRouterEvent(
+                        KUBEVIRT_GATEWAY_NODE_DETACHED,
+                        router, oldValue.electedGateway()));
+            }
+
+            if (oldValue.electedGateway() != null
+                    && newValue.electedGateway() != null
+                    && !Objects.equals(oldValue.electedGateway(), newValue.electedGateway())) {
+                notifyDelegate(new KubevirtRouterEvent(
+                        KUBEVIRT_GATEWAY_NODE_CHANGED,
+                        router, oldValue.electedGateway()));
+            }
         }
     }
 
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFlowRuleManager.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFlowRuleManager.java
index ffb73e2..585c925 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFlowRuleManager.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFlowRuleManager.java
@@ -139,7 +139,7 @@
         localNodeId = clusterService.getLocalNode().id();
         leadershipService.runForLeadership(appId.name());
         nodeService.completeNodes(WORKER)
-                .forEach(node -> initializePipeline(node.intgBridge()));
+                .forEach(node -> initializeWorkerNodePipeline(node.intgBridge()));
 
         log.info("Started");
     }
@@ -245,7 +245,30 @@
         }));
     }
 
-    protected void initializePipeline(DeviceId deviceId) {
+    protected void initializeGatewayNodePipeline(DeviceId deviceId) {
+        // for inbound table transition
+        connectTables(deviceId, STAT_INBOUND_TABLE, VTAG_TABLE);
+
+        if (getProviderNetworkOnlyFlag()) {
+            // we directly transit from vTag table to PRE_FLAT table for provider
+            // network only mode, because there is no need to differentiate ARP
+            // and IP packets on this mode
+            connectTables(deviceId, VTAG_TABLE, PRE_FLAT_TABLE);
+        } else {
+            // for vTag and ARP table transition
+            connectTables(deviceId, VTAG_TABLE, ARP_TABLE);
+        }
+
+        // for PRE_FLAT and FLAT table transition
+        connectTables(deviceId, PRE_FLAT_TABLE, FLAT_TABLE);
+
+        // for setting up default FLAT table behavior which is drop
+        setupGatewayNodeFlatTable(deviceId);
+
+        // for setting up default Forwarding table behavior which is NORMAL
+        setupForwardingTable(deviceId);
+    }
+    protected void initializeWorkerNodePipeline(DeviceId deviceId) {
         // for inbound table transition
         connectTables(deviceId, STAT_INBOUND_TABLE, VTAP_INBOUND_TABLE);
         connectTables(deviceId, VTAP_INBOUND_TABLE, DHCP_TABLE);
@@ -266,6 +289,9 @@
         // for PRE_FLAT and FLAT table transition
         connectTables(deviceId, PRE_FLAT_TABLE, FLAT_TABLE);
 
+        // for FLAT table and ACL table transition
+        connectTables(deviceId, FLAT_TABLE, ACL_EGRESS_TABLE);
+
         // for ARP and ACL table transition
         connectTables(deviceId, ARP_TABLE, ACL_INGRESS_TABLE);
 
@@ -282,8 +308,8 @@
         // table lookup
         setupJumpTable(deviceId);
 
-        // for setting up default FLAT table behavior which is NORMAL
-        setupFlatTable(deviceId);
+        // for setting up default Forwarding table behavior which is NORMAL
+        setupForwardingTable(deviceId);
     }
 
     private void setupJumpTable(DeviceId deviceId) {
@@ -323,7 +349,7 @@
         applyRule(flowRule, true);
     }
 
-    private void setupFlatTable(DeviceId deviceId) {
+    private void setupForwardingTable(DeviceId deviceId) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
                 .setOutput(PortNumber.NORMAL);
@@ -335,10 +361,29 @@
                 .withPriority(LOW_PRIORITY)
                 .fromApp(appId)
                 .makePermanent()
+                .forTable(FORWARDING_TABLE)
+                .build();
+
+        applyRule(flowRule, true);
+    }
+
+    private void setupGatewayNodeFlatTable(DeviceId deviceId) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .drop();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(appId)
+                .makePermanent()
                 .forTable(FLAT_TABLE)
                 .build();
 
         applyRule(flowRule, true);
+
     }
 
     private boolean getProviderNetworkOnlyFlag() {
@@ -372,7 +417,11 @@
                             return;
                         }
 
-                        initializePipeline(node.intgBridge());
+                        if (event.subject().type().equals(WORKER)) {
+                            initializeWorkerNodePipeline(node.intgBridge());
+                        } else {
+                            initializeGatewayNodePipeline(node.intgBridge());
+                        }
                     });
                     break;
                 case KUBEVIRT_NODE_CREATED:
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java
index 7f539de..b734226 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkHandler.java
@@ -23,6 +23,7 @@
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.UDP;
 import org.onosproject.cluster.ClusterService;
@@ -35,6 +36,10 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkEvent;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkListener;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterAdminService;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterListener;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
 import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
@@ -66,6 +71,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 import static java.lang.Thread.sleep;
@@ -74,7 +80,6 @@
 import static org.onlab.packet.ICMP.TYPE_ECHO_REPLY;
 import static org.onlab.packet.ICMP.TYPE_ECHO_REQUEST;
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.kubevirtnetworking.api.Constants.DEFAULT_GATEWAY_MAC;
 import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRE_FLAT_TABLE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
@@ -88,6 +93,8 @@
 import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_INBOUND_TABLE;
 import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_TO_TUNNEL_PREFIX;
 import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedRouter;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getbrIntMacAddress;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.segmentIdHex;
 import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.NXM_NX_IP_TTL;
 import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.NXM_OF_ICMP_TYPE;
@@ -140,9 +147,15 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected DriverService driverService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtRouterAdminService kubevirtRouterService;
+
     private final KubevirtNetworkListener networkListener = new InternalNetworkEventListener();
     private final KubevirtNodeListener nodeListener = new InternalNodeEventListener();
 
+    private final InternalRouterEventListener kubevirtRouterlistener =
+            new InternalRouterEventListener();
+
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler"));
 
@@ -157,6 +170,8 @@
         nodeService.addListener(nodeListener);
         leadershipService.runForLeadership(appId.name());
 
+        kubevirtRouterService.addListener(kubevirtRouterlistener);
+
         log.info("Started");
     }
 
@@ -165,6 +180,8 @@
         networkService.removeListener(networkListener);
         nodeService.removeListener(nodeListener);
         leadershipService.withdraw(appId.name());
+
+        kubevirtRouterService.removeListener(kubevirtRouterlistener);
         eventExecutor.shutdown();
 
         log.info("Stopped");
@@ -281,9 +298,9 @@
 
         setDhcpRule(deviceId, true);
         setForwardingRule(deviceId, true);
-        setGatewayArpRule(network, TENANT_ARP_TABLE,
+        setGatewayArpRuleForInternalNetwork(network, TENANT_ARP_TABLE,
                 network.tenantDeviceId(node.hostname()), true);
-        setGatewayIcmpRule(network, TENANT_ICMP_TABLE,
+        setGatewayIcmpRuleForInternalNetwork(network, TENANT_ICMP_TABLE,
                 network.tenantDeviceId(node.hostname()), true);
 
         log.info("Install default flow rules for tenant bridge {}", network.tenantBridgeName());
@@ -327,10 +344,36 @@
                 install);
     }
 
-    private void setGatewayArpRule(KubevirtNetwork network,
-                                   int tableNum, DeviceId deviceId, boolean install) {
+    private void initGatewayNodeForInternalNetwork(KubevirtNetwork network,
+                                                   KubevirtRouter router,
+                                                   KubevirtNode electedGateway,
+                                                   boolean install) {
+        setGatewayArpRuleForInternalNetwork(network, PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
+        setGatewayIcmpRuleForInternalNetwork(network, PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
+    }
+
+
+    private void setGatewayInterNetworkRouting(KubevirtNetwork network, KubevirtRouter router, boolean install) {
+        router.internal().forEach(srcNetwork -> {
+            if (srcNetwork.equals(network.networkId())) {
+                return;
+            }
+
+        });
+    }
+
+    private void setGatewayArpRuleForInternalNetwork(KubevirtNetwork network,
+                                                     int tableNum, DeviceId deviceId, boolean install) {
         Device device = deviceService.getDevice(deviceId);
 
+        MacAddress brIntMacAddress = getbrIntMacAddress(deviceService, deviceId);
+
+        if (brIntMacAddress == null) {
+            log.error("Setting gateway arp rule for internal network because " +
+                    "there's no br-int port for device {}", deviceId);
+            return;
+        }
+
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         sBuilder.matchEthType(EthType.EtherType.ARP.ethType().toShort())
                 .matchArpOp(ARP.OP_REQUEST)
@@ -341,9 +384,9 @@
                 .extension(buildMoveArpShaToThaExtension(device), device.id())
                 .extension(buildMoveArpSpaToTpaExtension(device), device.id())
                 .setArpOp(ARP.OP_REPLY)
-                .setArpSha(DEFAULT_GATEWAY_MAC)
+                .setArpSha(brIntMacAddress)
                 .setArpSpa(Ip4Address.valueOf(network.gatewayIp().toString()))
-                .setEthSrc(DEFAULT_GATEWAY_MAC)
+                .setEthSrc(brIntMacAddress)
                 .setOutput(PortNumber.IN_PORT);
 
         flowService.setRule(
@@ -357,9 +400,24 @@
         );
     }
 
-    private void
-    setGatewayIcmpRule(KubevirtNetwork network,
-                       int tableNum, DeviceId deviceId, boolean install) {
+    /**
+     * Sends ICMP echo reply for the ICMP echo request from the kubevirt VM.
+     *
+     * @param network kubevirt network
+     * @param tableNum flow table number
+     * @param deviceId device id of the selected gateway for the network
+     * @param install install if true, remove otherwise
+     */
+    private void setGatewayIcmpRuleForInternalNetwork(KubevirtNetwork network,
+                                              int tableNum, DeviceId deviceId, boolean install) {
+        MacAddress brIntMacAddress = getbrIntMacAddress(deviceService, deviceId);
+
+        if (brIntMacAddress == null) {
+            log.error("Setting gateway ICMP rule for internal network because " +
+                    "there's no br-int port for device {}", deviceId);
+            return;
+        }
+
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
                 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
@@ -376,7 +434,7 @@
                 .extension(buildLoadExtension(device,
                         NXM_OF_ICMP_TYPE, TYPE_ECHO_REPLY), device.id())
                 .setIpSrc(network.gatewayIp())
-                .setEthSrc(DEFAULT_GATEWAY_MAC)
+                .setEthSrc(brIntMacAddress)
                 .setOutput(PortNumber.IN_PORT);
 
         flowService.setRule(
@@ -389,31 +447,221 @@
                 install);
     }
 
-
-    private void initGatewayNodeBridge(KubevirtNetwork network, boolean install) {
-        KubevirtNode electedGateway = gatewayNodeForSpecifiedNetwork(network);
-        if (electedGateway == null) {
-            log.warn("There's no elected gateway for the network {}", network.name());
-            return;
+    private class InternalRouterEventListener implements KubevirtRouterListener {
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
         }
 
-        setGatewayArpRule(network, PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
-        setGatewayIcmpRule(network, PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
-    }
+        @Override
+        public void event(KubevirtRouterEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_ROUTER_CREATED:
+                    eventExecutor.execute(() -> processRouterCreation(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_REMOVED:
+                    eventExecutor.execute(() -> processRouterDeletion(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_UPDATED:
+                    eventExecutor.execute(() -> processRouterUpdate(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_INTERNAL_NETWORKS_ATTACHED:
+                    eventExecutor.execute(() -> processRouterInternalNetworksAttached(event.subject(),
+                            event.internal()));
+                    break;
+                case KUBEVIRT_ROUTER_INTERNAL_NETWORKS_DETACHED:
+                    eventExecutor.execute(() -> processRouterInternalNetworksDetached(event.subject(),
+                            event.internal()));
+                    break;
+                case KUBEVIRT_GATEWAY_NODE_ATTACHED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeAttached(event.subject(),
+                            event.gateway()));
+                    break;
+                case KUBEVIRT_GATEWAY_NODE_DETACHED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeDetached(event.subject(),
+                            event.gateway()));
+                    break;
+                case KUBEVIRT_GATEWAY_NODE_CHANGED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeChanged(event.subject(),
+                            event.gateway()));
+                    break;
 
-    /**
-     * Returns the gateway node for the specified network.
-     * Among gateways, only one gateway would act as a gateway per network.
-     *
-     * @param network kubevirt network
-     * @return gateway node which would act as the gateway for the network
-     */
-    private KubevirtNode gatewayNodeForSpecifiedNetwork(KubevirtNetwork network) {
-        //TODO: would implement election logic for each network.
-        //TODO: would implement cleanup logic in case a gateway node is added
-        // and the election is changed
-        return nodeService.completeNodes(GATEWAY).stream()
-                .findFirst().orElse(null);
+                default:
+                    //do nothing
+                    break;
+            }
+        }
+
+        private void processRouterCreation(KubevirtRouter router) {
+            // When a router is created, we performs the election process to associate the router
+            // to the specific gateway. After the election, KubevirtNetwork handler installs bunch of rules
+            // to elected gateway node so that VMs associated to the router can ping to their gateway IP.
+            // SNAT and floating ip rule setup is out of this handler's scope and would be done with the other handlers
+            if (!isRelevantHelper()) {
+                return;
+            }
+            KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(nodeService, router);
+            if (electedGw == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, electedGw, true);
+                }
+            });
+            kubevirtRouterService.updateRouter(router.updatedElectedGateway(electedGw.hostname()));
+        }
+
+        private void processRouterDeletion(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(nodeService, router);
+            if (electedGw == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, electedGw, false);
+                }
+            });
+        }
+
+        private void processRouterUpdate(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (router.electedGateway() == null) {
+                return;
+            }
+
+            KubevirtNode electedGw = nodeService.node(router.electedGateway());
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, electedGw, true);
+                }
+            });
+        }
+
+        private void processRouterInternalNetworksAttached(KubevirtRouter router,
+                                                           Set<String> attachedInternalNetworks) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(nodeService, router);
+            if (electedGw == null) {
+                return;
+            }
+
+            attachedInternalNetworks.forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, electedGw, true);
+                }
+            });
+        }
+
+        private void processRouterInternalNetworksDetached(KubevirtRouter router,
+                                                           Set<String> detachedInternalNetworks) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(nodeService, router);
+            if (electedGw == null) {
+                return;
+            }
+
+            detachedInternalNetworks.forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, electedGw, false);
+                }
+            });
+
+        }
+
+        private void processRouterGatewayNodeAttached(KubevirtRouter router,
+                                                      String associatedGateway) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtNode gatewayNode = nodeService.node(associatedGateway);
+            if (gatewayNode == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, gatewayNode, true);
+                }
+            });
+        }
+
+        private void processRouterGatewayNodeDetached(KubevirtRouter router,
+                                                      String disAssociatedGateway) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtNode gatewayNode = nodeService.node(disAssociatedGateway);
+            if (gatewayNode == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, gatewayNode, false);
+                }
+            });
+        }
+
+        private void processRouterGatewayNodeChanged(KubevirtRouter router,
+                                                     String disAssociatedGateway) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtNode oldGatewayNode = nodeService.node(disAssociatedGateway);
+            if (oldGatewayNode == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, oldGatewayNode, false);
+                }
+            });
+
+            KubevirtNode newGatewayNode = nodeService.node(router.electedGateway());
+            if (newGatewayNode == null) {
+                return;
+            }
+
+            router.internal().forEach(networkName -> {
+                KubevirtNetwork network = networkService.network(networkName);
+
+                if (network != null) {
+                    initGatewayNodeForInternalNetwork(network, router, oldGatewayNode, true);
+                }
+            });
+        }
     }
 
     private class InternalNetworkEventListener implements KubevirtNetworkListener {
@@ -451,7 +699,6 @@
                     break;
                 case FLAT:
                 case VLAN:
-                    initGatewayNodeBridge(network, true);
                     break;
                 default:
                     // do nothing
@@ -472,7 +719,6 @@
                     break;
                 case FLAT:
                 case VLAN:
-                    initGatewayNodeBridge(network, false);
                     break;
                 default:
                     // do nothing
@@ -516,6 +762,9 @@
                 case KUBEVIRT_NODE_COMPLETE:
                     eventExecutor.execute(() -> processNodeCompletion(event.subject()));
                     break;
+                case KUBEVIRT_NODE_REMOVED:
+                    eventExecutor.execute(() -> processNodeDeletion(event.subject()));
+                    break;
                 case KUBEVIRT_NODE_INCOMPLETE:
                 case KUBEVIRT_NODE_UPDATED:
                 default:
@@ -550,11 +799,11 @@
                     }
                 }
             } else if (node.type().equals(GATEWAY)) {
+                updateGatewayNodeForRouter();
                 for (KubevirtNetwork network : networkService.networks()) {
                     switch (network.type()) {
                         case FLAT:
                         case VLAN:
-                            initGatewayNodeBridge(network, true);
                             break;
                         case VXLAN:
                         case GRE:
@@ -566,5 +815,39 @@
                 }
             }
         }
+
+        private void processNodeDeletion(KubevirtNode node) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            if (node.type().equals(GATEWAY)) {
+                updateGatewayNodeForRouter();
+                for (KubevirtNetwork network : networkService.networks()) {
+                    switch (network.type()) {
+                        case FLAT:
+                        case VLAN:
+                            break;
+                        case VXLAN:
+                        case GRE:
+                        case GENEVE:
+                        default:
+                            // do nothing
+                            break;
+                    }
+                }
+            }
+        }
+
+        private void updateGatewayNodeForRouter() {
+            kubevirtRouterService.routers().forEach(router -> {
+                KubevirtNode newGwNode = gatewayNodeForSpecifiedRouter(nodeService, router);
+
+                if (newGwNode == null) {
+                    return;
+                }
+                kubevirtRouterService.updateRouter(router.updatedElectedGateway(newGwNode.hostname()));
+            });
+        }
     }
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRouterWatcher.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRouterWatcher.java
index 501099f..c346c58 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRouterWatcher.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRouterWatcher.java
@@ -30,6 +30,7 @@
 import org.onosproject.kubevirtnetworking.api.AbstractWatcher;
 import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
 import org.onosproject.kubevirtnetworking.api.KubevirtRouterAdminService;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigEvent;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigListener;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
@@ -78,6 +79,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected KubevirtApiConfigService configService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtRouterService routerService;
+
     private final ExecutorService eventExecutor = newSingleThreadExecutor(
             groupedThreads(this.getClass().getSimpleName(), "event-handler"));
 
@@ -133,7 +137,14 @@
             ObjectMapper mapper = new ObjectMapper();
             JsonNode json = mapper.readTree(resource);
             ObjectNode spec = (ObjectNode) json.get("spec");
-            return codec(KubevirtRouter.class).decode(spec, this);
+            KubevirtRouter router = codec(KubevirtRouter.class).decode(spec, this);
+            KubevirtRouter existing = routerService.router(router.name());
+
+            if (existing == null) {
+                return router;
+            } else {
+                return router.updatedElectedGateway(existing.electedGateway());
+            }
         } catch (IOException e) {
             log.error("Failed to parse kubevirt router object");
         }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java
new file mode 100644
index 0000000..808a323
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingSnatHandler.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * 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.kubevirtnetworking.impl;
+
+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.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.kubevirtnetworking.api.KubevirtFlowRuleService;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkListener;
+import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
+import org.onosproject.kubevirtnetworking.api.KubevirtPort;
+import org.onosproject.kubevirtnetworking.api.KubevirtPortEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtPortListener;
+import org.onosproject.kubevirtnetworking.api.KubevirtPortService;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterListener;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
+import org.onosproject.kubevirtnetworking.util.RulePopulatorUtil;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceAdminService;
+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.ExtensionTreatment;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.kubevirtnetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.kubevirtnetworking.api.Constants.FLAT_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRE_FLAT_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedRouter;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterSnatIpAddress;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getbrIntMacAddress;
+import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.CT_NAT_SRC_FLAG;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles kubevirt routing snat.
+ */
+
+@Component(immediate = true)
+public class KubevirtRoutingSnatHandler {
+    protected final Logger log = getLogger(getClass());
+    private static final int DEFAULT_TTL = 0xff;
+
+    private static final int TP_PORT_MINIMUM_NUM = 1025;
+    private static final int TP_PORT_MAXIMUM_NUM = 65535;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceAdminService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtPortService kubevirtPortService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeService kubevirtNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNetworkService kubevirtNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService flowService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtRouterService kubevirtRouterService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private final InternalKubevirtPortListener kubevirtPortListener =
+            new InternalKubevirtPortListener();
+
+    private final InternalRouterEventListener kubevirtRouterlistener =
+            new InternalRouterEventListener();
+
+    private final InternalNetworkEventListener kubevirtNetworkEventListener =
+            new InternalNetworkEventListener();
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(KUBEVIRT_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+
+        kubevirtPortService.addListener(kubevirtPortListener);
+        kubevirtRouterService.addListener(kubevirtRouterlistener);
+        kubevirtNetworkService.addListener(kubevirtNetworkEventListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        leadershipService.withdraw(appId.name());
+        kubevirtPortService.removeListener(kubevirtPortListener);
+        kubevirtRouterService.removeListener(kubevirtRouterlistener);
+        kubevirtNetworkService.removeListener(kubevirtNetworkEventListener);
+
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private void initGatewayNodeSnatForRouter(KubevirtRouter router, boolean install) {
+        KubevirtNode electedGw = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+
+        if (electedGw == null) {
+            log.warn("Fail to initialize gateway node snat for router {} " +
+                    "there's no gateway assigned to it", router.name());
+            return;
+        }
+
+        String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+
+        if (routerSnatIp == null) {
+            log.warn("Fail to initialize gateway node snat for router {} " +
+                    "there's no gateway snat ip assigned to it", router.name());
+            return;
+        }
+
+        setArpResponseToPeerRouter(electedGw, Ip4Address.valueOf(routerSnatIp), install);
+        setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(routerSnatIp), install);
+        setStatefulSnatDownstreamRuleForRouter(router, electedGw, Ip4Address.valueOf(routerSnatIp), install);
+    }
+
+    private void setArpResponseToPeerRouter(KubevirtNode gatewayNode, Ip4Address ip4Address, boolean install) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(externalPatchPortNum(gatewayNode))
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .matchArpOp(ARP.OP_REQUEST)
+                .matchArpTpa(ip4Address)
+                .build();
+
+        Device device = deviceService.getDevice(gatewayNode.intgBridge());
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .extension(RulePopulatorUtil.buildMoveEthSrcToDstExtension(device), device.id())
+                .extension(RulePopulatorUtil.buildMoveArpShaToThaExtension(device), device.id())
+                .extension(RulePopulatorUtil.buildMoveArpSpaToTpaExtension(device), device.id())
+                .setArpOp(ARP.OP_REPLY)
+                .setEthSrc(DEFAULT_GATEWAY_MAC)
+                .setArpSha(DEFAULT_GATEWAY_MAC)
+                .setArpSpa(ip4Address)
+                .setOutput(PortNumber.IN_PORT)
+                .build();
+
+        flowService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_ARP_GATEWAY_RULE,
+                PRE_FLAT_TABLE,
+                install);
+    }
+
+    private void setStatefulSnatUpstreamRules(KubevirtNode gatewayNode, KubevirtRouter router,
+                                              Ip4Address ip4Address, boolean install) {
+
+        MacAddress brIntMacAddress = getbrIntMacAddress(deviceService, gatewayNode.intgBridge());
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchEthDst(brIntMacAddress)
+                .build();
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        ExtensionTreatment natTreatment = RulePopulatorUtil
+                .niciraConnTrackTreatmentBuilder(driverService, gatewayNode.intgBridge())
+                .commit(true)
+                .natFlag(CT_NAT_SRC_FLAG)
+                .natAction(true)
+                .natIp(ip4Address)
+                .natPortMin(TpPort.tpPort(TP_PORT_MINIMUM_NUM))
+                .natPortMax(TpPort.tpPort(TP_PORT_MAXIMUM_NUM))
+                .build();
+
+        tBuilder.extension(natTreatment, gatewayNode.intgBridge())
+                .setEthDst(router.peerRouter().macAddress())
+                .setEthSrc(DEFAULT_GATEWAY_MAC)
+                .setOutput(externalPatchPortNum(gatewayNode));
+
+        flowService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                selector,
+                tBuilder.build(),
+                PRIORITY_STATEFUL_SNAT_RULE,
+                PRE_FLAT_TABLE,
+                install);
+    }
+
+    private void setStatefulSnatDownStreamRuleForNetwork(KubevirtNode gatewayNode,
+                                                         KubevirtRouter router,
+                                                         KubevirtNetwork network,
+                                                         boolean install) {
+        kubevirtPortService.ports(network.networkId()).forEach(kubevirtPort -> {
+            String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+            if (routerSnatIp == null) {
+                return;
+            }
+            setStatefulSnatDownStreamRuleForKubevirtPort(gatewayNode, IpAddress.valueOf(routerSnatIp),
+                    kubevirtPort, install);
+        });
+    }
+
+    private void setStatefulSnatDownStreamRuleForKubevirtPort(KubevirtNode gatewayNode,
+                                                         IpAddress gatewaySnatIp,
+                                                         KubevirtPort kubevirtPort,
+                                                         boolean install) {
+        MacAddress brIntMacAddress = getbrIntMacAddress(deviceService, gatewayNode.intgBridge());
+
+        if (brIntMacAddress == null) {
+            log.error("Failed to set stateful snat downstream rule because " +
+                    "there's no br-int port for device {}", gatewayNode.intgBridge());
+            return;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IpPrefix.valueOf(kubevirtPort.ipAddress(), 32));
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(kubevirtPort.macAddress())
+                .transition(FORWARDING_TABLE)
+                .build();
+
+        flowService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                sBuilder.build(),
+                treatment,
+                PRIORITY_STATEFUL_SNAT_RULE,
+                FLAT_TABLE,
+                install);
+    }
+
+    private void setStatefulSnatDownstreamRuleForRouter(KubevirtRouter router,
+                                                        KubevirtNode gatewayNode,
+                                                        IpAddress gatewaySnatIp,
+                                                        boolean install) {
+
+        MacAddress brIntMacAddress = getbrIntMacAddress(deviceService, gatewayNode.intgBridge());
+
+        if (brIntMacAddress == null) {
+            log.error("Failed to set stateful snat downstream rule because " +
+                    "there's no br-int port for device {}", gatewayNode.intgBridge());
+            return;
+        }
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IpPrefix.valueOf(gatewaySnatIp, 32));
+
+        ExtensionTreatment natTreatment = RulePopulatorUtil
+                .niciraConnTrackTreatmentBuilder(driverService, gatewayNode.intgBridge())
+                .commit(false)
+                .natAction(true)
+                .table((short) PRE_FLAT_TABLE)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(brIntMacAddress)
+                .extension(natTreatment, gatewayNode.intgBridge())
+                .build();
+
+        flowService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                sBuilder.build(),
+                treatment,
+                PRIORITY_STATEFUL_SNAT_RULE,
+                PRE_FLAT_TABLE,
+                install);
+
+        router.internal().forEach(networkName -> {
+            KubevirtNetwork network = kubevirtNetworkService.network(networkName);
+
+            if (network != null) {
+                setStatefulSnatDownStreamRuleForNetwork(gatewayNode, router, network, install);
+            }
+        });
+    }
+
+    private PortNumber externalPatchPortNum(KubevirtNode gatewayNode) {
+        Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), "int-to-gateway"))
+                .findAny().orElse(null);
+
+        return port != null ? port.number() : null;
+    }
+
+    private class InternalRouterEventListener implements KubevirtRouterListener {
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtRouterEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_ROUTER_CREATED:
+                    eventExecutor.execute(() -> processRouterCreation(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_REMOVED:
+                    eventExecutor.execute(() -> processRouterDeletion(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_UPDATED:
+                    eventExecutor.execute(() -> processRouterUpdate(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_INTERNAL_NETWORKS_ATTACHED:
+                    eventExecutor.execute(() -> processRouterInternalNetworksAttached(event.subject(),
+                            event.internal()));
+                    break;
+                case KUBEVIRT_ROUTER_INTERNAL_NETWORKS_DETACHED:
+                    eventExecutor.execute(() -> processRouterInternalNetworksDetached(event.subject(),
+                            event.internal()));
+                    break;
+                default:
+                    //do nothing
+                    break;
+            }
+        }
+        private void processRouterInternalNetworksAttached(KubevirtRouter router,
+                                                           Set<String> attachedInternalNetworks) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtNode gwNode = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+            if (gwNode == null) {
+                return;
+            }
+
+            attachedInternalNetworks.forEach(networkId -> {
+                kubevirtPortService.ports(networkId).forEach(kubevirtPort -> {
+                    String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+                    if (routerSnatIp == null) {
+                        return;
+                    }
+                    setStatefulSnatDownStreamRuleForKubevirtPort(gwNode, IpAddress.valueOf(routerSnatIp),
+                            kubevirtPort, true);
+                });
+            });
+        }
+
+        private void processRouterInternalNetworksDetached(KubevirtRouter router,
+                                                           Set<String> detachedInternalNetworks) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtNode gwNode = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+            if (gwNode == null) {
+                return;
+            }
+
+            detachedInternalNetworks.forEach(networkId -> {
+                kubevirtPortService.ports(networkId).forEach(kubevirtPort -> {
+                    String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+                    if (routerSnatIp == null) {
+                        log.info("snatIp is null");
+                        return;
+                    }
+                    setStatefulSnatDownStreamRuleForKubevirtPort(gwNode, IpAddress.valueOf(routerSnatIp),
+                            kubevirtPort, false);
+                });
+            });
+        }
+        private void processRouterCreation(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
+                initGatewayNodeSnatForRouter(router, true);
+            }
+        }
+
+        private void processRouterDeletion(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
+                initGatewayNodeSnatForRouter(router, false);
+            }
+        }
+
+        private void processRouterUpdate(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (router.enableSnat() && !router.external().isEmpty() && router.peerRouter() != null) {
+                initGatewayNodeSnatForRouter(router, true);
+            }
+        }
+    }
+
+    private class InternalNetworkEventListener implements KubevirtNetworkListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtNetworkEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_NETWORK_CREATED:
+                    eventExecutor.execute(() -> processNetworkCreation(event.subject()));
+                    break;
+                case KUBEVIRT_NETWORK_REMOVED:
+                    eventExecutor.execute(() -> processNetworkRemoval(event.subject()));
+                    break;
+                case KUBEVIRT_NETWORK_UPDATED:
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNetworkCreation(KubevirtNetwork network) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            switch (network.type()) {
+                case VXLAN:
+                case GRE:
+                case GENEVE:
+                    break;
+                case FLAT:
+                case VLAN:
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNetworkRemoval(KubevirtNetwork network) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            switch (network.type()) {
+                case VXLAN:
+                case GRE:
+                case GENEVE:
+                    break;
+                case FLAT:
+                case VLAN:
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+    private class InternalKubevirtPortListener implements KubevirtPortListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtPortEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_PORT_CREATED:
+                    eventExecutor.execute(() -> processPortCreation(event.subject()));
+                    break;
+                case KUBEVIRT_PORT_UPDATED:
+                    eventExecutor.execute(() -> processPortUpdate(event.subject()));
+                    break;
+                case KUBEVIRT_PORT_REMOVED:
+                    eventExecutor.execute(() -> processPortDeletion(event.subject()));
+                    break;
+                default:
+                    //do nothing
+                    break;
+            }
+        }
+
+        private void processPortCreation(KubevirtPort kubevirtPort) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtRouter router = routerForKubevirtPort(kubevirtPort);
+            if (router == null) {
+                return;
+            }
+
+            KubevirtNode gwNode = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+
+            if (gwNode != null) {
+                IpAddress gatewaySnatIp = getRouterSnatIpAddress(kubevirtRouterService, kubevirtPort.networkId());
+                if (gatewaySnatIp == null) {
+                    return;
+                }
+                setStatefulSnatDownStreamRuleForKubevirtPort(gwNode, gatewaySnatIp, kubevirtPort, true);
+            }
+        }
+
+        private void processPortUpdate(KubevirtPort kubevirtPort) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtRouter router = routerForKubevirtPort(kubevirtPort);
+            if (router == null) {
+                return;
+            }
+
+            KubevirtNode gwNode = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+
+            if (gwNode != null) {
+                IpAddress gatewaySnatIp = getRouterSnatIpAddress(kubevirtRouterService, kubevirtPort.networkId());
+                if (gatewaySnatIp == null) {
+                    return;
+                }
+                setStatefulSnatDownStreamRuleForKubevirtPort(gwNode, gatewaySnatIp, kubevirtPort, true);
+            }
+        }
+
+        private void processPortDeletion(KubevirtPort kubevirtPort) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtRouter router = routerForKubevirtPort(kubevirtPort);
+            if (router == null) {
+                return;
+            }
+
+            KubevirtNode gwNode = gatewayNodeForSpecifiedRouter(kubevirtNodeService, router);
+
+            if (gwNode != null) {
+                IpAddress gatewaySnatIp = getRouterSnatIpAddress(kubevirtRouterService, kubevirtPort.networkId());
+                if (gatewaySnatIp == null) {
+                    return;
+                }
+                setStatefulSnatDownStreamRuleForKubevirtPort(gwNode, gatewaySnatIp, kubevirtPort, false);
+            }
+        }
+
+        private KubevirtRouter routerForKubevirtPort(KubevirtPort kubevirtPort) {
+            if (kubevirtPort.ipAddress() != null) {
+                return kubevirtRouterService.routers().stream()
+                        .filter(r -> r.internal().contains(kubevirtPort.networkId()))
+                        .findAny().orElse(null);
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
index d852ca4..f40d42c 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
@@ -27,15 +27,19 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onosproject.cfg.ConfigProperty;
 import org.onosproject.kubevirtnetworking.api.DefaultKubevirtPort;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
 import org.onosproject.kubevirtnetworking.api.KubevirtPort;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouter;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
@@ -54,6 +58,7 @@
 import java.util.stream.Collectors;
 
 import static org.onosproject.kubevirtnetworking.api.Constants.TUNNEL_TO_TENANT_PREFIX;
+import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
 import static org.onosproject.net.AnnotationKeys.PORT_NAME;
 
 /**
@@ -83,8 +88,8 @@
     /**
      * Obtains the boolean property value with specified property key name.
      *
-     * @param properties    a collection of properties
-     * @param name          key name
+     * @param properties a collection of properties
+     * @param name       key name
      * @return mapping value
      */
     public static boolean getPropertyValueAsBoolean(Set<ConfigProperty> properties,
@@ -99,7 +104,7 @@
      * Re-structures the OVS port name.
      * The length of OVS port name should be not large than 15.
      *
-     * @param portName  original port name
+     * @param portName original port name
      * @return re-structured OVS port name
      */
     public static String structurePortName(String portName) {
@@ -146,8 +151,8 @@
     /**
      * Prints out the JSON string in pretty format.
      *
-     * @param mapper        Object mapper
-     * @param jsonString    JSON string
+     * @param mapper     Object mapper
+     * @param jsonString JSON string
      * @return pretty formatted JSON string
      */
     public static String prettyJson(ObjectMapper mapper, String jsonString) {
@@ -185,8 +190,8 @@
     /**
      * Calculate the broadcast address from given IP address and subnet prefix length.
      *
-     * @param ipAddr        IP address
-     * @param prefixLength  subnet prefix length
+     * @param ipAddr       IP address
+     * @param prefixLength subnet prefix length
      * @return broadcast address
      */
     public static String getBroadcastAddr(String ipAddr, int prefixLength) {
@@ -194,12 +199,13 @@
         SubnetUtils utils = new SubnetUtils(subnet);
         return utils.getInfo().getBroadcastAddress();
     }
+
     /**
      * Generates endpoint URL by referring to scheme, ipAddress and port.
      *
-     * @param scheme        scheme
-     * @param ipAddress     IP address
-     * @param port          port number
+     * @param scheme    scheme
+     * @param ipAddress IP address
+     * @param port      port number
      * @return generated endpoint URL
      */
     public static String endpoint(KubevirtApiConfig.Scheme scheme, IpAddress ipAddress, int port) {
@@ -218,7 +224,7 @@
     /**
      * Generates endpoint URL by referring to scheme, ipAddress and port.
      *
-     * @param apiConfig     kubernetes API config
+     * @param apiConfig kubernetes API config
      * @return generated endpoint URL
      */
     public static String endpoint(KubevirtApiConfig apiConfig) {
@@ -289,7 +295,7 @@
      * Obtains the tunnel port number with the given network and node.
      *
      * @param network kubevirt network
-     * @param node kubevirt node
+     * @param node    kubevirt node
      * @return tunnel port number
      */
     public static PortNumber tunnelPort(KubevirtNetwork network, KubevirtNode node) {
@@ -310,7 +316,7 @@
      * Obtains the kubevirt port from kubevirt POD.
      *
      * @param networks set of existing kubevirt networks
-     * @param pod kubevirt POD
+     * @param pod      kubevirt POD
      * @return kubevirt port
      */
     public static KubevirtPort getPort(Set<KubevirtNetwork> networks, Pod pod) {
@@ -365,7 +371,7 @@
     /**
      * Obtains the tunnel bridge to tenant bridge patch port number.
      *
-     * @param node kubevirt node
+     * @param node    kubevirt node
      * @param network kubevirt network
      * @return patch port number
      */
@@ -385,7 +391,7 @@
     /**
      * Obtains the tunnel port number of the given node.
      *
-     * @param node kubevirt node
+     * @param node    kubevirt node
      * @param network kubevirt network
      * @return tunnel port number
      */
@@ -432,4 +438,64 @@
         return port != null ? port.number() : null;
     }
 
+    /**
+     * Returns the gateway node for the specified kubevirt router.
+     * Among gateways, only one gateway would act as a gateway per perter.
+     * Currently gateway node is selected based on modulo operation with router hashcode.
+     *
+     * @param nodeService kubevirt node service
+     * @param router      kubevirt router
+     * @return elected gateway node
+     */
+    public static KubevirtNode gatewayNodeForSpecifiedRouter(KubevirtNodeService nodeService,
+                                                             KubevirtRouter router) {
+        //TODO: enhance election logic for a better load balancing
+
+        int numOfGateways = nodeService.completeNodes(GATEWAY).size();
+        if (numOfGateways == 0) {
+            return null;
+        }
+        return (KubevirtNode) nodeService.completeNodes(GATEWAY).toArray()[router.hashCode() % numOfGateways];
+    }
+
+    /**
+     * Returns the mac address of the br-int port of specified device.
+     *
+     * @param deviceService device service
+     * @param deviceId      device Id
+     * @return mac address of the br-int port
+     */
+    public static MacAddress getbrIntMacAddress(DeviceService deviceService,
+                                                DeviceId deviceId) {
+        return MacAddress.valueOf(deviceService.getPorts(deviceId).stream()
+                .filter(port -> Objects.equals(port.annotations().value(PORT_NAME), "br-int"))
+                .map(port -> port.annotations().value("portMac"))
+                .findAny().orElse(null));
+    }
+
+    /**
+     * Returns the snat ip address with specified router.
+     *
+     * @param routerService     router service
+     * @param internalNetworkId internal network id which is associated with the router
+     * @return snat ip address if exist, null otherwise
+     */
+    public static IpAddress getRouterSnatIpAddress(KubevirtRouterService routerService,
+                                                    String internalNetworkId) {
+        KubevirtRouter router = routerService.routers().stream()
+                .filter(r -> r.internal().contains(internalNetworkId))
+                .findAny().orElse(null);
+
+        if (router == null) {
+            return null;
+        }
+
+        String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+
+        if (routerSnatIp == null) {
+            return null;
+        }
+
+        return Ip4Address.valueOf(routerSnatIp);
+    }
 }
diff --git a/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodecTest.java b/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodecTest.java
index f2b2470..3af305c 100644
--- a/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodecTest.java
+++ b/apps/kubevirt-networking/app/src/test/java/org/onosproject/kubevirtnetworking/codec/KubevirtRouterCodecTest.java
@@ -83,6 +83,7 @@
                 .internal(ImmutableSet.of("vlan-1"))
                 .external(ImmutableMap.of("10.10.10.20", "flat-1"))
                 .peerRouter(peerRouter)
+                .electedGateway("gatewayNode")
                 .build();
 
         ObjectNode routerJson = kubevirtRouterCodec.encode(router, context);
@@ -96,10 +97,13 @@
         assertEquals("router-1", router.name());
         assertEquals("Example Virtual Router", router.description());
         assertTrue(router.enableSnat());
+        assertEquals("192.168.10.5",
+                router.external().keySet().stream().findAny().orElse(null));
         assertEquals("external-network", router.external().get("192.168.10.5"));
         assertTrue(router.internal().contains("vxlan-network-1"));
         assertTrue(router.internal().contains("vxlan-network-2"));
         assertEquals("192.168.10.1", router.peerRouter().ipAddress().toString());
+        assertEquals("gatewayNode", router.electedGateway());
     }
 
     private KubevirtRouter getKubevirtRouter(String resourceName) throws IOException {
diff --git a/apps/kubevirt-networking/app/src/test/resources/org/onosproject/kubevirtnetworking/codec/KubevirtRouter.json b/apps/kubevirt-networking/app/src/test/resources/org/onosproject/kubevirtnetworking/codec/KubevirtRouter.json
index 44fd556..14e60ec 100644
--- a/apps/kubevirt-networking/app/src/test/resources/org/onosproject/kubevirtnetworking/codec/KubevirtRouter.json
+++ b/apps/kubevirt-networking/app/src/test/resources/org/onosproject/kubevirtnetworking/codec/KubevirtRouter.json
@@ -12,5 +12,6 @@
   ],
   "peerRouter": {
     "ip": "192.168.10.1"
-  }
+  },
+  "gateway": "gatewayNode"
 }
\ No newline at end of file