Retrieve peer router MAC address using ARP.

Change-Id: I494902d28779288219f631f91080596c7b1ea1fa
(cherry picked from commit 542052721ead4f2b74d954a6328d9de0184d9f65)
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtRouterEvent.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtRouterEvent.java
index e9bb84f..4b8ef56 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtRouterEvent.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtRouterEvent.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.kubevirtnetworking.api;
 
+import org.onlab.packet.MacAddress;
 import org.onosproject.event.AbstractEvent;
 
 import java.util.Set;
@@ -33,6 +34,7 @@
     private final String externalNet;
     private final String peerRouterIp;
     private final String gateway;
+    private final MacAddress peerRouterMac;
 
     /**
      * Creates an event of a given type for the specified kubevirt router.
@@ -49,6 +51,7 @@
         this.externalNet = null;
         this.peerRouterIp = null;
         this.gateway = null;
+        this.peerRouterMac = null;
     }
 
     /**
@@ -67,7 +70,7 @@
         this.externalNet = null;
         this.peerRouterIp = null;
         this.gateway = null;
-
+        this.peerRouterMac = null;
     }
 
     /**
@@ -87,6 +90,7 @@
         this.externalNet = null;
         this.peerRouterIp = null;
         this.gateway = null;
+        this.peerRouterMac = null;
     }
 
     /**
@@ -105,6 +109,7 @@
         this.externalNet = null;
         this.peerRouterIp = null;
         this.gateway = null;
+        this.peerRouterMac = null;
     }
 
     /**
@@ -115,10 +120,11 @@
      * @param externalIp    virtual router's IP address included in external network
      * @param externalNet   external network name
      * @param peerRouterIp  external peer router IP address
+     * @param peerRouterMac external peer router MAC address
      */
     public KubevirtRouterEvent(Type type, KubevirtRouter subject,
                                String externalIp, String externalNet,
-                               String peerRouterIp) {
+                               String peerRouterIp, MacAddress peerRouterMac) {
         super(type, subject);
         this.internal = null;
         this.podName = null;
@@ -127,6 +133,7 @@
         this.externalNet = externalNet;
         this.peerRouterIp = peerRouterIp;
         this.gateway = null;
+        this.peerRouterMac = peerRouterMac;
     }
 
     public KubevirtRouterEvent(Type type, KubevirtRouter subject,
@@ -139,6 +146,7 @@
         this.externalIp = null;
         this.externalNet = null;
         this.peerRouterIp = null;
+        this.peerRouterMac = null;
     }
 
     public enum Type {
@@ -270,6 +278,23 @@
         return gateway;
     }
 
+    /**
+     * Returns the external peer router IP address.
+     *
+     * @return external peer router IP if exists, null otherwise
+     */
+    public String externalPeerRouterIp() {
+        return peerRouterIp;
+    }
+    /**
+     * Returns the external peer router MAC address.
+     *
+     * @return external peer router MAC if exists, null otherwise
+     */
+    public MacAddress peerRouterMac() {
+        return peerRouterMac;
+    }
+
     @Override
     public String toString() {
         if (floatingIp == null) {
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 8f3fdbd..87de35e 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
@@ -270,7 +270,8 @@
                             notifyDelegate(new KubevirtRouterEvent(
                             KUBEVIRT_ROUTER_EXTERNAL_NETWORK_ATTACHED,
                             router, entry.getKey(), entry.getValue(),
-                            newValue.peerRouter().ipAddress().toString())));
+                            newValue.peerRouter().ipAddress().toString(),
+                                    newValue.peerRouter().macAddress())));
             }
 
             if (oldValue.external().size() > 0 && newValue.external().size() == 0) {
@@ -279,7 +280,8 @@
                             notifyDelegate(new KubevirtRouterEvent(
                             KUBEVIRT_ROUTER_EXTERNAL_NETWORK_DETACHED,
                             router, entry.getKey(), entry.getValue(),
-                            oldValue.peerRouter().ipAddress().toString())));
+                            oldValue.peerRouter().ipAddress().toString(),
+                                    oldValue.peerRouter().macAddress())));
             }
 
             Set<String> added = new HashSet<>(newValue.internal());
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 e682029..47bf5cc 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
@@ -84,7 +84,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.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;
@@ -664,7 +663,6 @@
                                                                 KubevirtPort dstPort,
                                                                 KubevirtNode gatewayNode,
                                                                 boolean install) {
-
         Device gwDevice = deviceService.getDevice(gatewayNode.intgBridge());
 
         if (gwDevice == null) {
@@ -699,7 +697,7 @@
                 sBuilder.build(),
                 treatment,
                 PRIORITY_INTERNAL_ROUTING_RULE,
-                FLAT_TABLE,
+                PRE_FLAT_TABLE,
                 install
         );
     }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkManager.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkManager.java
