Initial implementation of security group for kubevirt tenant network

Change-Id: If49d03021408a134be01267cc4eee9e0091e3c3d
diff --git a/apps/kubevirt-networking/api/BUILD b/apps/kubevirt-networking/api/BUILD
index 968dd56..e4264cc 100644
--- a/apps/kubevirt-networking/api/BUILD
+++ b/apps/kubevirt-networking/api/BUILD
@@ -1,4 +1,5 @@
 COMPILE_DEPS = CORE_DEPS + [
+    "//apps/kubevirt-node/api:onos-apps-kubevirt-node-api",
     "@k8s_client//jar",
     "@k8s_model//jar",
     "@k8s_model_common//jar",
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
index a2099e1..79e8351 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
@@ -34,7 +34,7 @@
     public static final String TENANT_TO_TUNNEL_PREFIX = "i-to-t-";
     public static final String TUNNEL_TO_TENANT_PREFIX = "t-to-i-";
 
-    // flow table index
+    // provider and VLAN integration bridge flow table index
     public static final int STAT_INBOUND_TABLE = 0;
     public static final int VTAP_INBOUND_TABLE = 1;
     public static final int STAT_FLAT_OUTBOUND_TABLE = 10;
@@ -44,9 +44,9 @@
     public static final int FLAT_TABLE = 32;
     public static final int ARP_TABLE = 35;
     public static final int ACL_EGRESS_TABLE = 40;
-    public static final int ACL_INGRESS_TABLE = 44;
-    public static final int CT_TABLE = 45;
     public static final int ACL_RECIRC_TABLE = 43;
+    public static final int ACL_INGRESS_TABLE = 44;
+    public static final int ACL_CT_TABLE = 45;
     public static final int JUMP_TABLE = 50;
     public static final int ROUTING_TABLE = 60;
     public static final int STAT_OUTBOUND_TABLE = 70;
@@ -54,11 +54,15 @@
     public static final int FORWARDING_TABLE = 80;
     public static final int ERROR_TABLE = 100;
 
-    // tenant integration bridge flow table index
+    // tenant (VXLAN/GRE/GENEVE) integration bridge flow table index
     public static final int TENANT_INBOUND_TABLE = 0;
     public static final int TENANT_DHCP_TABLE = 5;
     public static final int TENANT_ARP_TABLE = 30;
     public static final int TENANT_ICMP_TABLE = 35;
+    public static final int TENANT_ACL_EGRESS_TABLE = 40;
+    public static final int TENANT_ACL_RECIRC_TABLE = 43;
+    public static final int TENANT_ACL_INGRESS_TABLE = 44;
+    public static final int TENANT_ACL_CT_TABLE = 45;
     public static final int TENANT_FORWARDING_TABLE = 80;
 
     // tunnel bridge flow table index
@@ -69,8 +73,17 @@
     public static final int PRIORITY_FORWARDING_RULE = 30000;
     public static final int PRIORITY_DHCP_RULE = 42000;
     public static final int PRIORITY_ARP_GATEWAY_RULE = 41000;
+    public static final int PRIORITY_ARP_DEFAULT_RULE = 40000;
     public static final int PRIORITY_TUNNEL_RULE = 31000;
 
+    public static final int PRIORITY_IP_INGRESS_RULE = 19000;
+    public static final int PRIORITY_IP_EGRESS_RULE = 18000;
+    public static final int PRIORITY_ACL_RULE = 31000;
+    public static final int PRIORITY_ACL_INGRESS_RULE = 30000;
+    public static final int PRIORITY_CT_HOOK_RULE = 30500;
+    public static final int PRIORITY_CT_RULE = 32000;
+    public static final int PRIORITY_CT_DROP_RULE = 32500;
+
     // CLI item length
     public static final int CLI_ID_LENGTH = 30;
     public static final int CLI_NAME_LENGTH = 30;
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
index 35d367f..73c96f0 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtNetwork.java
@@ -30,6 +30,7 @@
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
+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.api.KubevirtNetwork.Type.FLAT;
 import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GENEVE;
@@ -157,9 +158,7 @@
     public DeviceId tenantDeviceId(String hostname) {
         if (type == VXLAN || type == GRE || type == GENEVE) {
             String dpid = genDpidFromName(tenantBridgeName() + "-" + hostname);
-            if (dpid != null) {
-                return DeviceId.deviceId(dpid);
-            }
+            return DeviceId.deviceId(dpid);
         }
         return null;
     }
@@ -176,6 +175,17 @@
     }
 
     @Override
+    public PortNumber tenantToTunnelPort(DeviceId deviceId) {
+        String portName = TENANT_TO_TUNNEL_PREFIX + segmentIdHex(segmentId);
+        Port port = port(deviceId, portName);
+        if (port == null) {
+            return null;
+        } else {
+            return port.number();
+        }
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPort.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPort.java
index 992eb90..2a7385f 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPort.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPort.java
@@ -16,12 +16,17 @@
 package org.onosproject.kubevirtnetworking.api;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
 import java.util.Objects;
+import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -37,6 +42,7 @@
     private final IpAddress ipAddress;
     private final DeviceId deviceId;
     private final PortNumber portNumber;
+    private final Set<String> securityGroups;
 
     /**
      * Default constructor.
@@ -46,14 +52,16 @@
      * @param ipAddress         IP address
      * @param deviceId          device identifier
      * @param portNumber        port number
+     * @param securityGroups    security groups
      */
     public DefaultKubevirtPort(String networkId, MacAddress macAddress, IpAddress ipAddress,
-                               DeviceId deviceId, PortNumber portNumber) {
+                               DeviceId deviceId, PortNumber portNumber, Set<String> securityGroups) {
         this.networkId = networkId;
         this.macAddress = macAddress;
         this.ipAddress = ipAddress;
         this.deviceId = deviceId;
         this.portNumber = portNumber;
+        this.securityGroups = securityGroups;
     }
 
     @Override
@@ -77,6 +85,27 @@
     }
 
     @Override
+    public DeviceId tenantDeviceId() {
+        KubevirtNetworkService networkService =
+                DefaultServiceDirectory.getService(KubevirtNetworkService.class);
+        KubevirtNodeService nodeService =
+                DefaultServiceDirectory.getService(KubevirtNodeService.class);
+        KubevirtNetwork network = networkService.network(networkId);
+        KubevirtNode node = nodeService.node(deviceId);
+        return network.tenantDeviceId(node.hostname());
+    }
+
+    @Override
+    public boolean isTenant() {
+        KubevirtNetworkService networkService =
+                DefaultServiceDirectory.getService(KubevirtNetworkService.class);
+        KubevirtNetwork network = networkService.network(networkId);
+        return network.type() == KubevirtNetwork.Type.VXLAN ||
+                network.type() == KubevirtNetwork.Type.GRE ||
+                network.type() == KubevirtNetwork.Type.GENEVE;
+    }
+
+    @Override
     public PortNumber portNumber() {
         return portNumber;
     }
@@ -89,6 +118,7 @@
                 .ipAddress(updateIpAddress)
                 .deviceId(deviceId)
                 .portNumber(portNumber)
+                .securityGroups(securityGroups)
                 .build();
     }
 
@@ -100,6 +130,7 @@
                 .ipAddress(ipAddress)
                 .deviceId(deviceId)
                 .portNumber(updatedPortNumber)
+                .securityGroups(securityGroups)
                 .build();
     }
 
@@ -111,6 +142,28 @@
                 .ipAddress(ipAddress)
                 .deviceId(updatedDeviceId)
                 .portNumber(portNumber)
+                .securityGroups(securityGroups)
+                .build();
+    }
+
+    @Override
+    public Set<String> securityGroups() {
+        if (securityGroups != null) {
+            return ImmutableSet.copyOf(securityGroups);
+        } else {
+            return ImmutableSet.of();
+        }
+    }
+
+    @Override
+    public KubevirtPort updateSecurityGroups(Set<String> sgs) {
+        return new Builder()
+                .networkId(networkId)
+                .macAddress(macAddress)
+                .ipAddress(ipAddress)
+                .deviceId(deviceId)
+                .portNumber(portNumber)
+                .securityGroups(sgs)
                 .build();
     }
 
@@ -125,12 +178,12 @@
         DefaultKubevirtPort that = (DefaultKubevirtPort) o;
         return networkId.equals(that.networkId) && macAddress.equals(that.macAddress) &&
                 ipAddress.equals(that.ipAddress) && deviceId.equals(that.deviceId) &&
-                portNumber.equals(that.portNumber);
+                portNumber.equals(that.portNumber) && securityGroups.equals(that.securityGroups);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(networkId, macAddress, ipAddress, deviceId, portNumber);
+        return Objects.hash(networkId, macAddress, ipAddress, deviceId, portNumber, securityGroups);
     }
 
     @Override
@@ -141,6 +194,7 @@
                 .add("ipAddress", ipAddress)
                 .add("deviceId", deviceId)
                 .add("portNumber", portNumber)
+                .add("securityGroups", securityGroups)
                 .toString();
     }
 
@@ -163,6 +217,7 @@
         private IpAddress ipAddress;
         private DeviceId deviceId;
         private PortNumber portNumber;
+        private Set<String> securityGroups;
 
         // private constructor not intended to use from external
         private Builder() {
@@ -174,7 +229,7 @@
             checkArgument(macAddress != null, NOT_NULL_MSG, "macAddress");
 
             return new DefaultKubevirtPort(networkId, macAddress, ipAddress,
-                    deviceId, portNumber);
+                    deviceId, portNumber, securityGroups);
         }
 
         @Override
@@ -206,5 +261,11 @@
             this.portNumber = portNumber;
             return this;
         }
+
+        @Override
+        public Builder securityGroups(Set<String> securityGroups) {
+            this.securityGroups = securityGroups;
+            return this;
+        }
     }
 }
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtSecurityGroupRule.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtSecurityGroupRule.java
index 556d0bc..63d5f18 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtSecurityGroupRule.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtSecurityGroupRule.java
@@ -114,6 +114,21 @@
     }
 
     @Override
