From 1f9d16fe8d5ce6a95d82b54976a4a8cb227d33a3 Mon Sep 17 00:00:00 2001 From: Roey Chen Date: Sat, 11 Apr 2015 02:56:51 -0700 Subject: [PATCH] NSXv: Fine grained control for logging security-group rules Allows admin to control security-groups rule logging NSXv distributed firewall expose an API to control rule logging, as for the moment, admin user can use this feature only from inside of the distributed firewall. This patch make use of this API to provide the cloud admin with three ways to control security-group logging: - log whenever security-group rule is matched - log when a packet doesn't match any security-group rule - log whenever security-group rule is matched for selected security-groups Change-Id: I2a4dbff2ecba4c6041b4aaad1f20941440a5f6b6 --- etc/nsx.ini | 8 ++ etc/policy.json | 6 +- vmware_nsx/common/config.py | 8 ++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- ...c87aedb206f_nsxv_security_group_logging.py | 34 ++++++ vmware_nsx/db/nsxv_db.py | 4 +- vmware_nsx/db/nsxv_models.py | 1 + vmware_nsx/extensions/securitygrouplogging.py | 67 ++++++++++ vmware_nsx/plugins/nsx_v/plugin.py | 115 +++++++++++++++--- .../nsx_v/vshield/securitygroup_utils.py | 21 +++- .../test_secgroup_rule_local_ip_prefix.py | 5 +- vmware_nsx/tests/unit/nsx_v/test_plugin.py | 40 ++++++ 12 files changed, 286 insertions(+), 25 deletions(-) create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/2c87aedb206f_nsxv_security_group_logging.py create mode 100644 vmware_nsx/extensions/securitygrouplogging.py diff --git a/etc/nsx.ini b/etc/nsx.ini index d7a15957b8..6502d6188f 100644 --- a/etc/nsx.ini +++ b/etc/nsx.ini @@ -191,6 +191,14 @@ # (Optional) DHCP lease time # dhcp_lease_time = 86400 +# (Optional) Indicates whether distributed-firewall rule for security-groups +# blocked traffic is logged. +# log_security_groups_blocked_traffic = False + +# (Optional) Indicates whether distributed-firewall security-groups rules are +# logged. +# log_security_groups_allowed_traffic = False + [nsx] # Maximum number of ports for each bridged logical switch # The recommended value for this parameter varies with NSX version diff --git a/etc/policy.json b/etc/policy.json index 4c7f00368b..4cc5dbbe90 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -139,5 +139,9 @@ "get_service_provider": "rule:regular_user", "get_lsn": "rule:admin_only", - "create_lsn": "rule:admin_only" + "create_lsn": "rule:admin_only", + + "create_security_group:logging": "rule:admin_only", + "update_security_group:logging": "rule:admin_only", + "get_security_group:logging": "rule:admin_only" } diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 4c5340dc60..a9a0871f5a 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -401,6 +401,14 @@ nsxv_opts = [ 'involves configuring the dvs backing nsx_v directly. ' 'If False, only features exposed via nsx_v will be ' 'supported')), + cfg.BoolOpt('log_security_groups_blocked_traffic', + default=False, + help=_("Indicates whether distributed-firewall rule for " + "security-groups blocked traffic is logged")), + cfg.BoolOpt('log_security_groups_allowed_traffic', + default=False, + help=_("Indicates whether distributed-firewall " + "security-groups allowed traffic is logged")), ] # Register the configuration options diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD index d85661892a..88774cf6bc 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -4c45bcadccf9 +2c87aedb206f diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/2c87aedb206f_nsxv_security_group_logging.py b/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/2c87aedb206f_nsxv_security_group_logging.py new file mode 100644 index 0000000000..f65e85898c --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/2c87aedb206f_nsxv_security_group_logging.py @@ -0,0 +1,34 @@ +# Copyright 2016 OpenStack 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. +# + +"""nsxv_security_group_logging + +Revision ID: 2c87aedb206f +Revises: 4c45bcadccf9 +Create Date: 2016-03-15 06:06:06.680092 + +""" + +# revision identifiers, used by Alembic. +revision = '2c87aedb206f' +down_revision = '4c45bcadccf9' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('nsxv_security_group_section_mappings', + sa.Column('logging', sa.Boolean(), nullable=False)) diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index 6cf3769242..1de7a7ccbf 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -366,10 +366,10 @@ def delete_nsxv_internal_edge(session, ext_ip_address): filter_by(ext_ip_address=ext_ip_address).delete()) -def add_neutron_nsx_section_mapping(session, neutron_id, ip_section_id): +def add_neutron_nsx_section_mapping(session, neutron_id, section_id, logging): with session.begin(subtransactions=True): mapping = nsxv_models.NsxvSecurityGroupSectionMapping( - neutron_id=neutron_id, ip_section_id=ip_section_id) + neutron_id=neutron_id, ip_section_id=section_id, logging=logging) session.add(mapping) return mapping diff --git a/vmware_nsx/db/nsxv_models.py b/vmware_nsx/db/nsxv_models.py index 29153bea35..756d912a52 100644 --- a/vmware_nsx/db/nsxv_models.py +++ b/vmware_nsx/db/nsxv_models.py @@ -114,6 +114,7 @@ class NsxvSecurityGroupSectionMapping(model_base.BASEV2): ondelete="CASCADE"), primary_key=True) ip_section_id = sa.Column(sa.String(100)) + logging = sa.Column(sa.Boolean, default=False, nullable=False) class NsxvRuleMapping(model_base.BASEV2): diff --git a/vmware_nsx/extensions/securitygrouplogging.py b/vmware_nsx/extensions/securitygrouplogging.py new file mode 100644 index 0000000000..010e8f5b20 --- /dev/null +++ b/vmware_nsx/extensions/securitygrouplogging.py @@ -0,0 +1,67 @@ +# Copyright 2016 VMware, Inc. All rights reserved. +# +# 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. + +from neutron.api import extensions +from neutron.api.v2 import attributes + +RESOURCE_ATTRIBUTE_MAP = { + 'security_groups': { + 'logging': { + 'allow_post': True, + 'allow_put': True, + 'convert_to': attributes.convert_to_boolean, + 'default': False, + 'enforce_policy': True, + 'is_visible': True} + } +} + + +class Securitygrouplogging(extensions.ExtensionDescriptor): + """Security group logging extension.""" + + @classmethod + def get_name(cls): + return "Security group logging" + + @classmethod + def get_alias(cls): + return "security-group-logging" + + @classmethod + def get_description(cls): + return "Security group logging extension." + + @classmethod + def get_namespace(cls): + # todo + return "http://docs.openstack.org/ext/security_group_logging/api/v2.0" + + @classmethod + def get_updated(cls): + return "2015-04-13T10:00:00-00:00" + + def get_required_extensions(self): + return ["security-group"] + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + return [] + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 753cc368ad..19aea40d5a 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -31,6 +31,7 @@ from neutron.api.v2 import attributes as attr from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources +from neutron import context as n_context from neutron.db import agents_db from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import db_base_plugin_v2 @@ -122,6 +123,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, "router", "security-group", "secgroup-rule-local-ip-prefix", + "security-group-logging", "nsxv-router-type", "nsxv-router-size", "vnic-index", @@ -170,6 +172,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._validate_config() self.sg_container_id = self._create_security_group_container() self.default_section = self._create_cluster_default_fw_section() + self._process_security_groups_rules_logging() self._router_managers = managers.RouterTypeManager(self) if cfg.CONF.nsxv.use_dvs_features: @@ -264,7 +267,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Default security-group rules block_rule = self.nsx_sg_utils.get_rule_config( - [self.sg_container_id], 'Block All', 'deny') + [self.sg_container_id], 'Block All', 'deny', + logged=cfg.CONF.nsxv.log_security_groups_blocked_traffic) rule_list.append(block_rule) with locking.LockManager.get_lock('default-section-init'): @@ -282,6 +286,38 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, section_id = self.nsx_sg_utils.parse_and_get_section_id(c) return section_id + def _process_security_groups_rules_logging(self): + + with locking.LockManager.get_lock('nsx-dfw-section', + lock_file_prefix='dfw-section', + external=True): + context = n_context.get_admin_context() + log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic + + for sg in self.get_security_groups(context, fields=['id']): + fw_section = self._get_section(context.session, sg['id']) + # If the section/sg is already logged, then no action is + # required. + if fw_section is None or fw_section['logging']: + continue + + # Section/sg is not logged, update rules logging according to + # the 'log_security_groups_allowed_traffic' config option. + try: + section_uri = fw_section['ip_section_id'] + h, c = self.nsx_v.vcns.get_section(section_uri) + section = self.nsx_sg_utils.parse_section(c) + section_needs_update = ( + self.nsx_sg_utils.set_rules_logged_option( + section, log_all_rules)) + if section_needs_update: + self.nsx_v.vcns.update_section( + section_uri, + self.nsx_sg_utils.to_xml_string(section), h) + except Exception as exc: + LOG.error(_LE('Unable to update section for logging. %s'), + exc) + def _create_dhcp_static_binding(self, context, neutron_port_db): network_id = neutron_port_db['network_id'] @@ -1926,6 +1962,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if mapping is not None: return mapping['ip_section_id'] + def _get_section(self, session, security_group_id): + return nsxv_db.get_nsx_section(session, security_group_id) + + def _update_section_logging(self, session, section, section_db): + logging = not section_db['logging'] + # Update the DB for the new logging settings. + with session.begin(subtransactions=True): + section_db['logging'] = logging + # Update section rules logging only if we are not already logging them. + log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic + section_needs_update = False + if not log_all_rules: + section_needs_update = ( + self.nsx_sg_utils.set_rules_logged_option(section, logging)) + return section_needs_update + def create_security_group(self, context, security_group, default_sg=False): """Create a security group.""" @@ -1934,6 +1986,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, new_security_group = super(NsxVPluginV2, self).create_security_group( context, security_group, default_sg) + sg_id = new_security_group['id'] nsx_sg_name = self.nsx_sg_utils.get_nsx_sg_name(sg_data) # NSX security-group config @@ -1943,12 +1996,17 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Translate Neutron rules to NSXv fw rules and construct the fw section nsx_sg_id = section_uri = None try: + log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic # Create the nsx security group h, nsx_sg_id = self.nsx_v.vcns.create_security_group(sg_dict) section_name = self.nsx_sg_utils.get_nsx_section_name(nsx_sg_name) - nsx_rules = [self._create_nsx_rule(context, rule, nsx_sg_id) for - rule in new_security_group['security_group_rules']] + logging = sg_data.get('logging', False) + nsx_rules = [] + for rule in new_security_group['security_group_rules']: + nsx_rule = self._create_nsx_rule( + context, rule, nsx_sg_id, logged=log_all_rules or logging) + nsx_rules.append(nsx_rule) section = self.nsx_sg_utils.get_section_with_rules( section_name, nsx_rules) @@ -1961,10 +2019,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Save moref in the DB for future access nsx_db.add_neutron_nsx_security_group_mapping( - context.session, new_security_group['id'], nsx_sg_id) + context.session, sg_id, nsx_sg_id) # Add database associations for fw section and rules nsxv_db.add_neutron_nsx_section_mapping( - context.session, new_security_group['id'], section_uri) + context.session, sg_id, section_uri, logging) for pair in rule_pairs: # Save nsx rule id in the DB for future access nsxv_db.add_neutron_nsx_rule_mapping(context.session, @@ -1979,31 +2037,46 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Only admin can delete the default security-group if default_sg: context = context.elevated() - super(NsxVPluginV2, self).delete_security_group( - context, new_security_group['id']) + super(NsxVPluginV2, self).delete_security_group(context, sg_id) # Delete the created nsx security-group and the fw section self._delete_section(section_uri) self._delete_nsx_security_group(nsx_sg_id) LOG.exception(_LE('Failed to create security group')) + if context.is_admin: + new_security_group['logging'] = logging return new_security_group def update_security_group(self, context, id, security_group): s = security_group['security_group'] nsx_sg_id = nsx_db.get_nsx_security_group_id(context.session, id) - section_uri = self._get_section_uri(context.session, id) - h, c = self.nsx_v.vcns.get_section(section_uri) - section = self.nsx_sg_utils.parse_section(c) + section_db = self._get_section(context.session, id) + section_uri = section_db['ip_section_id'] + section_needs_update = False sg_data = super(NsxVPluginV2, self).update_security_group( context, id, security_group) + + # Reflect security-group name or description changes in the backend, + # dfw section name needs to be updated as well. + h, c = self.nsx_v.vcns.get_section(section_uri) + section = self.nsx_sg_utils.parse_section(c) if set(['name', 'description']) & set(s.keys()): nsx_sg_name = self.nsx_sg_utils.get_nsx_sg_name(sg_data) + section_name = self.nsx_sg_utils.get_nsx_section_name(nsx_sg_name) self.nsx_v.vcns.update_security_group( nsx_sg_id, nsx_sg_name, sg_data['description']) - section_name = self.nsx_sg_utils.get_nsx_section_name(nsx_sg_name) section.attrib['name'] = section_name + section_needs_update = True + # Update the dfw section if security-group logging option has changed. + # TBD: enforce admin only? + if 'logging' in s and s['logging'] != section_db['logging']: + section_needs_update = self._update_section_logging( + context.session, section, section_db) + if section_needs_update: self.nsx_v.vcns.update_section( section_uri, self.nsx_sg_utils.to_xml_string(section), h) + if context.is_admin: + sg_data['logging'] = section_db['logging'] return sg_data def delete_security_group(self, context, id): @@ -2028,7 +2101,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, with excutils.save_and_reraise_exception(): LOG.exception(_LE("Failed to delete security group")) - def _create_nsx_rule(self, context, rule, nsx_sg_id=None): + def _create_nsx_rule(self, context, rule, nsx_sg_id=None, logged=False): src = None dest = None port = None @@ -2089,7 +2162,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, source=src, destination=dest, services=services, - flags=flags) + flags=flags, + logged=logged) return nsx_rule def create_security_group_rule(self, context, security_group_rule): @@ -2103,10 +2177,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, :param security_group_rules: list of rules to create """ sg_rules = security_group_rules['security_group_rules'] + sg_id = sg_rules[0]['security_group_rule']['security_group_id'] ruleids = set() nsx_rules = [] self._validate_security_group_rules(context, security_group_rules) + + # Fetching the the dfw section associated with the security-group + section_db = self._get_section(context.session, sg_id) + section_uri = section_db['ip_section_id'] + logging = section_db['logging'] + log_all_rules = cfg.CONF.nsxv.log_security_groups_allowed_traffic + # Translating Neutron rules to Nsx DFW rules for r in sg_rules: rule = r['security_group_rule'] @@ -2114,12 +2196,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, rule[ext_loip.LOCAL_IP_PREFIX] = None rule['id'] = uuidutils.generate_uuid() ruleids.add(rule['id']) - nsx_rules.append(self._create_nsx_rule(context, rule)) + nsx_rules.append(self._create_nsx_rule( + context, rule, logged=log_all_rules or logging)) - # Find section uri for the security group, retrieve it and update with - # the new rules - section_uri = self._get_section_uri( - context.session, rule['security_group_id']) _h, _c = self.nsx_v.vcns.get_section(section_uri) section = self.nsx_sg_utils.parse_section(_c) self.nsx_sg_utils.extend_section_with_rules(section, nsx_rules) diff --git a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py index b591434248..69ccbf8e2c 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py @@ -58,9 +58,10 @@ 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): + flags=None, logged=False): """Helper method to create a nsx rule dict.""" ruleTag = et.Element('rule') + ruleTag.attrib['logged'] = 'true' if logged else 'false' nameTag = et.SubElement(ruleTag, 'name') nameTag.text = name actionTag = et.SubElement(ruleTag, 'action') @@ -146,3 +147,21 @@ class NsxSecurityGroupUtils(object): def parse_and_get_section_id(self, section_xml): section = et.fromstring(section_xml) return section.attrib['id'] + + def is_section_logged(self, section): + # Determine if this section rules are being logged by the first rule + # 'logged' value. + rule = section.find('rule') + if rule is not None: + return rule.attrib.get('logged') == 'true' + return False + + def set_rules_logged_option(self, section, logged): + value = 'true' if logged else 'false' + rules = section.findall('rule') + updated = False + for rule in rules: + if rule.attrib['logged'] != value: + rule.attrib['logged'] = value + updated = True + return updated diff --git a/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py b/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py index e080f442d9..8abdbf20db 100644 --- a/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py +++ b/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py @@ -120,5 +120,6 @@ class TestNsxVExtendedSGRule(test_nsxv_plugin.NsxVSecurityGroupsTestCase, super(TestNsxVExtendedSGRule, self).test_create_rule_with_local_ip_prefix() plugin.nsx_sg_utils.get_rule_config.assert_called_with( - destination=dest, applied_to_ids=mock.ANY, name=mock.ANY, - services=mock.ANY, source=mock.ANY, flags=mock.ANY) + source=mock.ANY, destination=dest, services=mock.ANY, + name=mock.ANY, applied_to_ids=mock.ANY, flags=mock.ANY, + logged=mock.ANY) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 72436d2e7e..28a4f9d4b1 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -50,6 +50,7 @@ from vmware_nsx.common import nsx_constants from vmware_nsx.db import nsxv_db from vmware_nsx.extensions import routersize as router_size from vmware_nsx.extensions import routertype as router_type +from vmware_nsx.extensions import securitygrouplogging from vmware_nsx.extensions import vnicindex as ext_vnic_idx from vmware_nsx.plugins.nsx_v.vshield.common import constants as vcns_const from vmware_nsx.plugins.nsx_v.vshield import edge_utils @@ -2368,6 +2369,8 @@ class NsxVSecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase): ext_mgr=None, service_plugins=None): test_utils.override_nsx_ini_test() + attributes.RESOURCE_ATTRIBUTE_MAP.update( + securitygrouplogging.RESOURCE_ATTRIBUTE_MAP) mock_vcns = mock.patch(vmware.VCNS_NAME, autospec=True) mock_vcns_instance = mock_vcns.start() self.fc2 = fake_vcns.FakeVcns() @@ -2510,6 +2513,43 @@ class NsxVTestSecurityGroup(ext_sg.TestSecurityGroups, # This test is aimed to test the security-group db mixin pass + def _plugin_update_security_group(self, context, id, logging): + data = {'security_group': {'logging': logging}} + security_group = ( + self.plugin.update_security_group(context, id, data)) + return security_group + + def _plugin_create_security_group(self, context, logging=False): + data = {'security_group': {'name': 'SG', + 'tenant_id': 'tenant_id', + 'description': ''}} + if logging: + data['security_group']['logging'] = True + security_group = ( + self.plugin.create_security_group(context, data, False)) + return security_group + + def test_create_security_group_default_logging(self): + _context = context.get_admin_context() + sg = self._plugin_create_security_group(_context) + self.assertFalse(sg['logging']) + + def test_create_security_group_with_logging(self): + _context = context.get_admin_context() + sg = self._plugin_create_security_group(_context, logging=True) + self.assertTrue(sg['logging']) + + def test_update_security_group_with_logging(self): + _context = context.get_admin_context() + sg = self._plugin_create_security_group(_context) + sg = self._plugin_update_security_group(_context, sg['id'], True) + self.assertTrue(sg['logging']) + + def test_security_group_logging_not_visible_for_user(self): + _context = context.Context('user', 'tenant_id') + sg = self._plugin_create_security_group(_context) + self.assertFalse('logging' in sg) + class TestVdrTestCase(L3NatTest, L3NatTestCaseBase, test_l3_plugin.L3NatDBIntTestCase,