index cc4149e..7b40b01 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkManager.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtNetworkManager.java
@@ -21,6 +21,7 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.event.ListenerRegistry;
+import org.onosproject.kubevirtnetworking.api.KubevirtFlowRuleService;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetwork;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkAdminService;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkEvent;
@@ -28,6 +29,10 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkService;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkStore;
 import org.onosproject.kubevirtnetworking.api.KubevirtNetworkStoreDelegate;
+import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.packet.PacketService;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -72,8 +77,23 @@
     protected CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected KubevirtNetworkStore networkStore;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtRouterService routerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeService kubevirtNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService kubevirtFlowRuleService;
+
     private final KubevirtNetworkStoreDelegate delegate = new InternalNetworkStorageDelegate();
 
     private ApplicationId appId;
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingArpHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingArpHandler.java
new file mode 100644
index 0000000..fd8cb4a
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtRoutingArpHandler.java
@@ -0,0 +1,363 @@
+/*
+ * 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.MacAddress;
+import org.onlab.packet.VlanId;
+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.KubevirtNetworkAdminService;
+import org.onosproject.kubevirtnetworking.api.KubevirtPeerRouter;
+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.kubevirtnetworking.util.KubevirtNetworkingUtil;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
+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.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.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.nio.ByteBuffer;
+import java.util.Objects;
+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.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.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles arp packet.
+ */
+@Component(immediate = true)
+public class KubevirtRoutingArpHandler {
+    protected final Logger log = getLogger(getClass());
+
+    @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 PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtRouterAdminService kubevirtRouterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNetworkAdminService kubevirtNetworkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeService kubevirtNodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService kubevirtFlowRuleService;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+    private final InternalRouterEventListener kubevirtRouterlistener = new InternalRouterEventListener();
+
+    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());
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        kubevirtRouterService.addListener(kubevirtRouterlistener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        leadershipService.withdraw(appId.name());
+        packetService.removeProcessor(packetProcessor);
+        kubevirtRouterService.removeListener(kubevirtRouterlistener);
+
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+    /**
+     * Triggers ARP request to retrieve the peer router mac address.
+     *
+     * @param router kubevirt router
+     * @param peerRouterIp peer router IP address
+     */
+    private void retrievePeerRouterMac(KubevirtRouter router, IpAddress peerRouterIp) {
+
+        log.info("Sending ARP request to the peer router {} to retrieve the MAC address.",
+                peerRouterIp.getIp4Address().toString());
+        String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+
+        if (routerSnatIp == null) {
+            return;
+        }
+
+        IpAddress sourceIp = IpAddress.valueOf(routerSnatIp);
+
+        MacAddress sourceMac = DEFAULT_GATEWAY_MAC;
+        Ethernet ethRequest = ARP.buildArpRequest(sourceMac.toBytes(),
+                sourceIp.toOctets(),
+                peerRouterIp.toOctets(), VlanId.NO_VID);
+
+        KubevirtNode gatewayNode = kubevirtNodeService.node(router.electedGateway());
+
+        if (gatewayNode == null) {
+            return;
+        }
+
+        PortNumber externalPatchPortNum = KubevirtNetworkingUtil.externalPatchPortNum(deviceService, gatewayNode);
+
+        if (externalPatchPortNum == null) {
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(externalPatchPortNum)
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                gatewayNode.intgBridge(),
+                treatment,
+                ByteBuffer.wrap(ethRequest.serialize())));
+    }
+
+    /**
+     * Sets default ARP flow rule to retrieve peer router mac address.
+     *
+     * @param routerSnatIp route Snat IP
+     * @param peerRouterIp peer router IP
+     * @param gatewayNodeId gateway node
+     * @param install install if true, uninstall otherwise
+     */
+    private void setRuleArpRequestToController(IpAddress routerSnatIp,
+                                              IpAddress peerRouterIp,
+                                              String gatewayNodeId,
+                                              boolean install) {
+        KubevirtNode gatewayNode = kubevirtNodeService.node(gatewayNodeId);
+        if (gatewayNode == null) {
+            return;
+        }
+
+        if (routerSnatIp == null) {
+            return;
+        }
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .matchArpOp(ARP.OP_REPLY)
+                .matchArpSpa(peerRouterIp.getIp4Address())
+                .matchArpTpa(routerSnatIp.getIp4Address())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .punt()
+                .build();
+
+        kubevirtFlowRuleService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_ARP_GATEWAY_RULE,
+                PRE_FLAT_TABLE,
+                install
+        );
+    }
+
+    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_GATEWAY_NODE_ATTACHED:
+                case KUBEVIRT_ROUTER_EXTERNAL_NETWORK_ATTACHED:
+                    eventExecutor.execute(() -> processRouterExternalNetAttachedOrGwAttached(event.subject()));
+                    break;
+                case KUBEVIRT_ROUTER_EXTERNAL_NETWORK_DETACHED:
+                    eventExecutor.execute(() -> processRouterExternalNetDetached(event.subject(),
+                            event.externalIp(), event.externalPeerRouterIp()));
+                    break;
+                case KUBEVIRT_GATEWAY_NODE_DETACHED:
+                    eventExecutor.execute(() -> processRouterGatewayNodeDetached(event.subject(), event.gateway()));
+                    break;
+                default:
+                    //do nothing
+                    break;
+            }
+        }
+
+        private void processRouterExternalNetAttachedOrGwAttached(KubevirtRouter router) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            KubevirtNode gatewayNode = kubevirtNodeService.node(router.electedGateway());
+
+            if (gatewayNode == null) {
+                return;
+            }
+
+            String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+            if (routerSnatIp == null) {
+                return;
+            }
+
+            if (router.peerRouter() != null &&
+                    router.peerRouter().macAddress() == null &&
+                    router.peerRouter().ipAddress() != null) {
+                setRuleArpRequestToController(IpAddress.valueOf(routerSnatIp),
+                        router.peerRouter().ipAddress(), gatewayNode.hostname(), true);
+
+                retrievePeerRouterMac(router, router.peerRouter().ipAddress());
+            }
+        }
+
+        private void processRouterExternalNetDetached(KubevirtRouter router, String routerSnatIp,
+                                                      String peerRouterIp) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (router.electedGateway() == null) {
+                return;
+            }
+            KubevirtNode gatewayNode = kubevirtNodeService.node(router.electedGateway());
+
+            if (gatewayNode == null) {
+                return;
+            }
+
+            if (routerSnatIp == null || peerRouterIp == null) {
+                return;
+            }
+            setRuleArpRequestToController(IpAddress.valueOf(routerSnatIp),
+                    IpAddress.valueOf(peerRouterIp), gatewayNode.hostname(), false);
+        }
+
+        private void processRouterGatewayNodeDetached(KubevirtRouter router, String detachedGatewayNode) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (detachedGatewayNode == null) {
+                return;
+            }
+            String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
+            if (routerSnatIp == null) {
+                return;
+            }
+
+            if (router.peerRouter() != null && router.peerRouter().ipAddress() != null) {
+                setRuleArpRequestToController(IpAddress.valueOf(routerSnatIp),
+                        router.peerRouter().ipAddress(), detachedGatewayNode, false);
+            }
+        }
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+
+            if (ethernet != null && ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                processArpPacket(ethernet);
+            }
+        }
+
+        private void processArpPacket(Ethernet ethernet) {
+            ARP arp = (ARP) ethernet.getPayload();
+
+            if (arp.getOpCode() == ARP.OP_REQUEST) {
+                return;
+            }
+
+            IpAddress spa = Ip4Address.valueOf(arp.getSenderProtocolAddress());
+            MacAddress sha = MacAddress.valueOf(arp.getSenderHardwareAddress());
+
+            IpAddress tpa = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+
+            KubevirtRouter router = kubevirtRouterService.routers().stream()
+                    .filter(r -> r.peerRouter() != null && r.peerRouter().ipAddress().equals(spa))
+                    .filter(r -> {
+                        String routerSnatIp = r.external().keySet().stream().findAny().orElse(null);
+                        if (routerSnatIp == null) {
+                            return false;
+                        }
+                        return IpAddress.valueOf(routerSnatIp).equals(tpa);
+                    })
+                    .findAny().orElse(null);
+
+            if (router == null) {
+                return;
+            }
+
+            KubevirtPeerRouter peerRouter = new KubevirtPeerRouter(spa, sha);
+            log.info("Update peer router mac adress {} to router {}", peerRouter.macAddress(), router.name());
+
+            kubevirtRouterService.updatePeerRouterMac(router.name(), sha);
+        }
+    }
+}
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
index 20fa0cb..c341037 100644
--- 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
@@ -82,10 +82,9 @@
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VXLAN;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.externalPatchPortNum;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedRouter;
-import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getExternalNetworkByRouter;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterForKubevirtPort;
-import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterSnatIpAddress;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterMacAddress;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getRouterSnatIpAddress;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.tunnelPort;
 import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.CT_NAT_SRC_FLAG;
 import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.buildExtension;