+    public KubevirtSecurityGroupRule updateDirection(String updated) {
+        return DefaultKubevirtSecurityGroupRule.builder()
+                .remoteGroupId(remoteGroupId)
+                .etherType(etherType)
+                .protocol(protocol)
+                .portRangeMin(portRangeMin)
+                .portRangeMax(portRangeMax)
+                .securityGroupId(securityGroupId)
+                .remoteIpPrefix(remoteIpPrefix)
+                .id(id)
+                .direction(updated)
+                .build();
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
index b1bf336..94ed1de 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetwork.java
@@ -144,7 +144,7 @@
     DeviceId tenantDeviceId(String hostname);
 
     /**
-     * Returns the tunnel to tenant port number.
+     * Returns the tunnel bridge to tenant bridge port number.
      *
      * @param deviceId device identifier
      * @return port number
@@ -152,6 +152,14 @@
     PortNumber tunnelToTenantPort(DeviceId deviceId);
 
     /**
+     * Returns the tenant bridge to tunnel bridge patch port number.
+     *
+     * @param deviceId device identifier
+     * @return port number
+     */
+    PortNumber tenantToTunnelPort(DeviceId deviceId);
+
+    /**
      * Builder of new network.
      */
     interface Builder {
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetworkService.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetworkService.java
index 574f365..e89a635 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetworkService.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtNetworkService.java
@@ -48,4 +48,11 @@
      * @return set of kubevirt networks
      */
     Set<KubevirtNetwork> networks(Type type);
+
+    /**
+     * Returns all kubevirt tenant networks.
+     *
+     * @return set of kubevirt tenant networks
+     */
+    Set<KubevirtNetwork> tenantNetworks();
 }
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPort.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPort.java
index 850ac6f..3f19640 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPort.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPort.java
@@ -20,6 +20,8 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
+import java.util.Set;
+
 /**
  * Representation of virtual port.
  */
@@ -54,6 +56,20 @@
     DeviceId deviceId();
 
     /**
+     * Returns the tenant device ID of the port.
+     *
+     * @return device ID
+     */
+    DeviceId tenantDeviceId();
+
+    /**
+     * Returns the result whether the port is associated with the tenant network.
+     *
+     * @return true if the port is associated with tenant network, false otherwise
+     */
+    boolean isTenant();
+
+    /**
      * Returns the port number of the port.
      *
      * @return port number
@@ -85,6 +101,21 @@
     KubevirtPort updateDeviceId(DeviceId updatedDeviceId);
 
     /**
+     * Returns the security group IDs.
+     *
+     * @return security group identifiers.
+     */
+    Set<String> securityGroups();
+
+    /**
+     * Returns new port instance with the given security groups.
+     *
+     * @param sgs security groups
+     * @return updated port
+     */
+    KubevirtPort updateSecurityGroups(Set<String> sgs);
+
+    /**
      * Builder of new port.
      */
     interface Builder {
@@ -135,5 +166,13 @@
          * @return port builder
          */
         Builder portNumber(PortNumber portNumber);
+
+        /**
+         * Returns port builder with supplied security group identifiers.
+         *
+         * @param securityGroups security group identifiers
+         * @return port builder
+         */
+        Builder securityGroups(Set<String> securityGroups);
     }
 }
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPortEvent.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPortEvent.java
index efc67d5..2c2012a 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPortEvent.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtPortEvent.java
@@ -15,13 +15,20 @@
  */
 package org.onosproject.kubevirtnetworking.api;
 
+import org.onlab.util.Tools;
 import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.DeviceId;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
 
 /**
  * Kubevirt port event class.
  */
 public class KubevirtPortEvent extends AbstractEvent<KubevirtPortEvent.Type, KubevirtPort> {
 
+    private final String securityGroupId;
+    private final DeviceId deviceId;
+
     /**
      * Creates an event of a given type for the specified port.
      *
@@ -30,6 +37,34 @@
      */
     public KubevirtPortEvent(Type type, KubevirtPort subject) {
         super(type, subject);
+        securityGroupId = null;
+        deviceId = null;
+    }
+
+    /**
+     * Creates an event of a given type for the specified port.
+     *
+     * @param type              kubevirt port event type
+     * @param subject           kubevirt port subject
+     * @param securityGroupId   kubevirt security group ID
+     */
+    public KubevirtPortEvent(Type type, KubevirtPort subject, String securityGroupId) {
+        super(type, subject);
+        this.securityGroupId = securityGroupId;
+        this.deviceId = null;
+    }
+
+    /**
+     * Creates an event of a given type for the specified port.
+     *
+     * @param type              kubevirt port event type
+     * @param subject           kubevirt port subject
+     * @param deviceId          kubevirt device ID
+     */
+    public KubevirtPortEvent(Type type, KubevirtPort subject, DeviceId deviceId) {
+        super(type, subject);
+        this.deviceId = deviceId;
+        this.securityGroupId = null;
     }
 
     /**
@@ -51,5 +86,39 @@
          * Signifies that the kubevirt port is removed.
          */
         KUBEVIRT_PORT_REMOVED,
+
+        /**
+         * Signifies that the kubevirt device is added.
+         */
+        KUBEVIRT_PORT_DEVICE_ADDED,
+
+        /**
+         * Signifies that the kubevirt security group rule is added to a specific port.
+         */
+        KUBEVIRT_PORT_SECURITY_GROUP_ADDED,
+
+        /**
+         * Signifies that the kubevirt security group rule is removed from a specific port.
+         */
+        KUBEVIRT_PORT_SECURITY_GROUP_REMOVED,
+    }
+
+    /**
+     * Returns the security group rule IDs updated.
+     *
+     * @return edgestack security group
+     */
+    public String securityGroupId() {
+        return securityGroupId;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("time", Tools.defaultOffsetDataTime(time()))
+                .add("type", type())
+                .add("port", subject())
+                .add("security group", securityGroupId())
+                .toString();
     }
 }
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtSecurityGroupRule.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtSecurityGroupRule.java
index 8010d97..5c99306 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtSecurityGroupRule.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubevirtSecurityGroupRule.java
@@ -86,6 +86,14 @@
     String remoteGroupId();
 
     /**
+     * Returns the security group rule with updated direction.
+     *
+     * @param direction direction
+     * @return updated security group
+     */
+    KubevirtSecurityGroupRule updateDirection(String direction);
+
+    /**
      * A default builder interface.
      */
     interface Builder {
diff --git a/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPortTest.java b/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPortTest.java
index f91ad2e..69b53ed 100644
--- a/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPortTest.java
+++ b/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubevirtPortTest.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.kubevirtnetworking.api;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.testing.EqualsTester;
 import org.junit.Before;
 import org.junit.Test;
@@ -23,6 +24,8 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
+import java.util.Set;
+
 import static junit.framework.TestCase.assertEquals;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 
@@ -40,6 +43,8 @@
     private static final DeviceId DEVICE_ID_2 = DeviceId.deviceId("of:000000000000002");
     private static final PortNumber PORT_NUMBER_1 = PortNumber.portNumber(1);
     private static final PortNumber PORT_NUMBER_2 = PortNumber.portNumber(2);
+    private static final Set<String> SGS_1 = ImmutableSet.of("1");
+    private static final Set<String> SGS_2 = ImmutableSet.of("2");
 
     private KubevirtPort port1;
     private KubevirtPort sameAsPort1;
@@ -64,6 +69,7 @@
                 .ipAddress(IP_ADDRESS_1)
                 .deviceId(DEVICE_ID_1)
                 .portNumber(PORT_NUMBER_1)
+                .securityGroups(SGS_1)
                 .build();
 
         sameAsPort1 = DefaultKubevirtPort.builder()
@@ -72,6 +78,7 @@
                 .ipAddress(IP_ADDRESS_1)
                 .deviceId(DEVICE_ID_1)
                 .portNumber(PORT_NUMBER_1)
+                .securityGroups(SGS_1)
                 .build();
 
         port2 = DefaultKubevirtPort.builder()
@@ -80,6 +87,7 @@
                 .ipAddress(IP_ADDRESS_2)
                 .deviceId(DEVICE_ID_2)
                 .portNumber(PORT_NUMBER_2)
+                .securityGroups(SGS_2)
                 .build();
     }
 
@@ -105,5 +113,6 @@
         assertEquals(IP_ADDRESS_1, port.ipAddress());
         assertEquals(DEVICE_ID_1, port.deviceId());
         assertEquals(PORT_NUMBER_1, port.portNumber());
+        assertEquals(SGS_1, port.securityGroups());
     }
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtPortCodec.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtPortCodec.java
index dbbf9c8..a388227 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtPortCodec.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/codec/KubevirtPortCodec.java
@@ -16,6 +16,7 @@
 package org.onosproject.kubevirtnetworking.codec;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
@@ -40,6 +41,7 @@
     private static final String IP_ADDRESS = "ipAddress";
     private static final String DEVICE_ID = "deviceId";
     private static final String PORT_NUMBER = "portNumber";
+    private static final String SECURITY_GROUPS = "securityGroups";
 
     private static final String MISSING_MESSAGE = " is required in KubevirtPort";
 
@@ -63,6 +65,14 @@
             result.put(PORT_NUMBER, port.portNumber().toString());
         }
 
+        if (port.securityGroups() != null) {
+            ArrayNode sgIds = context.mapper().createArrayNode();
+            for (String sgId : port.securityGroups()) {
+                sgIds.add(sgId);
+            }
+            result.set(SECURITY_GROUPS, sgIds);
+        }
+
         return result;
     }
 
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtPortStore.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtPortStore.java
index 5104bc8..15461df 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtPortStore.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubevirtPortStore.java
@@ -25,6 +25,7 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtPortEvent;
 import org.onosproject.kubevirtnetworking.api.KubevirtPortStore;
 import org.onosproject.kubevirtnetworking.api.KubevirtPortStoreDelegate;
+import org.onosproject.net.DeviceId;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
@@ -41,6 +42,7 @@
 import org.slf4j.Logger;
 
 import java.util.Collection;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
@@ -48,7 +50,10 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_CREATED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_DEVICE_ADDED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_REMOVED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_SECURITY_GROUP_ADDED;
+import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_SECURITY_GROUP_REMOVED;
 import static org.onosproject.kubevirtnetworking.api.KubevirtPortEvent.Type.KUBEVIRT_PORT_UPDATED;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -166,6 +171,8 @@
                     eventExecutor.execute(() ->
                             notifyDelegate(new KubevirtPortEvent(
                                     KUBEVIRT_PORT_UPDATED, event.newValue().value())));
+                    processSecurityGroupEvent(event.oldValue().value(), event.newValue().value());
+                    processDeviceEvent(event.oldValue().value(), event.newValue().value());
                     break;
                 case REMOVE:
                     log.debug("Kubevirt port removed");
@@ -182,5 +189,36 @@
                     break;
             }
         }
