From 326b071db1336ee98f48bb8f57672aaaf6a656c5 Mon Sep 17 00:00:00 2001
From: Gary Kotton <gkotton@vmware.com>
Date: Wed, 31 Jan 2018 16:02:12 +0200
Subject: [PATCH] NSX|V: default support for IGMP traffic

Enable IGMP traffic to pass by default. This is added to the default
section created by the plugin.

Change-Id: I0320cc8bf81cda22633637ee9eac4a57fc3b4086
---
 vmware_nsx/plugins/nsx_v/plugin.py            | 22 ++++++++++++++
 .../nsx_v/vshield/securitygroup_utils.py      | 10 ++++++-
 vmware_nsx/plugins/nsx_v/vshield/vcns.py      | 30 +++++++++++++++++++
 .../tests/unit/nsx_v/vshield/fake_vcns.py     |  3 ++
 4 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py
index c5f99c4592..9f7760921f 100644
--- a/vmware_nsx/plugins/nsx_v/plugin.py
+++ b/vmware_nsx/plugins/nsx_v/plugin.py
@@ -526,6 +526,25 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
                 logged=cfg.CONF.nsxv.log_security_groups_allowed_traffic)
             rule_list.append(rule_config)
 
+        igmp_names = ['IGMP Membership Query', 'IGMP V2 Membership Report',
+                      'IGMP V3 Membership Report', 'IGMP Leave Group']
+        igmp_ids = []
+        for name in igmp_names:
+            igmp_id = self._get_appservice_id(name)
+            if igmp_id:
+                igmp_ids.append(igmp_id)
+        if igmp_ids:
+            rules = [{'name': 'Default IGMP rule for OS Security Groups',
+                      'action': 'allow',
+                      'service_ids': igmp_ids}]
+            for rule in rules:
+                rule_config = self.nsx_sg_utils.get_rule_config(
+                    applied_to_ids, rule['name'], rule['action'],
+                    applied_to_type,
+                    application_services=rule['service_ids'],
+                    logged=cfg.CONF.nsxv.log_security_groups_allowed_traffic)
+                rule_list.append(rule_config)
+
         # Default security-group rules
         block_rule = self.nsx_sg_utils.get_rule_config(
             [self.sg_container_id], 'Block All', 'deny',
@@ -4674,3 +4693,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
     def update_housekeeper(self, context, name, housekeeper):
         self.housekeeper.run(context, name)
         return self.housekeeper.get(name)
+
+    def _get_appservice_id(self, name):
+        return self.nsx_v.vcns.get_application_id(name)
diff --git a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py
index 5d7c9e183a..ed62066ca4 100644
--- a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py
+++ b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py
@@ -60,7 +60,8 @@ class NsxSecurityGroupUtils(object):
     def get_rule_config(self, applied_to_ids, name, action='allow',
                         applied_to='SecurityGroup',
                         source=None, destination=None, services=None,
-                        flags=None, logged=False, tag=None):
+                        flags=None, logged=False, tag=None,
+                        application_services=None):
         """Helper method to create a nsx rule dict."""
         ruleTag = et.Element('rule')
         ruleTag.attrib['logged'] = 'true' if logged else 'false'
@@ -116,6 +117,13 @@ class NsxSecurityGroupUtils(object):
                     svcPortTag = et.SubElement(svcTag, 'icmpCode')
                     svcPortTag.text = str(icmpcode)
 
+        if application_services:
+            s = et.SubElement(ruleTag, 'services')
+            for application_service in application_services:
+                svcTag = et.SubElement(s, 'service')
+                svcProtocolTag = et.SubElement(svcTag, 'value')
+                svcProtocolTag.text = str(application_service)
+
         if flags:
             if flags.get('ethertype') is not None:
                 pktTag = et.SubElement(ruleTag, 'packetType')
diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py
index 4cee421647..749119e450 100644
--- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py
+++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py
@@ -121,6 +121,7 @@ class Vcns(object):
             insecure=insecure, timeout=cfg.CONF.nsxv.nsx_transaction_timeout)
         self._nsx_version = None
         self._normalized_scoping_objects = None
+        self._normalized_global_objects = None
 
     @retry_upon_exception(exceptions.ServiceConflict)
     def _client_request(self, client, method, uri,
@@ -1155,3 +1156,32 @@ class Vcns(object):
     def delete_bgp_routing_config(self, edge_id):
         uri = self._build_uri_path(edge_id, BGP_ROUTING_CONFIG)
         return self.do_request(HTTP_DELETE, uri)
+
+    def get_global_objects(self):
+        uri = '%s/application/scope/globalroot-0' % SERVICES_PREFIX
+        h, scoping_objects = self.do_request(HTTP_GET, uri, decode=False,
+                                             format='xml')
+        return scoping_objects
+
+    def _globalobjects_lookup(self, name, use_cache=False):
+        """Return objectId a specific name in the NSX global objects."""
+        # used cached scoping objects during plugin init since it is
+        # a big structure to retrieve and parse each time.
+        if use_cache and self._normalized_global_objects is not None:
+            # Use the cached data
+            root = self._normalized_global_objects
+        else:
+            # Not using cache, or we do want to use it,
+            # but it was not saved yet:
+            # So get the data from the NSX and parse it
+            so_list = self.get_global_objects()
+            root = utils.normalize_xml(so_list)
+            # Save it for possible usage next time (even if not using cache)
+            self._normalized_global_objects = root
+
+        for obj in root.iter('application'):
+            if obj.find('name').text == name:
+                return obj.find('objectId').text
+
+    def get_application_id(self, name):
+        return self._globalobjects_lookup(name, use_cache=True)
diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py
index 8cbcddd689..f7b9884b78 100644
--- a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py
+++ b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py
@@ -1614,3 +1614,6 @@ class FakeVcns(object):
         header = {'status': 200}
         response = ''
         return header, response
+
+    def get_application_id(self, name):
+        return 'application-123'