@@ -190,9 +189,19 @@
             return;
         }
 
-        setArpResponseToPeerRouter(electedGw, Ip4Address.valueOf(routerSnatIp), install);
-        setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(routerSnatIp), install);
-        setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(routerSnatIp), install);
+        String externalNet = router.external().values().stream().findAny().orElse(null);
+        if (externalNet == null) {
+            return;
+        }
+
+        if (router.peerRouter() != null &&
+                router.peerRouter().ipAddress() != null && router.peerRouter().macAddress() != null) {
+            setArpResponseToPeerRouter(electedGw, Ip4Address.valueOf(routerSnatIp), install);
+            setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(routerSnatIp),
+                    router.peerRouter().macAddress(), install);
+            setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(routerSnatIp),
+                    kubevirtNetworkService.network(externalNet), install);
+        }
     }
 
     private void setArpResponseToPeerRouter(KubevirtNode gatewayNode, Ip4Address ip4Address, boolean install) {
@@ -229,14 +238,15 @@
 
     private void setStatefulSnatUpstreamRules(KubevirtNode gatewayNode,
                                               KubevirtRouter router,
-                                              Ip4Address ip4Address,
+                                              Ip4Address routerSnatIp,
+                                              MacAddress peerRouterMacAddress,
                                               boolean install) {
         MacAddress routerMacAddress = getRouterMacAddress(router);
         if (routerMacAddress == null) {
             return;
         }
-        MacAddress peerRouterMacAddres = router.peerRouter().macAddress();
-        if (peerRouterMacAddres == null) {
+
+        if (routerSnatIp == null || peerRouterMacAddress == null) {
             return;
         }
 
@@ -251,13 +261,13 @@
                 .commit(true)
                 .natFlag(CT_NAT_SRC_FLAG)
                 .natAction(true)
-                .natIp(ip4Address)
+                .natIp(routerSnatIp)
                 .natPortMin(TpPort.tpPort(TP_PORT_MINIMUM_NUM))
                 .natPortMax(TpPort.tpPort(TP_PORT_MAXIMUM_NUM))
                 .build();
 
         tBuilder.extension(natTreatment, gatewayNode.intgBridge())
-                .setEthDst(peerRouterMacAddres)
+                .setEthDst(peerRouterMacAddress)
                 .setEthSrc(DEFAULT_GATEWAY_MAC)
                 .setOutput(externalPatchPortNum(deviceService, gatewayNode));
 
@@ -353,7 +363,8 @@
 
     private void setStatefulSnatDownstreamRuleForRouter(KubevirtNode gatewayNode,
                                                         KubevirtRouter router,
-                                                        IpAddress gatewaySnatIp,
+                                                        IpAddress routerSnatIp,
+                                                        KubevirtNetwork externalNetwork,
                                                         boolean install) {
 
         MacAddress routerMacAddress = getRouterMacAddress(router);
@@ -364,8 +375,6 @@
             return;
         }
 
-        KubevirtNetwork externalNetwork = getExternalNetworkByRouter(kubevirtNetworkService, router);
-
         if (externalNetwork == null) {
             log.warn("Failed to set stateful snat downstream rule because " +
                     "there's no external network router {}", router.name());
@@ -383,7 +392,7 @@
             sBuilder.matchEthType(Ethernet.TYPE_IPV4);
         }
 
-        sBuilder.matchIPDst(IpPrefix.valueOf(gatewaySnatIp, 32));
+        sBuilder.matchIPDst(IpPrefix.valueOf(routerSnatIp, 32));
 
         ExtensionTreatment natTreatment = RulePopulatorUtil
                 .niciraConnTrackTreatmentBuilder(driverService, gatewayNode.intgBridge())
@@ -439,10 +448,14 @@
                             event.gateway()));
                     break;
                 case KUBEVIRT_ROUTER_EXTERNAL_NETWORK_ATTACHED:
-                    eventExecutor.execute(() -> processRouterExternalNetAttached(event.subject(), event.externalIp()));
+                    eventExecutor.execute(() -> processRouterExternalNetAttached(event.subject(),
+                            event.externalIp(), event.externalNet(),
+                            event.externalPeerRouterIp(), event.peerRouterMac()));
                     break;
                 case KUBEVIRT_ROUTER_EXTERNAL_NETWORK_DETACHED:
-                    eventExecutor.execute(() -> processRouterExternalNetDetached(event.subject(), event.externalIp()));
+                    eventExecutor.execute(() -> processRouterExternalNetDetached(event.subject(),
+                            event.externalIp(), event.externalNet(),
+                            event.externalPeerRouterIp(), event.peerRouterMac()));
                     break;
                 default:
                     //do nothing
@@ -450,7 +463,8 @@
             }
         }
 