+
+        private void processSecurityGroupEvent(KubevirtPort oldPort, KubevirtPort newPort) {
+            Set<String> oldSecurityGroups = oldPort.securityGroups() == null ?
+                    ImmutableSet.of() : oldPort.securityGroups();
+            Set<String> newSecurityGroups = newPort.securityGroups() == null ?
+                    ImmutableSet.of() : newPort.securityGroups();
+
+            oldSecurityGroups.stream()
+                    .filter(sgId -> !Objects.requireNonNull(
+                            newPort.securityGroups()).contains(sgId))
+                    .forEach(sgId -> notifyDelegate(new KubevirtPortEvent(
+                            KUBEVIRT_PORT_SECURITY_GROUP_REMOVED, newPort, sgId
+                    )));
+
+            newSecurityGroups.stream()
+                    .filter(sgId -> !oldPort.securityGroups().contains(sgId))
+                    .forEach(sgId -> notifyDelegate(new KubevirtPortEvent(
+                            KUBEVIRT_PORT_SECURITY_GROUP_ADDED, newPort, sgId
+                    )));
+        }
+
+        private void processDeviceEvent(KubevirtPort oldPort, KubevirtPort newPort) {
+            DeviceId oldDeviceId = oldPort.deviceId();
+            DeviceId newDeviceId = newPort.deviceId();
+
+            if (oldDeviceId == null && newDeviceId != null) {
+                notifyDelegate(new KubevirtPortEvent(
+                        KUBEVIRT_PORT_DEVICE_ADDED, newPort, newDeviceId
+                ));
+            }
+        }
     }
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
index f1a5fe0..b77fb04 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtFloatingIpHandler.java
@@ -37,8 +37,6 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
 import org.onosproject.kubevirtnetworking.util.RulePopulatorUtil;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
-import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
-import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
 import org.onosproject.kubevirtnode.api.KubevirtNodeService;
 import org.onosproject.net.Device;
 import org.onosproject.net.PortNumber;
@@ -130,16 +128,12 @@
     private final InternalRouterEventListener kubevirtRouterListener =
             new InternalRouterEventListener();
 
-    private final InternalNodeEventListener kubevirtNodeListener =
-            new InternalNodeEventListener();
-
     @Activate
     protected void activate() {
         appId = coreService.registerApplication(KUBEVIRT_NETWORKING_APP_ID);
         localNodeId = clusterService.getLocalNode().id();
         leadershipService.runForLeadership(appId.name());
         kubevirtRouterService.addListener(kubevirtRouterListener);
-        kubevirtNodeService.addListener(kubevirtNodeListener);
 
         log.info("Started");
     }
@@ -148,7 +142,6 @@
     protected void deactivate() {
         leadershipService.withdraw(appId.name());
         kubevirtRouterService.removeListener(kubevirtRouterListener);
-        kubevirtNodeService.removeListener(kubevirtNodeListener);
 
         eventExecutor.shutdown();
 
@@ -162,8 +155,6 @@
 
         KubevirtPort kubevirtPort = getKubevirtPort(floatingIp);
         if (kubevirtPort == null) {
-            log.warn("Failed to install floating Ip rules for floating ip {} " +
-                    "because there's no kubevirt port associated to it", floatingIp.floatingIp());
             return;
         }
 
@@ -386,6 +377,14 @@
                     eventExecutor.execute(() -> processRouterGatewayNodeChanged(event.subject(),
                             event.gateway()));
                     break;
+                case KUBEVIRT_GATEWAY_NODE_ATTACHED:
+                    eventExecutor.execute(() -> processGatewayNodeAttachment(event.subject(),
+                            event.gateway()));
+                    break;
+                case KUBEVIRT_GATEWAY_NODE_DETACHED:
+                    eventExecutor.execute(() -> processGatewayNodeDetachment(event.subject(),
+                            event.gateway()));
+                    break;
                 default:
                     //do nothing
                     break;
@@ -415,6 +414,28 @@
             });
         }
 
+        private void processGatewayNodeAttachment(KubevirtRouter router, String gatewayName) {
+            kubevirtRouterService.floatingIps().forEach(fip -> {
+                if (fip.routerName().equals(router.name())) {
+                    KubevirtNode gw = kubevirtNodeService.node(gatewayName);
+                    if (gw != null) {
+                        setFloatingIpRulesForFip(router, fip, gw, true);
+                    }
+                }
+            });
+        }
+
+        private void processGatewayNodeDetachment(KubevirtRouter router, String gatewayName) {
+            kubevirtRouterService.floatingIps().forEach(fip -> {
+                if (fip.routerName().equals(router.name())) {
+                    KubevirtNode gw = kubevirtNodeService.node(gatewayName);
+                    if (gw != null) {
+                        setFloatingIpRulesForFip(router, fip, gw, false);
+                    }
+                }
+            });
+        }
+
         private void processFloatingIpAssociation(KubevirtRouter router, KubevirtFloatingIp floatingIp) {
             if (!isRelevantHelper() || router.electedGateway() == null) {
                 return;
@@ -443,45 +464,4 @@
             setFloatingIpRulesForFip(router, floatingIp, electedGw, false);
         }
     }
-
-    private class InternalNodeEventListener implements KubevirtNodeListener {
-
-        private boolean isRelevantHelper() {
-            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
-        }
-
-        @Override
-        public void event(KubevirtNodeEvent event) {
-            switch (event.type()) {
-                case KUBEVIRT_NODE_COMPLETE:
-                    eventExecutor.execute(() -> processNodeCompletion(event.subject()));
-                    break;
-                case KUBEVIRT_NODE_REMOVED:
-                case KUBEVIRT_NODE_INCOMPLETE:
-                default:
-                    break;
-            }
-        }
-
-        private void processNodeCompletion(KubevirtNode node) {
-            if (!isRelevantHelper() || !node.type().equals(KubevirtNode.Type.GATEWAY)) {
-                return;
-            }
-
-            kubevirtRouterService.floatingIps().forEach(fip -> {
-                KubevirtRouter router = kubevirtRouterService.router(fip.routerName());
-
-                if (router != null && router.electedGateway() != null) {
-                    KubevirtNode electedGw = kubevirtNodeService.node(router.electedGateway());
-                    if (electedGw == null) {
-                        return;
-                    }
-
-                    if (electedGw.hostname().equals(node.hostname())) {
-                        setFloatingIpRulesForFip(router, fip, electedGw, true);
-                    }
-                }
-            });
-        }
-    }
 }
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 603a293..f3edd55 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
@@ -88,12 +88,17 @@
 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_DEFAULT_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_DHCP_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_FORWARDING_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ICMP_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_INTERNAL_ROUTING_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_IP_EGRESS_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_IP_INGRESS_RULE;
 import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_TUNNEL_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_EGRESS_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_INGRESS_TABLE;
 import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ARP_TABLE;
 import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_DHCP_TABLE;
 import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_FORWARDING_TABLE;
@@ -219,9 +224,9 @@
 
     private void createBridge(KubevirtNode node, KubevirtNetwork network) {
 
-        Device tunBridge = deviceService.getDevice(network.tenantDeviceId(node.hostname()));
-        if (tunBridge != null) {
-            log.warn("The tunnel bridge {} already exists at node {}",
+        Device tenantBridge = deviceService.getDevice(network.tenantDeviceId(node.hostname()));
+        if (tenantBridge != null) {
+            log.warn("The tenant bridge {} already exists at node {}",
                     network.tenantBridgeName(), node.hostname());
             setDefaultRulesForTenantNetwork(node, network);
             return;
@@ -321,7 +326,8 @@
         ifaceConfig.removePatchMode(tunToIntIntf);
     }
 
-    private void setArpRulesForTenantNetwork(KubevirtNode node, KubevirtNetwork network) {
+    private void setGatewayArpRulesForTenantNetwork(KubevirtNode node,
+                                                    KubevirtNetwork network) {
 
         KubevirtRouter router = getRouterForKubevirtNetwork(kubevirtRouterService, network);
         if (router == null) {
@@ -333,11 +339,12 @@
             return;
         }
 
-        setGatewayArpRuleForTenantInternalNetwork(router, network, TENANT_ARP_TABLE, electedGw.intgBridge(),
-                network.tenantDeviceId(node.hostname()), true);
+        setGatewayArpRuleForTenantInternalNetwork(router, network, TENANT_ARP_TABLE,
+                electedGw.intgBridge(), network.tenantDeviceId(node.hostname()), true);
     }
 
-    private void setIcmpRulesForTenantNetwork(KubevirtNode node, KubevirtNetwork network) {
+    private void setGatewayIcmpRulesForTenantNetwork(KubevirtNode node,
+                                                     KubevirtNetwork network) {
         KubevirtRouter router = getRouterForKubevirtNetwork(kubevirtRouterService, network);
         if (router == null) {
             return;
@@ -348,11 +355,12 @@
             return;
         }
 
-        setGatewayIcmpRuleForTenantInternalNetwork(router, network, TENANT_ICMP_TABLE, electedGw.intgBridge(),
-                network.tenantDeviceId(node.hostname()), true);
+        setGatewayIcmpRuleForTenantInternalNetwork(router, network, TENANT_ICMP_TABLE,
+                electedGw.intgBridge(), network.tenantDeviceId(node.hostname()), true);
     }
 
-    private void setDefaultGatewayRuleToWorkerNodeWhenNodeCreated(KubevirtNode node, KubevirtNetwork network) {
+    private void setGatewayRuleToWorkerNodeWhenNodeCreated(KubevirtNode node,
+                                                           KubevirtNetwork network) {
         KubevirtRouter router = getRouterForKubevirtNetwork(kubevirtRouterService, network);
         if (router == null) {
             return;
@@ -363,10 +371,12 @@
             return;
         }
 
-        setDefaultGatewayRuleToWorkerNodeTunBridge(router, network, electedGw.intgBridge(), node, true);
+        setDefaultGatewayRuleToWorkerNodeTunBridge(router, network,
+                electedGw.intgBridge(), node, true);
     }
 
-    private void setDefaultRulesForTenantNetwork(KubevirtNode node, KubevirtNetwork network) {
+    private void setDefaultRulesForTenantNetwork(KubevirtNode node,
+                                                 KubevirtNetwork network) {
         DeviceId deviceId = network.tenantDeviceId(node.hostname());
 
         while (!deviceService.isAvailable(deviceId)) {
@@ -384,9 +394,14 @@
         flowService.connectTables(deviceId, TENANT_ARP_TABLE, TENANT_ICMP_TABLE);
         flowService.connectTables(deviceId, TENANT_ICMP_TABLE, TENANT_FORWARDING_TABLE);
 
+        setArpRuleForTenantNetwork(deviceId, true);
         setDhcpRuleForTenantNetwork(deviceId, true);
         setForwardingRule(deviceId, true);
 
+        // security group related rules
+        setTenantIngressTransitionRule(network, network.tenantDeviceId(node.hostname()), true);
+        setEgressTransitionRule(network.tenantDeviceId(node.hostname()), true);
+
         log.info("Install default flow rules for tenant bridge {}", network.tenantBridgeName());
     }
 
@@ -436,13 +451,14 @@
             case VXLAN:
             case GRE:
             case GENEVE:
-                setDefaultEgressRuleToGatewayNode(router, network, electedGateway.intgBridge(), install);
+                setDefaultEgressRuleToGatewayNode(router, network,
+                        electedGateway.intgBridge(), install);
                 kubevirtNodeService.completeNodes(WORKER).forEach(node -> {
-                    setGatewayArpRuleForTenantInternalNetwork(router, network, TENANT_ARP_TABLE,
-                            electedGateway.intgBridge(),
+                    setGatewayArpRuleForTenantInternalNetwork(router, network,
+                            TENANT_ARP_TABLE, electedGateway.intgBridge(),
                             network.tenantDeviceId(node.hostname()), install);
-                    setGatewayIcmpRuleForTenantInternalNetwork(router, network, TENANT_ICMP_TABLE,
-                            electedGateway.intgBridge(),
+                    setGatewayIcmpRuleForTenantInternalNetwork(router, network,
+                            TENANT_ICMP_TABLE, electedGateway.intgBridge(),
                             network.tenantDeviceId(node.hostname()), install);
                     setDefaultGatewayRuleToWorkerNodeTunBridge(router, network,
                             electedGateway.intgBridge(), node, install);
@@ -451,11 +467,12 @@
                 break;
             case FLAT:
             case VLAN:
-                setGatewayArpRuleForProviderInternalNetwork(router, network, PRE_FLAT_TABLE,
-                        electedGateway.intgBridge(), install);
-                setGatewayIcmpRuleForProviderInternalNetwork(router, network, PRE_FLAT_TABLE,
-                        electedGateway.intgBridge(), install);
-                setGatewayProviderInterNetworkRoutingWithinSameRouter(network, router, electedGateway, install);
+                setGatewayArpRuleForProviderInternalNetwork(router, network,
+                        PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
+                setGatewayIcmpRuleForProviderInternalNetwork(router, network,
+                        PRE_FLAT_TABLE, electedGateway.intgBridge(), install);
+                setGatewayProviderInterNetworkRoutingWithinSameRouter(network,
+                        router, electedGateway, install);
                 break;
             default:
                 // do nothing
@@ -519,6 +536,42 @@
                 install);
     }
 
+    private void setTenantIngressTransitionRule(KubevirtNetwork network,
+                                                DeviceId deviceId, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+                .matchInPort(network.tenantToTunnelPort(deviceId));
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.transition(TENANT_ACL_INGRESS_TABLE);
+
+        flowService.setRule(appId,
+                deviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_IP_INGRESS_RULE,
+                TENANT_ICMP_TABLE,
+                install
+        );
+    }
+
+    private void setEgressTransitionRule(DeviceId deviceId, boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(EthType.EtherType.IPV4.ethType().toShort());
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.transition(TENANT_ACL_EGRESS_TABLE);
+
+        flowService.setRule(appId,
+                deviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_IP_EGRESS_RULE,
+                TENANT_ICMP_TABLE,
+                install
+        );
+    }
+
     private void setDefaultEgressRuleToGatewayNode(KubevirtRouter router,
                                                    KubevirtNetwork network,
                                                    DeviceId gwDeviceId,
@@ -610,6 +663,25 @@
                 install);
     }
 
+    private void setArpRuleForTenantNetwork(DeviceId tenantDeviceId,
+                                            boolean install) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort());
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+                .transition(TENANT_FORWARDING_TABLE);
+
+        flowService.setRule(
+                appId,
+                tenantDeviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_ARP_DEFAULT_RULE,
+                TENANT_ARP_TABLE,
+                install
+        );
+    }
+
     private void setGatewayArpRuleForTenantInternalNetwork(KubevirtRouter router,
                                                            KubevirtNetwork network,
                                                            int tableNum,
@@ -1000,7 +1072,8 @@
             });
         }
 
-        private void removeDetachedInternalNetworkRules(KubevirtNetwork removedNetwork, KubevirtRouter router,
+        private void removeDetachedInternalNetworkRules(KubevirtNetwork removedNetwork,
+                                                        KubevirtRouter router,
                                                         KubevirtNode electedGw) {
             router.internal().stream().filter(networkId -> kubevirtNetworkService.network(networkId) != null)
                     .forEach(networkId -> {
@@ -1213,9 +1286,9 @@
                             createBridge(node, network);
                             createPatchTenantInterface(node, network);
                             setDefaultRulesForTenantNetwork(node, network);
-                            setArpRulesForTenantNetwork(node, network);
-                            setIcmpRulesForTenantNetwork(node, network);
-                            setDefaultGatewayRuleToWorkerNodeWhenNodeCreated(node, network);
+                            setGatewayArpRulesForTenantNetwork(node, network);
+                            setGatewayIcmpRulesForTenantNetwork(node, network);
+                            setGatewayRuleToWorkerNodeWhenNodeCreated(node, network);
                             break;
                         case FLAT:
                             reserveVrouterIp(network);
@@ -1322,7 +1395,8 @@
                             || kubevirtNetworkService.network(srcNetwork) == null) {
                         return;
                     }
-                    setGatewayInterNetworkRoutingFromNetworkToPort(router, kubevirtNetworkService.network(srcNetwork),
+                    setGatewayInterNetworkRoutingFromNetworkToPort(router,
+                            kubevirtNetworkService.network(srcNetwork),
                             kubevirtPort, gwNode, true);
                 });
             }
@@ -1347,7 +1421,8 @@
                             || kubevirtNetworkService.network(srcNetwork) == null) {
                         return;
                     }
-                    setGatewayInterNetworkRoutingFromNetworkToPort(router, kubevirtNetworkService.network(srcNetwork),
+                    setGatewayInterNetworkRoutingFromNetworkToPort(router,
+                            kubevirtNetworkService.network(srcNetwork),
                             kubevirtPort, gwNode, true);
                 });
             }
@@ -1372,7 +1447,8 @@
                             || kubevirtNetworkService.network(srcNetwork) == null) {
                         return;
                     }
-                    setGatewayInterNetworkRoutingFromNetworkToPort(router, kubevirtNetworkService.network(srcNetwork),
+                    setGatewayInterNetworkRoutingFromNetworkToPort(router,
+                            kubevirtNetworkService.network(srcNetwork),
                             kubevirtPort, gwNode, false);
                 });
             }
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 7b40b01..74d317d 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
@@ -46,6 +46,9 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GENEVE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GRE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VXLAN;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -216,6 +219,15 @@
                 .filter(n -> n.type() == type).collect(Collectors.toSet()));
     }
 
+    @Override
+    public Set<KubevirtNetwork> tenantNetworks() {
+        return ImmutableSet.copyOf(networkStore.networks().stream()
+                .filter(n -> n.type() == VXLAN ||
+                             n.type() == GRE ||
+                             n.type() == GENEVE)
+                .collect(Collectors.toSet()));
+    }
+
     private class InternalNetworkStorageDelegate implements KubevirtNetworkStoreDelegate {
 
         @Override
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 a2667f9..9acf35e 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
@@ -42,8 +42,6 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
 import org.onosproject.kubevirtnetworking.util.RulePopulatorUtil;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
-import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
-import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
 import org.onosproject.kubevirtnode.api.KubevirtNodeService;
 import org.onosproject.net.Device;
 import org.onosproject.net.PortNumber;
@@ -148,9 +146,6 @@
     private final InternalRouterEventListener kubevirtRouterlistener =
             new InternalRouterEventListener();
 
-    private final InternalNodeEventListener kubevirtNodeListener =
-            new InternalNodeEventListener();
-
     private ApplicationId appId;
     private NodeId localNodeId;
 
@@ -162,7 +157,6 @@
 
         kubevirtPortService.addListener(kubevirtPortListener);
         kubevirtRouterService.addListener(kubevirtRouterlistener);
-        kubevirtNodeService.addListener(kubevirtNodeListener);
 
         log.info("Started");
     }
@@ -170,7 +164,6 @@
     @Deactivate
     protected void deactivate() {
         leadershipService.withdraw(appId.name());
-        kubevirtNodeService.removeListener(kubevirtNodeListener);
         kubevirtPortService.removeListener(kubevirtPortListener);
         kubevirtRouterService.removeListener(kubevirtRouterlistener);
 
@@ -818,12 +811,4 @@
             }
         }
     }