-        private void processRouterExternalNetAttached(KubevirtRouter router, String externalIp) {
+        private void processRouterExternalNetAttached(KubevirtRouter router, String externalIp, String externalNet,
+                                                      String peerRouterIp, MacAddress peerRouterMac) {
             if (!isRelevantHelper()) {
                 return;
             }
@@ -462,10 +476,13 @@
                 return;
             }
 
-            if (router.enableSnat() && router.peerRouter() != null && externalIp != null) {
+            if (router.enableSnat() &&
+                    peerRouterIp != null && peerRouterMac != null && externalIp != null && externalNet != null) {
                 setArpResponseToPeerRouter(electedGw, Ip4Address.valueOf(externalIp), true);
-                setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(externalIp), true);
-                setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(externalIp), true);
+                setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(externalIp),
+                        peerRouterMac, true);
+                setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(externalIp),
+                        kubevirtNetworkService.network(externalNet), true);
             }
 
             router.internal()
@@ -473,10 +490,6 @@
                     .filter(networkId -> kubevirtNetworkService.network(networkId) != null)
                     .map(kubevirtNetworkService::network)
                     .forEach(network -> {
-                        String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
-                        if (routerSnatIp == null) {
-                            return;
-                        }
                         kubevirtPortService.ports(network.networkId()).forEach(kubevirtPort -> {
                             setStatefulSnatDownStreamRuleForKubevirtPort(router,
                                     electedGw, kubevirtPort, true);
@@ -484,7 +497,8 @@
                     });
         }
 
-        private void processRouterExternalNetDetached(KubevirtRouter router, String externalIp) {
+        private void processRouterExternalNetDetached(KubevirtRouter router, String externalIp, String externalNet,
+                                                      String peerRouterIp, MacAddress peerRouterMac) {
             if (!isRelevantHelper()) {
                 return;
             }
@@ -499,10 +513,13 @@
                 return;
             }
 
-            if (router.enableSnat() && router.peerRouter() != null && externalIp != null) {
+            if (router.enableSnat() &&
+                    peerRouterIp != null && peerRouterMac != null && externalIp != null && externalNet != null) {
                 setArpResponseToPeerRouter(electedGw, Ip4Address.valueOf(externalIp), false);
-                setStatefulSnatUpstreamRules(electedGw, router, Ip4Address.valueOf(externalIp), false);
-                setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(externalIp), false);
+                setStatefulSnatUpstreamRules(electedGw, router,
+                        Ip4Address.valueOf(externalIp), peerRouterMac, false);
+                setStatefulSnatDownstreamRuleForRouter(electedGw, router, Ip4Address.valueOf(externalIp),
+                        kubevirtNetworkService.network(externalNet), false);
             }
 
             router.internal()
@@ -510,10 +527,6 @@
                     .filter(networkId -> kubevirtNetworkService.network(networkId) != null)
                     .map(kubevirtNetworkService::network)
                     .forEach(network -> {
-                        String routerSnatIp = router.external().keySet().stream().findAny().orElse(null);
-                        if (routerSnatIp == null) {
-                            return;
-                        }
                         kubevirtPortService.ports(network.networkId()).forEach(kubevirtPort -> {
                             setStatefulSnatDownStreamRuleForKubevirtPort(router,
                                     electedGw, kubevirtPort, false);
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 94a7883..92104a0 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
@@ -42,7 +42,6 @@
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
 import org.onosproject.kubevirtnode.api.KubevirtNodeService;
-import org.onosproject.kubevirtnode.api.KubevirtPhyInterface;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
@@ -550,13 +549,13 @@
      * @return external patch port number
      */
     public static PortNumber externalPatchPortNum(DeviceService deviceService, KubevirtNode gatewayNode) {
-        KubevirtPhyInterface intf = gatewayNode.phyIntfs().stream().findFirst().orElse(null);
-        if (intf == null) {
+        String gatewayBridgeName = gatewayNode.gatewayBridgeName();
+        if (gatewayBridgeName == null) {
             log.warn("No external interface is attached to gateway {}", gatewayNode.hostname());
             return null;
         }
 
-        String patchPortName = "int-to-" + intf.network();
+        String patchPortName = "int-to-" + gatewayBridgeName;
         Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
                 .filter(p -> p.isEnabled() &&
                         Objects.equals(p.annotations().value(PORT_NAME), patchPortName))