-
-    private class InternalNodeEventListener implements KubevirtNodeListener {
-
-        @Override
-        public void event(KubevirtNodeEvent event) {
-
-        }
-    }
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtSecurityGroupHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtSecurityGroupHandler.java
new file mode 100644
index 0000000..77b60d9
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtSecurityGroupHandler.java
@@ -0,0 +1,1072 @@
+/*
+ * Copyright 2020-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 com.google.common.collect.Sets;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cfg.ConfigProperty;
+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.KubevirtNetwork.Type;
+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.KubevirtSecurityGroup;
+import org.onosproject.kubevirtnetworking.api.KubevirtSecurityGroupEvent;
+import org.onosproject.kubevirtnetworking.api.KubevirtSecurityGroupListener;
+import org.onosproject.kubevirtnetworking.api.KubevirtSecurityGroupRule;
+import org.onosproject.kubevirtnetworking.api.KubevirtSecurityGroupService;
+import org.onosproject.kubevirtnetworking.util.RulePopulatorUtil;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
+import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
+import org.onosproject.kubevirtnode.api.KubevirtNodeService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+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.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.lang.Thread.sleep;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.kubevirtnetworking.api.Constants.ERROR_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ACL_INGRESS_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ACL_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_CT_DROP_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_CT_HOOK_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_CT_RULE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_CT_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_EGRESS_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_INGRESS_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_ACL_RECIRC_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.TENANT_FORWARDING_TABLE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GENEVE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.GRE;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VLAN;
+import static org.onosproject.kubevirtnetworking.api.KubevirtNetwork.Type.VXLAN;
+import static org.onosproject.kubevirtnetworking.impl.OsgiPropertyConstants.USE_SECURITY_GROUP;
+import static org.onosproject.kubevirtnetworking.impl.OsgiPropertyConstants.USE_SECURITY_GROUP_DEFAULT;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.getPropertyValueAsBoolean;
+import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.buildPortRangeMatches;
+import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.computeCtMaskFlag;
+import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.computeCtStateFlag;
+import static org.onosproject.kubevirtnetworking.util.RulePopulatorUtil.niciraConnTrackTreatmentBuilder;
+import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.WORKER;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Populates flow rules to handle EdgeStack SecurityGroups.
+ */
+@Component(
+        immediate = true,
+        property = {
+                USE_SECURITY_GROUP + ":Boolean=" + USE_SECURITY_GROUP_DEFAULT
+        }
+)
+public class KubevirtSecurityGroupHandler {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final int VM_IP_PREFIX = 32;
+
+    private static final String STR_NULL = "null";
+    private static final String PROTO_ICMP = "ICMP";
+    private static final String PROTO_ICMP_NUM = "1";
+    private static final String PROTO_TCP = "TCP";
+    private static final String PROTO_TCP_NUM = "6";
+    private static final String PROTO_UDP = "UDP";
+    private static final String PROTO_UDP_NUM = "17";
+    private static final String PROTO_SCTP = "SCTP";
+    private static final String PROTO_SCTP_NUM = "132";
+    private static final byte PROTOCOL_SCTP = (byte) 0x84;
+    private static final String PROTO_ANY = "ANY";
+    private static final String PROTO_ANY_NUM = "0";
+    private static final String ETHTYPE_IPV4 = "IPV4";
+    private static final String EGRESS = "EGRESS";
+    private static final String INGRESS = "INGRESS";
+    private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
+
+    private static final int ICMP_CODE_MIN = 0;
+    private static final int ICMP_CODE_MAX = 255;
+    private static final int ICMP_TYPE_MIN = 0;
+    private static final int ICMP_TYPE_MAX = 255;
+
+    private static final int CT_COMMIT = 0;
+    private static final int CT_NO_COMMIT = 1;
+    private static final short CT_NO_RECIRC = -1;
+
+    private static final int ACTION_NONE = 0;
+    private static final int ACTION_DROP = -1;
+
+    private static final long SLEEP_MS = 5000;
+
+    /** Apply EdgeStack security group rule for VM traffic. */
+    private boolean useSecurityGroup = USE_SECURITY_GROUP_DEFAULT;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeService nodeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNetworkService networkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtPortService portService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtSecurityGroupService securityGroupService;
+
+    private final KubevirtPortListener portListener =
+            new InternalKubevirtPortListener();
+    private final KubevirtSecurityGroupListener securityGroupListener =
+            new InternalSecurityGroupListener();
+    private final KubevirtNodeListener nodeListener =
+            new InternalNodeListener();
+    private final KubevirtNetworkListener networkListener =
+            new InternalNetworkListener();
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(KUBEVIRT_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        securityGroupService.addListener(securityGroupListener);
+        portService.addListener(portListener);
+        networkService.addListener(networkListener);
+        configService.registerProperties(getClass());
+        nodeService.addListener(nodeListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        securityGroupService.removeListener(securityGroupListener);
+        portService.removeListener(portListener);
+        configService.unregisterProperties(getClass(), false);
+        nodeService.removeListener(nodeListener);
+        networkService.removeListener(networkListener);
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, USE_SECURITY_GROUP);
+        if (flag == null) {
+            log.info("useSecurityGroup is not configured, " +
+                    "using current value of {}", useSecurityGroup);
+        } else {
+            useSecurityGroup = flag;
+            log.info("Configured. useSecurityGroup is {}",
+                    useSecurityGroup ? "enabled" : "disabled");
+        }
+
+        securityGroupService.setSecurityGroupEnabled(useSecurityGroup);
+        resetSecurityGroupRules();
+    }
+
+    private boolean getUseSecurityGroupFlag() {
+        Set<ConfigProperty> properties =
+                configService.getProperties(getClass().getName());
+        return getPropertyValueAsBoolean(properties, USE_SECURITY_GROUP);
+    }
+
+    private void initializeConnTrackTable(DeviceId deviceId, boolean install) {
+
+        // table={ACL_INGRESS_TABLE(44)},ip,ct_state=-trk, actions=ct(table:{ACL_CT_TABLE(45)})
+        long ctState = computeCtStateFlag(false, false, false);
+        long ctMask = computeCtMaskFlag(true, false, false);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, (short) TENANT_ACL_CT_TABLE,
+                ACTION_NONE, PRIORITY_CT_HOOK_RULE, install);
+
+        //table={ACL_CT_TABLE(45)},ip,nw_dst=10.10.0.2,ct_state=+trk+est,action=goto_table:{NORMAL_TABLE(80)}
+        ctState = computeCtStateFlag(true, false, true);
+        ctMask = computeCtMaskFlag(true, false, true);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+                TENANT_FORWARDING_TABLE, PRIORITY_CT_RULE, install);
+
+        //table={ACL_CT_TABLE(45)},ip,nw_dst=10.10.0.2,ct_state=+trk+new,action=drop
+        ctState = computeCtStateFlag(true, true, false);
+        ctMask = computeCtMaskFlag(true, true, false);
+        setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+                ACTION_DROP, PRIORITY_CT_DROP_RULE, install);
+    }
+
+    private void initializeAclTable(DeviceId deviceId, boolean install) {
+
+        ExtensionTreatment ctTreatment =
+                niciraConnTrackTreatmentBuilder(driverService, deviceId)
+                        .commit(true)
+                        .build();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.extension(ctTreatment, deviceId)
+                .transition(TENANT_FORWARDING_TABLE);
+
+        flowRuleService.setRule(appId,
+                deviceId,
+                sBuilder.build(),
+                tBuilder.build(),
+                PRIORITY_ACL_INGRESS_RULE,
+                TENANT_ACL_RECIRC_TABLE,
+                install);
+    }
+
+    private void initializeEgressTable(DeviceId deviceId, boolean install) {
+        if (install) {
+            flowRuleService.setUpTableMissEntry(deviceId, TENANT_ACL_EGRESS_TABLE);
+        } else {
+            flowRuleService.connectTables(deviceId, TENANT_ACL_EGRESS_TABLE, TENANT_FORWARDING_TABLE);
+        }
+    }
+
+    private void initializeIngressTable(DeviceId deviceId, boolean install) {
+        if (install) {
+            flowRuleService.setUpTableMissEntry(deviceId, TENANT_ACL_INGRESS_TABLE);
+        } else {
+            flowRuleService.connectTables(deviceId, TENANT_ACL_INGRESS_TABLE, TENANT_FORWARDING_TABLE);
+        }
+    }
+
+    private void updateSecurityGroupRule(KubevirtPort port,
+                                         KubevirtSecurityGroupRule sgRule, boolean install) {
+
+        if (port == null || sgRule == null) {
+            return;
+        }
+
+        if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().isEmpty()) {
+            getRemotePorts(port, sgRule.remoteGroupId())
+                    .forEach(rPort -> {
+                        populateSecurityGroupRule(sgRule, port,
+                                rPort.ipAddress().toIpPrefix(), install);
+                        populateSecurityGroupRule(sgRule, rPort,
+                                port.ipAddress().toIpPrefix(), install);
+
+                        KubevirtSecurityGroupRule rSgRule = sgRule.updateDirection(
+                                sgRule.direction().equalsIgnoreCase(EGRESS) ? INGRESS : EGRESS);
+                        populateSecurityGroupRule(rSgRule, port,
+                                rPort.ipAddress().toIpPrefix(), install);
+                        populateSecurityGroupRule(rSgRule, rPort,
+                                port.ipAddress().toIpPrefix(), install);
+                    });
+        } else {
+            populateSecurityGroupRule(sgRule, port,
+                    sgRule.remoteIpPrefix() == null ? IP_PREFIX_ANY :
+                            sgRule.remoteIpPrefix(), install);
+        }
+    }
+
+    private boolean checkProtocol(String protocol) {
+        if (protocol == null) {
+            log.debug("No protocol was specified, use default IP(v4/v6) protocol.");
+            return true;
+        } else {
+            String protocolUpper = protocol.toUpperCase();
+            if (protocolUpper.equals(PROTO_TCP) ||
+                    protocolUpper.equals(PROTO_UDP) ||
+                    protocolUpper.equals(PROTO_ICMP) ||
+                    protocolUpper.equals(PROTO_SCTP) ||
+                    protocolUpper.equals(PROTO_ANY) ||
+                    protocol.equals(PROTO_TCP_NUM) ||
+                    protocol.equals(PROTO_UDP_NUM) ||
+                    protocol.equals(PROTO_ICMP_NUM) ||
+                    protocol.equals(PROTO_SCTP_NUM) ||
+                    protocol.equals(PROTO_ANY_NUM)) {
+                return true;
+            } else {
+                log.error("Unsupported protocol {}, we only support " +
+                        "TCP/UDP/ICMP/SCTP protocols.", protocol);
+                return false;
+            }
+        }
+    }
+
+    private void populateSecurityGroupRule(KubevirtSecurityGroupRule sgRule,
+                                           KubevirtPort port,
+                                           IpPrefix remoteIp,
+                                           boolean install) {
+        if (!checkProtocol(sgRule.protocol())) {
+            return;
+        }
+
+        DeviceId deviceId = port.isTenant() ? port.tenantDeviceId() : port.deviceId();
+
+        Set<TrafficSelector> selectors = buildSelectors(sgRule,
+                Ip4Address.valueOf(port.ipAddress().toInetAddress()),
+                remoteIp, port.networkId());
+        if (selectors == null || selectors.isEmpty()) {
+            return;
+        }
+
+        // if the device is not available we do not perform any action
+        if (deviceId == null || !deviceService.isAvailable(deviceId)) {
+            return;
+        }
+
+        // XXX All egress traffic needs to go through connection tracking module,
+        // which might hurt its performance.
+        ExtensionTreatment ctTreatment =
+                niciraConnTrackTreatmentBuilder(driverService, deviceId)
+                        .commit(true)
+                        .build();
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        int aclTable;
+        if (sgRule.direction().equalsIgnoreCase(EGRESS)) {
+            aclTable = TENANT_ACL_EGRESS_TABLE;
+            tBuilder.transition(TENANT_ACL_RECIRC_TABLE);
+        } else {
+            aclTable = TENANT_ACL_INGRESS_TABLE;
+            tBuilder.extension(ctTreatment, deviceId)
+                    .transition(TENANT_FORWARDING_TABLE);
+        }
+
+        int finalAclTable = aclTable;
+        selectors.forEach(selector -> {
+            flowRuleService.setRule(appId,
+                    deviceId,
+                    selector, tBuilder.build(),
+                    PRIORITY_ACL_RULE,
+                    finalAclTable,
+                    install);
+        });
+    }
+
+    /**
+     * Sets connection tracking rule using OVS extension commands.
+     * It is not so graceful, but I don't want to make it more general because
+     * it is going to be used only here.
+     * The following is the usage of the function.
+     *
+     * @param deviceId Device ID
+     * @param ctState ctState: please use RulePopulatorUtil.computeCtStateFlag()
+     *                to build the value
+     * @param ctMask crMask: please use RulePopulatorUtil.computeCtMaskFlag()
+     *               to build the value
+     * @param commit CT_COMMIT for commit action, CT_NO_COMMIT otherwise
+     * @param recircTable table number for recirculation after CT actions.
+     *                    CT_NO_RECIRC with no recirculation
+     * @param action Additional actions. ACTION_DROP, ACTION_NONE,
+     *               GOTO_XXX_TABLE are supported.
+     * @param priority priority value for the rule
+     * @param install true for insertion, false for removal
+     */
+    private void setConnTrackRule(DeviceId deviceId, long ctState, long ctMask,
+                                  int commit, short recircTable,
+                                  int action, int priority, boolean install) {
+
+        ExtensionSelector esCtSate = RulePopulatorUtil
+                .buildCtExtensionSelector(driverService, deviceId, ctState, ctMask);
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .extension(esCtSate, deviceId)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .build();
+
+        TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
+
+        if (commit == CT_COMMIT || recircTable > 0) {
+            RulePopulatorUtil.NiciraConnTrackTreatmentBuilder natTreatmentBuilder =
+                    niciraConnTrackTreatmentBuilder(driverService, deviceId);
+            natTreatmentBuilder.natAction(false);
+            natTreatmentBuilder.commit(commit == CT_COMMIT);
+            if (recircTable > 0) {
+                natTreatmentBuilder.table(recircTable);
+            }
+            tb.extension(natTreatmentBuilder.build(), deviceId);
+        } else if (action == ACTION_DROP) {
+            tb.drop();
+        }
+
+        if (action != ACTION_NONE && action != ACTION_DROP) {
+            tb.transition(action);
+        }
+
+        int tableType = ERROR_TABLE;
+        if (priority == PRIORITY_CT_RULE || priority == PRIORITY_CT_DROP_RULE) {
+            tableType = TENANT_ACL_CT_TABLE;
+        } else if (priority == PRIORITY_CT_HOOK_RULE) {
+            tableType = TENANT_ACL_INGRESS_TABLE;
+        } else {
+            log.error("Cannot an appropriate table for the conn track rule.");
+        }
+
+        flowRuleService.setRule(
+                appId,
+                deviceId,
+                selector,
+                tb.build(),
+                priority,
+                tableType,
+                install);
+    }
+
+    /**
+     * Returns a set of host IP addresses engaged with supplied security group ID.
+     * It only searches a VM in the same tenant boundary.
+     *
+     * @param srcPort edgestack port
+     * @param sgId security group id
+     * @return set of ip addresses
+     */
+    private Set<KubevirtPort> getRemotePorts(KubevirtPort srcPort, String sgId) {
+        return portService.ports().stream()
+                .filter(port -> !port.macAddress().equals(srcPort.macAddress()))
+                .filter(port -> port.securityGroups().contains(sgId))
+                .filter(port -> port.ipAddress() != null)
+                .collect(Collectors.toSet());
+    }
+
+    private Set<TrafficSelector> buildSelectors(KubevirtSecurityGroupRule sgRule,
+                                                Ip4Address vmIp,
+                                                IpPrefix remoteIp,
+                                                String netId) {
+        if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, VM_IP_PREFIX))) {
+            // do nothing if the remote IP is my IP
+            return null;
+        }
+
+        Set<TrafficSelector> selectorSet = Sets.newHashSet();
+
+        if (sgRule.portRangeMax() != null && sgRule.portRangeMin() != null &&
+                sgRule.portRangeMin() < sgRule.portRangeMax()) {
+            Map<TpPort, TpPort> portRangeMatchMap =
+                    buildPortRangeMatches(sgRule.portRangeMin(),
+                            sgRule.portRangeMax());
+            portRangeMatchMap.forEach((key, value) -> {
+
+                TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+                buildMatches(sBuilder, sgRule, vmIp, remoteIp, netId);
+
+                if (sgRule.protocol().equalsIgnoreCase(PROTO_TCP) ||
+                        sgRule.protocol().equals(PROTO_TCP_NUM)) {
+                    if (sgRule.direction().equalsIgnoreCase(EGRESS)) {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchTcpSrc(key);
+                        } else {
+                            sBuilder.matchTcpSrcMasked(key, value);
+                        }
+                    } else {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchTcpDst(key);
+                        } else {
+                            sBuilder.matchTcpDstMasked(key, value);
+                        }
+                    }
+                } else if (sgRule.protocol().equalsIgnoreCase(PROTO_UDP) ||
+                        sgRule.protocol().equals(PROTO_UDP_NUM)) {
+                    if (sgRule.direction().equalsIgnoreCase(EGRESS)) {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchUdpSrc(key);
+                        } else {
+                            sBuilder.matchUdpSrcMasked(key, value);
+                        }
+                    } else {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchUdpDst(key);
+                        } else {
+                            sBuilder.matchUdpDstMasked(key, value);
+                        }
+                    }
+                } else if (sgRule.protocol().equalsIgnoreCase(PROTO_SCTP) ||
+                        sgRule.protocol().equals(PROTO_SCTP_NUM)) {
+                    if (sgRule.direction().equalsIgnoreCase(EGRESS)) {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchSctpSrc(key);
+                        } else {
+                            sBuilder.matchSctpSrcMasked(key, value);
+                        }
+                    } else {
+                        if (value.toInt() == TpPort.MAX_PORT) {
+                            sBuilder.matchSctpDst(key);
+                        } else {
+                            sBuilder.matchSctpDstMasked(key, value);
+                        }
+                    }
+                }
+
+                selectorSet.add(sBuilder.build());
+            });
+        } else {
+
+            TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+            buildMatches(sBuilder, sgRule, vmIp, remoteIp, netId);
+
+            selectorSet.add(sBuilder.build());
+        }
+
+        return selectorSet;
+    }
+
+    private void buildMatches(TrafficSelector.Builder sBuilder,
+                              KubevirtSecurityGroupRule sgRule, Ip4Address vmIp,
+                              IpPrefix remoteIp, String netId) {
+        buildTunnelId(sBuilder, netId);
+        buildMatchEthType(sBuilder, sgRule.etherType());
+        buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
+        buildMatchProto(sBuilder, sgRule.protocol());
+        buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
+                sgRule.portRangeMin() == null ? 0 : sgRule.portRangeMin(),
+                sgRule.portRangeMax() == null ? 0 : sgRule.portRangeMax());
+        buildMatchIcmp(sBuilder, sgRule.protocol(),
+                sgRule.portRangeMin(), sgRule.portRangeMax());
+        buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
+    }
+
+    private void buildTunnelId(TrafficSelector.Builder sBuilder, String netId) {
+        KubevirtNetwork network = networkService.network(netId);
+
+        if (network == null) {
+            log.warn("Network {} not found!", netId);
+            return;
+        }
+
+        String segId = network.segmentId();
+        Type netType = network.type();
+
+        if (netType == VLAN) {
+            sBuilder.matchVlanId(VlanId.vlanId(segId));
+        } else if (netType == VXLAN || netType == GRE || netType == GENEVE) {
+            // sBuilder.matchTunnelId(Long.parseLong(segId));
+            log.trace("{} typed match rules are installed for security group", netType);
+        } else {
+            log.debug("Cannot tag the VID as it is unsupported vnet type {}", netType);
+        }
+
+
+    }
+
+    private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+                                     String direction,
+                                     Ip4Address vmIp) {
+        if (direction.equalsIgnoreCase(EGRESS)) {
+            sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, VM_IP_PREFIX));
+        } else {
+            sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, VM_IP_PREFIX));
+        }
+    }
+
+    private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
+        // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
+        sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+        if (etherType != null && !Objects.equals(etherType, STR_NULL) &&
+                !etherType.equalsIgnoreCase(ETHTYPE_IPV4)) {
+            log.debug("EthType {} is not supported yet in Security Group", etherType);
+        }
+    }
+
+    private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder,
+                                    IpPrefix remoteIpPrefix, String direction) {
+        if (remoteIpPrefix != null &&
+                !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+            if (direction.equalsIgnoreCase(EGRESS)) {
+                sBuilder.matchIPDst(remoteIpPrefix);
+            } else {
+                sBuilder.matchIPSrc(remoteIpPrefix);
+            }
+        }
+    }
+
+    private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
+        if (protocol != null) {
+            switch (protocol.toUpperCase()) {
+                case PROTO_ICMP:
+                case PROTO_ICMP_NUM:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
+                    break;
+                case PROTO_TCP:
+                case PROTO_TCP_NUM:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
+                    break;
+                case PROTO_UDP:
+                case PROTO_UDP_NUM:
+                    sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
+                    break;
+                case PROTO_SCTP:
+                case PROTO_SCTP_NUM:
+                    sBuilder.matchIPProtocol(PROTOCOL_SCTP);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private void buildMatchPort(TrafficSelector.Builder sBuilder,
+                                String protocol, String direction,
+                                int portMin, int portMax) {
+        if (portMax > 0 && portMin == portMax) {
+            if (protocol.equalsIgnoreCase(PROTO_TCP) ||
+                    protocol.equals(PROTO_TCP_NUM)) {
+                if (direction.equalsIgnoreCase(EGRESS)) {
+                    sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+                }
+            } else if (protocol.equalsIgnoreCase(PROTO_UDP) ||
+                    protocol.equals(PROTO_UDP_NUM)) {
+                if (direction.equalsIgnoreCase(EGRESS)) {
+                    sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+                }
+            } else if (protocol.equalsIgnoreCase(PROTO_SCTP) ||
+                    protocol.equals(PROTO_SCTP_NUM)) {
+                if (direction.equalsIgnoreCase(EGRESS)) {
+                    sBuilder.matchSctpSrc(TpPort.tpPort(portMax));
+                } else {
+                    sBuilder.matchSctpDst(TpPort.tpPort(portMax));
+                }
+            }
+        }
+    }
+
+    private void buildMatchIcmp(TrafficSelector.Builder sBuilder,
+                                String protocol, Integer icmpCode, Integer icmpType) {
+        if (protocol != null) {
+            if (protocol.equalsIgnoreCase(PROTO_ICMP) ||
+                    protocol.equals(PROTO_ICMP_NUM)) {
+                if (icmpCode != null && icmpCode >= ICMP_CODE_MIN &&
+                        icmpCode <= ICMP_CODE_MAX) {
+                    sBuilder.matchIcmpCode(icmpCode.byteValue());
+                }
+                if (icmpType != null && icmpType >= ICMP_TYPE_MIN &&
+                        icmpType <= ICMP_TYPE_MAX) {
+                    sBuilder.matchIcmpType(icmpType.byteValue());
+                }
+            }
+        }
+    }
+
+    private void resetSecurityGroupRules() {
+
+        if (getUseSecurityGroupFlag()) {
+            nodeService.completeNodes(WORKER).forEach(node -> {
+                initializeEgressTable(node.intgBridge(), true);
+                initializeConnTrackTable(node.intgBridge(), true);
+                initializeAclTable(node.intgBridge(), true);
+                initializeIngressTable(node.intgBridge(), true);
+
+                for (KubevirtNetwork network : networkService.tenantNetworks()) {
+                    initializeEgressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeIngressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeConnTrackTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeAclTable(network.tenantDeviceId(node.hostname()), true);
+                }
+            });
+
+            securityGroupService.securityGroups().forEach(securityGroup ->
+                    securityGroup.rules().forEach(this::securityGroupRuleAdded));
+        } else {
+            nodeService.completeNodes(WORKER).forEach(node -> {
+                initializeEgressTable(node.intgBridge(), false);
+                initializeConnTrackTable(node.intgBridge(), false);
+                initializeAclTable(node.intgBridge(), false);
+                initializeIngressTable(node.intgBridge(), false);
+
+                for (KubevirtNetwork network : networkService.tenantNetworks()) {
+                    initializeEgressTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeIngressTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeConnTrackTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeAclTable(network.tenantDeviceId(node.hostname()), false);
+                }
+            });
+
+            securityGroupService.securityGroups().forEach(securityGroup ->
+                    securityGroup.rules().forEach(this::securityGroupRuleRemoved));
+        }
+
+        log.info("Reset security group info " +
+                (getUseSecurityGroupFlag() ? "with" : "without") + " Security Group");
+    }
+
+    private void securityGroupRuleAdded(KubevirtSecurityGroupRule sgRule) {
+        portService.ports().stream()
+                .filter(port -> port.securityGroups().contains(sgRule.securityGroupId()))
+                .forEach(port -> {
+                    updateSecurityGroupRule(port, sgRule, true);
+                    log.info("Applied security group rule {} to port {}",
+                            sgRule.id(), port.macAddress());
+                });
+    }
+
+    private void securityGroupRuleRemoved(KubevirtSecurityGroupRule sgRule) {
+        portService.ports().stream()
+                .filter(port -> port.securityGroups().contains(sgRule.securityGroupId()))
+                .forEach(port -> {
+                    updateSecurityGroupRule(port, sgRule, false);
+                    log.info("Removed security group rule {} from port {}",
+                            sgRule.id(), port.macAddress());
+                });
+    }
+
+    private class InternalKubevirtPortListener implements KubevirtPortListener {
+
+        @Override
+        public boolean isRelevant(KubevirtPortEvent event) {
+            return getUseSecurityGroupFlag();
+        }
+
+        private boolean isRelevantHelper(KubevirtPortEvent event) {
+            DeviceId deviceId = event.subject().deviceId();
+
+            if (deviceId == null) {
+                return false;
+            }
+
+            return mastershipService.isLocalMaster(deviceId);
+        }
+
+        @Override
+        public void event(KubevirtPortEvent event) {
+            log.debug("security group event received {}", event);
+
+            switch (event.type()) {
+                case KUBEVIRT_PORT_SECURITY_GROUP_ADDED:
+                    eventExecutor.execute(() -> processPortSgAdd(event));
+                    break;
+                case KUBEVIRT_PORT_SECURITY_GROUP_REMOVED:
+                    eventExecutor.execute(() -> processPortSgRemove(event));
+                    break;
+                case KUBEVIRT_PORT_REMOVED:
+                    eventExecutor.execute(() -> processPortRemove(event));
+                    break;
+                case KUBEVIRT_PORT_DEVICE_ADDED:
+                    eventExecutor.execute(() -> processPortDeviceAdded(event));
+                    break;
+                default:
+                    // do nothing for the other events
+                    break;
+            }
+        }
+
+        private void processPortSgAdd(KubevirtPortEvent event) {
+            if (!isRelevantHelper(event)) {
+                return;
+            }
+
+            if (event.securityGroupId() == null ||
+                securityGroupService.securityGroup(event.securityGroupId()) == null) {
+                return;
+            }
+
+            KubevirtPort port = event.subject();
+            KubevirtSecurityGroup sg = securityGroupService.securityGroup(event.securityGroupId());
+
+            sg.rules().forEach(sgRule -> {
+                updateSecurityGroupRule(port, sgRule, true);
+            });
+            log.info("Added security group {} to port {}",
+                    event.securityGroupId(), event.subject().macAddress());
+        }
+
+        private void processPortSgRemove(KubevirtPortEvent event) {
+            if (!isRelevantHelper(event)) {
+                return;
+            }
+
+            if (event.securityGroupId() == null ||
+                securityGroupService.securityGroup(event.securityGroupId()) == null) {
+                return;
+            }
+
+            KubevirtPort port = event.subject();
+            KubevirtSecurityGroup sg = securityGroupService.securityGroup(event.securityGroupId());
+
+            sg.rules().forEach(sgRule -> {
+                updateSecurityGroupRule(port, sgRule, false);
+            });
+            log.info("Removed security group {} from port {}",
+                    event.securityGroupId(), event.subject().macAddress());
+        }
+
+        private void processPortRemove(KubevirtPortEvent event) {
+            if (!isRelevantHelper(event)) {
+                return;
+            }
+
+            KubevirtPort port = event.subject();
+            for (String sgStr : port.securityGroups()) {
+                KubevirtSecurityGroup sg = securityGroupService.securityGroup(sgStr);
+                sg.rules().forEach(sgRule -> {
+                    updateSecurityGroupRule(port, sgRule, false);
+                });
+                log.info("Removed security group {} from port {}",
+                                        sgStr, event.subject().macAddress());
+            }
+        }
+
+        private void processPortDeviceAdded(KubevirtPortEvent event) {
+            if (!isRelevantHelper(event)) {
+                return;
+            }
+
+            for (String sgId : event.subject().securityGroups()) {
+                KubevirtSecurityGroup sg = securityGroupService.securityGroup(sgId);
+
+                sg.rules().forEach(sgRule -> {
+                    updateSecurityGroupRule(event.subject(), sgRule, true);
+                });
+                log.info("Added security group {} to port {}",
+                        event.securityGroupId(), event.subject().macAddress());
+            }
+        }
+    }
+
+    private class InternalNetworkListener 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:
+                case KUBEVIRT_NETWORK_UPDATED:
+                default:
+                    // do thing
+                    break;
+            }
+        }
+
+        private void processNetworkCreation(KubevirtNetwork network) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            Set<KubevirtNode> nodes = nodeService.completeNodes(WORKER);
+
+            if (nodes.size() > 0) {
+                // now we wait 5s for all tenant bridges are created,
+                // FIXME: we need to fina a better way to wait all tenant bridges
+                // are created before installing default security group rules
+                try {
+                    sleep(SLEEP_MS);
+                } catch (InterruptedException e) {
+                    log.error("Failed to install security group default rules.");
+                }
+
+                for (KubevirtNode node : nodes) {
+                    initializeEgressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeIngressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeConnTrackTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeAclTable(network.tenantDeviceId(node.hostname()), true);
+                }
+            }
+        }
+    }
+
+    private class InternalSecurityGroupListener implements KubevirtSecurityGroupListener {
+
+        @Override
+        public boolean isRelevant(KubevirtSecurityGroupEvent event) {
+            return getUseSecurityGroupFlag();
+        }
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtSecurityGroupEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_SECURITY_GROUP_RULE_CREATED:
+                    eventExecutor.execute(() -> processSgRuleCreate(event));
+                    break;
+                case KUBEVIRT_SECURITY_GROUP_RULE_REMOVED:
+                    eventExecutor.execute(() -> processSgRuleRemove(event));
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processSgRuleCreate(KubevirtSecurityGroupEvent event) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtSecurityGroupRule sgRuleToAdd = event.rule();
+            securityGroupRuleAdded(sgRuleToAdd);
+            log.info("Applied new security group rule {} to ports", sgRuleToAdd.id());
+        }
+
+        private void processSgRuleRemove(KubevirtSecurityGroupEvent event) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            KubevirtSecurityGroupRule sgRuleToRemove = event.rule();
+            securityGroupRuleRemoved(sgRuleToRemove);
+            log.info("Removed security group rule {} from ports", sgRuleToRemove.id());
+        }
+    }
+
+    private class InternalNodeListener implements KubevirtNodeListener {
+
+        @Override
+        public boolean isRelevant(KubevirtNodeEvent event) {
+            return event.subject().type() == WORKER;
+        }
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubevirtNodeEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_NODE_COMPLETE:
+                    eventExecutor.execute(() -> processNodeComplete(event.subject()));
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void processNodeComplete(KubevirtNode node) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            resetSecurityGroupRulesByNode(node);
+        }
+
+        private void resetSecurityGroupRulesByNode(KubevirtNode node) {
+            if (getUseSecurityGroupFlag()) {
+                initializeEgressTable(node.intgBridge(), true);
+                initializeConnTrackTable(node.intgBridge(), true);
+                initializeAclTable(node.intgBridge(), true);
+                initializeIngressTable(node.intgBridge(), true);
+
+                for (KubevirtNetwork network : networkService.tenantNetworks()) {
+                    initializeEgressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeIngressTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeConnTrackTable(network.tenantDeviceId(node.hostname()), true);
+                    initializeAclTable(network.tenantDeviceId(node.hostname()), true);
+                }
+
+                securityGroupService.securityGroups().forEach(securityGroup ->
+                        securityGroup.rules().forEach(
+                                KubevirtSecurityGroupHandler.this::securityGroupRuleAdded));
+            } else {
+                initializeEgressTable(node.intgBridge(), false);
+                initializeConnTrackTable(node.intgBridge(), false);
+                initializeAclTable(node.intgBridge(), false);
+                initializeIngressTable(node.intgBridge(), false);
+
+                for (KubevirtNetwork network : networkService.tenantNetworks()) {
+                    initializeEgressTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeIngressTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeConnTrackTable(network.tenantDeviceId(node.hostname()), false);
+                    initializeAclTable(network.tenantDeviceId(node.hostname()), false);
+                }
+
+                securityGroupService.securityGroups().forEach(securityGroup ->
+                        securityGroup.rules().forEach(
+                                KubevirtSecurityGroupHandler.this::securityGroupRuleRemoved));
+            }
+
+            log.info("Reset security group info " +
+                    (getUseSecurityGroupFlag() ? "with" : "without") + " Security Group");
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtVmWatcher.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtVmWatcher.java
index 61b9be0..308707e 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtVmWatcher.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubevirtVmWatcher.java
@@ -87,6 +87,8 @@
     private static final String DOMAIN = "domain";
     private static final String DEVICES = "devices";
     private static final String INTERFACES = "interfaces";
+    private static final String NETWORK_POLICIES = "networkPolicies";
+    private static final String SECURITY_GROUPS = "securityGroups";
     private static final String NAME = "name";
     private static final String NETWORK = "network";
     private static final String MAC = "macAddress";
@@ -211,6 +213,9 @@
                 case DELETED:
                     eventExecutor.execute(() -> processDeletion(resource));
                     break;
+                case MODIFIED:
+                    eventExecutor.execute(() -> processModification(resource));
+                    break;
                 case ERROR:
                     log.warn("Failures processing VM manipulation.");
                     break;
@@ -238,6 +243,9 @@
 
                 String name = parseResourceName(resource);
 
+                Set<String> sgs = parseSecurityGroups(resource);
+                port = port.updateSecurityGroups(sgs);
+
                 Map<String, IpAddress> ips = parseIpAddresses(resource);
                 IpAddress ip;
                 IpAddress existingIp = ips.get(port.networkId());
@@ -303,6 +311,28 @@
             });
         }
 
+        private void processModification(String resource) {
+            if (!isMaster()) {
+                return;
+            }
+
+            parseMacAddresses(resource).forEach((mac, net) -> {
+                KubevirtPort port = DefaultKubevirtPort.builder()
+                        .macAddress(mac)
+                        .networkId(net)
+                        .build();
+
+                KubevirtPort existing = portAdminService.port(port.macAddress());
+
+                if (existing == null) {
+                    return;
+                }
+
+                Set<String> sgs = parseSecurityGroups(resource);
+                portAdminService.updatePort(existing.updateSecurityGroups(sgs));
+            });
+        }
+
         private void processDeletion(String resource) {
             if (!isMaster()) {
                 return;
@@ -410,6 +440,37 @@
             return null;
         }
 
+        private Set<String> parseSecurityGroups(String resource) {
+            try {
+                ObjectMapper mapper = new ObjectMapper();
+                JsonNode json = mapper.readTree(resource);
+                JsonNode metadata = json.get(SPEC).get(TEMPLATE).get(METADATA);
+
+                JsonNode annots = metadata.get(ANNOTATIONS);
+                if (annots == null) {
+                    return new HashSet<>();
+                }
+
+                JsonNode sgsJson = annots.get(SECURITY_GROUPS);
+                if (sgsJson == null) {
+                    return new HashSet<>();
+                }
+
+                Set<String> result = new HashSet<>();
+                ArrayNode sgs = (ArrayNode) mapper.readTree(sgsJson.asText());
+                for (JsonNode sg : sgs) {
+                    result.add(sg.asText());
+                }
+
+                return result;
+
+            } catch (IOException e) {
+                log.error("Failed to parse kubevirt security group IDs.");
+            }
+
+            return new HashSet<>();
+        }
+
         private Map<MacAddress, String> parseMacAddresses(String resource) {
             try {
                 ObjectMapper mapper = new ObjectMapper();
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/OsgiPropertyConstants.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/OsgiPropertyConstants.java
index 68b15ea..5d5742b 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/OsgiPropertyConstants.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/OsgiPropertyConstants.java
@@ -28,4 +28,7 @@
 
     static final String DHCP_SERVER_MAC = "dhcpServerMac";
     static final String DHCP_SERVER_MAC_DEFAULT = "fe:00:00:00:00:02";
+
+    static final String USE_SECURITY_GROUP = "useSecurityGroup";
+    static final boolean USE_SECURITY_GROUP_DEFAULT = true;
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/RulePopulatorUtil.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/RulePopulatorUtil.java
index 610066f..304e9b6 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/RulePopulatorUtil.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/RulePopulatorUtil.java
@@ -15,15 +15,19 @@
  */
 package org.onosproject.kubevirtnetworking.util;
 
+import com.google.common.collect.Maps;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.TpPort;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.ExtensionSelectorResolver;
 import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.criteria.ExtensionSelectorType;
 import org.onosproject.net.flow.instructions.ExtensionPropertyException;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
 import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
@@ -31,6 +35,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_LOAD;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_MOV_ARP_SHA_TO_THA;
@@ -257,6 +263,185 @@
         }
     }
 
+
+    /**
+     * Builds OVS ConnTrack matches.
+     *
+     * @param driverService driver service
+     * @param deviceId device ID
+     * @param ctState connection tracking sate masking value
+     * @param ctSateMask connection tracking sate masking value
+     * @return OVS ConnTrack extension match
+     */
+    public static ExtensionSelector buildCtExtensionSelector(DriverService driverService,
+                                                             DeviceId deviceId,
+                                                             long ctState,
+                                                             long ctSateMask) {
+        DriverHandler handler = driverService.createHandler(deviceId);
+        ExtensionSelectorResolver esr = handler.behaviour(ExtensionSelectorResolver.class);
+
+        ExtensionSelector extensionSelector = esr.getExtensionSelector(
+                ExtensionSelectorType.ExtensionSelectorTypes.NICIRA_MATCH_CONNTRACK_STATE.type());
+        try {
+            extensionSelector.setPropertyValue(CT_STATE, ctState);
+            extensionSelector.setPropertyValue(CT_STATE_MASK, ctSateMask);
+        } catch (Exception e) {
+            log.error("Failed to set nicira match CT state because of {}", e);
+            return null;
+        }
+
+        return extensionSelector;
+    }
+
+    /**
+     * Computes ConnTack State flag values.
+     *
+     * @param isTracking true for +trk, false for -trk
+     * @param isNew true for +new, false for nothing
+     * @param isEstablished true for +est, false for nothing
+     * @return ConnTrack State flags
+     */
+    public static long computeCtStateFlag(boolean isTracking,
+                                          boolean isNew,
+                                          boolean isEstablished) {
+        long ctMaskFlag = 0x00;
+
+        if (isTracking) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+        }
+
+        if (isNew) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+        }
+
+        if (isEstablished) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+        }
+
+        return ctMaskFlag;
+    }
+
+    /**
+     * Computes ConnTrack State mask values.
+     *
+     * @param isTracking true for setting +trk/-trk value, false for otherwise
+     * @param isNew true for setting +new value, false for otherwise
+     * @param isEstablished true for setting +est value, false for otherwise
+     * @return ConnTrack State Mask value
+     */
+    public static long computeCtMaskFlag(boolean isTracking,
+                                         boolean isNew,
+                                         boolean isEstablished) {
+        long ctMaskFlag = 0x00;
+
+        if (isTracking) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+        }
+
+        if (isNew) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+        }
+
+        if (isEstablished) {
+            ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+            ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+        }
+
+        return ctMaskFlag;
+    }
+
+    /**
+     * Computes port and port mask value from port min/max values.
+     *
+     * @param portMin port min value
+     * @param portMax port max value
+     * @return Port Mask value
+     */
+    public static Map<TpPort, TpPort> buildPortRangeMatches(int portMin, int portMax) {
+
+        boolean processing = true;
+        int start = portMin;
+        Map<TpPort, TpPort> portMaskMap = Maps.newHashMap();
+        while (processing) {
+            String minStr = Integer.toBinaryString(start);
+            String binStrMinPadded = STR_PADDING.substring(minStr.length()) + minStr;
+
+            int mask = testMasks(binStrMinPadded, start, portMax);
+            int maskStart = binLower(binStrMinPadded, mask);
+            int maskEnd = binHigher(binStrMinPadded, mask);
+
+            log.debug("start : {} port/mask = {} / {} ", start, getMask(mask), maskStart);
+            portMaskMap.put(TpPort.tpPort(maskStart), TpPort.tpPort(
+                    Integer.parseInt(Objects.requireNonNull(getMask(mask)), PORT_RADIX)));
+
+            start = maskEnd + 1;
+            if (start > portMax) {
+                processing = false;
+            }
+        }
+
+        return portMaskMap;
+    }
+
+    private static int binLower(String binStr, int bits) {
+        StringBuilder outBin = new StringBuilder(
+                binStr.substring(MASK_BEGIN_IDX, MASK_MAX_IDX - bits));
+        for (int i = 0; i < bits; i++) {
+            outBin.append(STR_ZERO);
+        }
+
+        return Integer.parseInt(outBin.toString(), MASK_RADIX);
+    }
+
+    private static int binHigher(String binStr, int bits) {
+        StringBuilder outBin = new StringBuilder(
+                binStr.substring(MASK_BEGIN_IDX, MASK_MAX_IDX - bits));
+        for (int i = 0; i < bits; i++) {
+            outBin.append(STR_ONE);
+        }
+
+        return Integer.parseInt(outBin.toString(), MASK_RADIX);
+    }
+
+    private static int testMasks(String binStr, int start, int end) {
+        int mask = MASK_BEGIN_IDX;
+        for (; mask <= MASK_MAX_IDX; mask++) {
+            int maskStart = binLower(binStr, mask);
+            int maskEnd = binHigher(binStr, mask);
+            if (maskStart < start || maskEnd > end) {
+                return mask - 1;
+            }
+        }
+
+        return mask;
+    }
+
+    private static String getMask(int bits) {
+        switch (bits) {
+            case 0:  return "ffff";
+            case 1:  return "fffe";
+            case 2:  return "fffc";
+            case 3:  return "fff8";
+            case 4:  return "fff0";
+            case 5:  return "ffe0";
+            case 6:  return "ffc0";
+            case 7:  return "ff80";
+            case 8:  return "ff00";
+            case 9:  return "fe00";
+            case 10: return "fc00";
+            case 11: return "f800";
+            case 12: return "f000";
+            case 13: return "e000";
+            case 14: return "c000";
+            case 15: return "8000";
+            case 16: return "0000";
+            default: return null;
+        }
+    }
+
     public static NiciraConnTrackTreatmentBuilder niciraConnTrackTreatmentBuilder(DriverService ds, DeviceId id) {
         return new NiciraConnTrackTreatmentBuilder(ds, id);
     }