]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Firewall as a Service (FWaaS) Iptables Driver
authorRajesh Mohan <rajesh.mlists@gmail.com>
Sat, 22 Jun 2013 00:55:08 +0000 (17:55 -0700)
committerRajesh Mohan <rajesh.mlists@gmail.com>
Tue, 13 Aug 2013 01:58:38 +0000 (18:58 -0700)
blueprint quantum-fwaas-iptables-driver

This is IPTables Driver for "Firewall As A
Service" feature.

This implements
- Fwaas rules are mapped to IPTables
- The rules are installed in the network
  namespace of quantum-routers

Change-Id: I157182f2c86fbcf8c141b9ad3cfc71168153ebf8

neutron/services/firewall/agents/firewall_agent_api.py
neutron/services/firewall/drivers/__init__.py [new file with mode: 0644]
neutron/services/firewall/drivers/fwaas_base.py [new file with mode: 0644]
neutron/services/firewall/drivers/linux/__init__.py [new file with mode: 0644]
neutron/services/firewall/drivers/linux/iptables_fwaas.py [new file with mode: 0644]
neutron/tests/unit/services/firewall/drivers/__init__.py [new file with mode: 0644]
neutron/tests/unit/services/firewall/drivers/linux/__init__.py [new file with mode: 0644]
neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py [new file with mode: 0644]

index dcfb0d07219e3f11351020ed6ff7f48d2646e654..6d06a51dd9e1f2c4c31ee1a6e81868a16ae617f1 100644 (file)
@@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
 FWaaSOpts = [
     cfg.StrOpt(
         'driver',
-        default=('neutron.services.firewall.agents.firewall_agent_api.'
+        default=('neutron.services.firewall.drivers.fwaas_base.'
                  'NoopFwaasDriver'),
         help=_("Name of the FWaaS Driver")),
     cfg.BoolOpt(
@@ -84,25 +84,3 @@ class FWaaSAgentRpcCallbackMixin(object):
     def delete_firewall(self, context, firewall, host):
         """Handle RPC cast from plugin to delete a firewall."""
         pass
-
-
-class NoopFwaasDriver(object):
-    """Noop Fwaas Driver.
-
-    Firewall driver which does nothing.
-    This driver is for disabling the firewall functionality.
-    Put in temporarily until Driver changes are integrated when
-    this will come in from there.
-    """
-
-    def create_firewall(self, apply_list, firewall):
-        pass
-
-    def delete_firewall(self, apply_list, firewall):
-        pass
-
-    def update_firewall(self, apply_list, firewall):
-        pass
-
-    def apply_default_policy(self, apply_list):
-        pass
diff --git a/neutron/services/firewall/drivers/__init__.py b/neutron/services/firewall/drivers/__init__.py
new file mode 100644 (file)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# 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.
diff --git a/neutron/services/firewall/drivers/fwaas_base.py b/neutron/services/firewall/drivers/fwaas_base.py
new file mode 100644 (file)
index 0000000..66bf867
--- /dev/null
@@ -0,0 +1,119 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Dell 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.
+#
+# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc.
+
+import abc
+
+
+class FwaasDriverBase(object):
+    """Firewall as a Service Driver base class.
+
+    Using FwaasDriver Class, an instance of L3 perimeter Firewall
+    can be created. The firewall co-exists with the L3 agent.
+
+    One instance is created for each tenant. One firewall policy
+    is associated with each tenant (in the Havana release).
+
+    The Firewall can be visualized as having two zones (in Havana
+    release), trusted and untrusted.
+
+    All the 'internal' interfaces of Neutron Router is treated as trusted. The
+    interface connected to 'external network' is treated as untrusted.
+
+    The policy is applied on traffic ingressing/egressing interfaces on
+    the trusted zone. This implies that policy will be applied for traffic
+    passing from
+        - trusted to untrusted zones
+        - untrusted to trusted zones
+        - trusted to trusted zones
+
+    Policy WILL NOT be applied for traffic from untrusted to untrusted zones.
+    This is not a problem in Havana release as there is only one interface
+    connected to external network.
+
+    Since the policy is applied on the internal interfaces, the traffic
+    will be not be NATed to floating IP. For incoming traffic, the
+    traffic will get NATed to internal IP address before it hits
+    the firewall rules. So, while writing the rules, care should be
+    taken if using rules based on floating IP.
+
+    The firewall rule addition/deletion/insertion/update are done by the
+    management console. When the policy is sent to the driver, the complete
+    policy is sent and the whole policy has to be applied atomically. The
+    firewall rules will not get updated individually. This is to avoid problems
+    related to out-of-order notifications or inconsistent behaviour by partial
+    application of rules.
+    """
+
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def create_firewall(self, apply_list, firewall):
+        """Create the Firewall with default (drop all) policy.
+
+        The default policy will be applied on all the interfaces of
+        trusted zone.
+        """
+        pass
+
+    @abc.abstractmethod
+    def delete_firewall(self, apply_list, firewall):
+        """Delete firewall.
+
+        Removes all policies created by this instance and frees up
+        all the resources.
+        """
+        pass
+
+    @abc.abstractmethod
+    def update_firewall(self, apply_list, firewall):
+        """Apply the policy on all trusted interfaces.
+
+        Remove previous policy and apply the new policy on all trusted
+        interfaces.
+        """
+        pass
+
+    @abc.abstractmethod
+    def apply_default_policy(self, apply_list, firewall):
+        """Apply the default policy on all trusted interfaces.
+
+        Remove current policy and apply the default policy on all trusted
+        interfaces.
+        """
+        pass
+
+
+class NoopFwaasDriver(FwaasDriverBase):
+    """Noop Fwaas Driver.
+
+    Firewall driver which does nothing.
+    This driver is for disabling Fwaas functionality.
+    """
+
+    def create_firewall(self, apply_list, firewall):
+        pass
+
+    def delete_firewall(self, apply_list, firewall):
+        pass
+
+    def update_firewall(self, apply_list, firewall):
+        pass
+
+    def apply_default_policy(self, apply_list, firewall):
+        pass
diff --git a/neutron/services/firewall/drivers/linux/__init__.py b/neutron/services/firewall/drivers/linux/__init__.py
new file mode 100644 (file)
index 0000000..5e8da71
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation.
+# 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.
diff --git a/neutron/services/firewall/drivers/linux/iptables_fwaas.py b/neutron/services/firewall/drivers/linux/iptables_fwaas.py
new file mode 100644 (file)
index 0000000..eaed8b0
--- /dev/null
@@ -0,0 +1,279 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Dell 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.
+#
+# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc.
+
+from neutron.agent.linux import iptables_manager
+from neutron.extensions import firewall as fw_ext
+from neutron.openstack.common import log as logging
+from neutron.services.firewall.drivers import fwaas_base
+
+LOG = logging.getLogger(__name__)
+FWAAS_DRIVER_NAME = 'Fwaas iptables driver'
+FWAAS_CHAIN = 'fwaas'
+FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy'
+INGRESS_DIRECTION = 'ingress'
+EGRESS_DIRECTION = 'egress'
+CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i',
+                     EGRESS_DIRECTION: 'o'}
+
+""" Firewall rules are applied on internal-interfaces of Neutron router.
+    The packets ingressing tenant's network will be on the output
+    direction on internal-interfaces.
+"""
+IPTABLES_DIR = {INGRESS_DIRECTION: '-o',
+                EGRESS_DIRECTION: '-i'}
+IPV4 = 'ipv4'
+IPV6 = 'ipv6'
+IP_VER_TAG = {IPV4: 'v4',
+              IPV6: 'v6'}
+
+
+class IptablesFwaasDriver(fwaas_base.FwaasDriverBase):
+    """IPTables driver for Firewall As A Service."""
+
+    def __init__(self):
+        LOG.debug(_("Initializing fwaas iptables driver"))
+
+    def create_firewall(self, apply_list, firewall):
+        LOG.debug(_('Creating firewall %(fw_id)s for tenant %(tid)s)'),
+                  {'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
+        try:
+            if firewall['admin_state_up']:
+                self._setup_firewall(apply_list, firewall)
+            else:
+                self.apply_default_policy(apply_list, firewall)
+        except (LookupError, RuntimeError):
+            # catch known library exceptions and raise Fwaas generic exception
+            LOG.exception(_("Failed to create firewall: %s"), firewall['id'])
+            raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
+
+    def delete_firewall(self, apply_list, firewall):
+        LOG.debug(_('Deleting firewall %(fw_id)s for tenant %(tid)s)'),
+                  {'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
+        fwid = firewall['id']
+        try:
+            for router_info in apply_list:
+                ipt_mgr = router_info.iptables_manager
+                self._remove_chains(fwid, ipt_mgr)
+                self._remove_default_chains(ipt_mgr)
+                ipt_mgr.apply()
+        except (LookupError, RuntimeError):
+            # catch known library exceptions and raise Fwaas generic exception
+            LOG.exception(_("Failed to delete firewall: %s"), fwid)
+            raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
+
+    def update_firewall(self, apply_list, firewall):
+        LOG.debug(_('Updating firewall %(fw_id)s for tenant %(tid)s)'),
+                  {'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
+        try:
+            if firewall['admin_state_up']:
+                self._setup_firewall(apply_list, firewall)
+            else:
+                self.apply_default_policy(apply_list, firewall)
+        except (LookupError, RuntimeError):
+            # catch known library exceptions and raise Fwaas generic exception
+            LOG.exception(_("Failed to update firewall: %s"), firewall['id'])
+            raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
+
+    def apply_default_policy(self, apply_list, firewall):
+        LOG.debug(_('Applying firewall %(fw_id)s for tenant %(tid)s)'),
+                  {'fw_id': firewall['id'], 'tid': firewall['tenant_id']})
+        fwid = firewall['id']
+        try:
+            for router_info in apply_list:
+                ipt_mgr = router_info.iptables_manager
+
+                # the following only updates local memory; no hole in FW
+                self._remove_chains(fwid, ipt_mgr)
+                self._remove_default_chains(ipt_mgr)
+
+                # create default 'DROP ALL' policy chain
+                self._add_default_policy_chain_v4v6(ipt_mgr)
+                self._enable_policy_chain(fwid, ipt_mgr)
+
+                # apply the changes
+                ipt_mgr.apply()
+        except (LookupError, RuntimeError):
+            # catch known library exceptions and raise Fwaas generic exception
+            LOG.exception(_("Failed to apply default policy on firewall: %s"),
+                          fwid)
+            raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME)
+
+    def _setup_firewall(self, apply_list, firewall):
+        fwid = firewall['id']
+        for router_info in apply_list:
+            ipt_mgr = router_info.iptables_manager
+
+            # the following only updates local memory; no hole in FW
+            self._remove_chains(fwid, ipt_mgr)
+            self._remove_default_chains(ipt_mgr)
+
+            # create default 'DROP ALL' policy chain
+            self._add_default_policy_chain_v4v6(ipt_mgr)
+            #create chain based on configured policy
+            self._setup_chains(firewall, ipt_mgr)
+
+            # apply the changes
+            ipt_mgr.apply()
+
+    def _get_chain_name(self, fwid, ver, direction):
+        return '%s%s%s' % (CHAIN_NAME_PREFIX[direction],
+                           IP_VER_TAG[ver],
+                           fwid)
+
+    def _setup_chains(self, firewall, ipt_mgr):
+        """Create Fwaas chain using the rules in the policy
+        """
+        fw_rules_list = firewall['firewall_rule_list']
+        fwid = firewall['id']
+
+        #default rules for invalid packets and established sessions
+        invalid_rule = self._drop_invalid_packets_rule()
+        est_rule = self._allow_established_rule()
+
+        for ver in [IPV4, IPV6]:
+            if ver == IPV4:
+                table = ipt_mgr.ipv4['filter']
+            else:
+                table = ipt_mgr.ipv6['filter']
+            ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
+            ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
+            for name in [ichain_name, ochain_name]:
+                table.add_chain(name)
+                table.add_rule(name, invalid_rule)
+                table.add_rule(name, est_rule)
+
+        for rule in fw_rules_list:
+            if not rule['enabled']:
+                continue
+            iptbl_rule = self._convert_fwaas_to_iptables_rule(rule)
+            if rule['ip_version'] == 4:
+                ver = IPV4
+                table = ipt_mgr.ipv4['filter']
+            else:
+                ver = IPV6
+                table = ipt_mgr.ipv6['filter']
+            ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION)
+            ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION)
+            table.add_rule(ichain_name, iptbl_rule)
+            table.add_rule(ochain_name, iptbl_rule)
+        self._enable_policy_chain(fwid, ipt_mgr)
+
+    def _remove_default_chains(self, nsid):
+        """Remove fwaas default policy chain."""
+        self._remove_chain_by_name(IPV4, FWAAS_DEFAULT_CHAIN, nsid)
+        self._remove_chain_by_name(IPV6, FWAAS_DEFAULT_CHAIN, nsid)
+
+    def _remove_chains(self, fwid, ipt_mgr):
+        """Remove fwaas policy chain."""
+        for ver in [IPV4, IPV6]:
+            for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
+                chain_name = self._get_chain_name(fwid, ver, direction)
+                self._remove_chain_by_name(ver, chain_name, ipt_mgr)
+
+    def _add_default_policy_chain_v4v6(self, ipt_mgr):
+        ipt_mgr.ipv4['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
+        ipt_mgr.ipv4['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
+        ipt_mgr.ipv6['filter'].add_chain(FWAAS_DEFAULT_CHAIN)
+        ipt_mgr.ipv6['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP')
+
+    def _remove_chain_by_name(self, ver, chain_name, ipt_mgr):
+        if ver == IPV4:
+            ipt_mgr.ipv4['filter'].ensure_remove_chain(chain_name)
+        else:
+            ipt_mgr.ipv6['filter'].ensure_remove_chain(chain_name)
+
+    def _add_rules_to_chain(self, ipt_mgr, ver, chain_name, rules):
+        if ver == IPV4:
+            table = ipt_mgr.ipv4['filter']
+        else:
+            table = ipt_mgr.ipv6['filter']
+        for rule in rules:
+            table.add_rule(chain_name, rule)
+
+    def _enable_policy_chain(self, fwid, ipt_mgr):
+        bname = iptables_manager.binary_name
+
+        for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']),
+                           (IPV6, ipt_mgr.ipv4['filter'])]:
+            for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]:
+                chain_name = self._get_chain_name(fwid, ver, direction)
+                chain_name = iptables_manager.get_chain_name(chain_name)
+                if chain_name in tbl.chains:
+                    jump_rule = ['%s qr-+ -j %s-%s' % (IPTABLES_DIR[direction],
+                                                       bname, chain_name)]
+                    self._add_rules_to_chain(ipt_mgr, ver, 'FORWARD',
+                                             jump_rule)
+
+        #jump to DROP_ALL policy
+        chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
+        jump_rule = ['-o qr-+ -j %s-%s' % (bname, chain_name)]
+        self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
+        self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
+
+        #jump to DROP_ALL policy
+        chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN)
+        jump_rule = ['-i qr-+ -j %s-%s' % (bname, chain_name)]
+        self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule)
+        self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule)
+
+    def _convert_fwaas_to_iptables_rule(self, rule):
+        if rule.get('action') == 'allow':
+            rule['action'] = 'ACCEPT'
+        else:
+            rule['action'] = 'DROP'
+
+        args = [self._protocol_arg(rule.get('protocol')),
+                self._port_arg('dport',
+                               rule.get('protocol'),
+                               rule.get('destination_port')),
+                self._port_arg('sport',
+                               rule.get('protocol'),
+                               rule.get('source_port')),
+                self._ip_prefix_arg('s', rule.get('source_ip_address')),
+                self._ip_prefix_arg('d', rule.get('destination_ip_address')),
+                self._action_arg(rule.get('action'))]
+
+        iptables_rule = ' '.join(args)
+        return iptables_rule
+
+    def _drop_invalid_packets_rule(self):
+        return '-m state --state INVALID -j DROP'
+
+    def _allow_established_rule(self):
+        return '-m state --state ESTABLISHED,RELATED -j ACCEPT'
+
+    def _action_arg(self, action):
+        if action:
+            return '-j %s' % action
+        return ''
+
+    def _protocol_arg(self, protocol):
+        if protocol:
+            return '-p %s' % protocol
+        return ''
+
+    def _port_arg(self, direction, protocol, port):
+        if not (protocol in ['udp', 'tcp'] and port):
+            return ''
+        return '--%s %s' % (direction, port)
+
+    def _ip_prefix_arg(self, direction, ip_prefix):
+        if ip_prefix:
+            return '-%s %s' % (direction, ip_prefix)
+        return ''
diff --git a/neutron/tests/unit/services/firewall/drivers/__init__.py b/neutron/tests/unit/services/firewall/drivers/__init__.py
new file mode 100644 (file)
index 0000000..cae279d
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
diff --git a/neutron/tests/unit/services/firewall/drivers/linux/__init__.py b/neutron/tests/unit/services/firewall/drivers/linux/__init__.py
new file mode 100644 (file)
index 0000000..cae279d
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
diff --git a/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py b/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py
new file mode 100644 (file)
index 0000000..2550bf1
--- /dev/null
@@ -0,0 +1,199 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Dell 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.
+#
+# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc.
+
+import mock
+from mock import call
+from oslo.config import cfg
+
+from neutron.agent.common import config as a_cfg
+import neutron.services.firewall.drivers.linux.iptables_fwaas as fwaas
+from neutron.tests import base
+from neutron.tests.unit import test_api_v2
+
+
+_uuid = test_api_v2._uuid
+FAKE_SRC_PREFIX = '10.0.0.0/24'
+FAKE_DST_PREFIX = '20.0.0.0/24'
+FAKE_PROTOCOL = 'tcp'
+FAKE_SRC_PORT = 5000
+FAKE_DST_PORT = 22
+FAKE_FW_ID = 'fake-fw-uuid'
+
+
+class IptablesFwaasTestCase(base.BaseTestCase):
+    def setUp(self):
+        super(IptablesFwaasTestCase, self).setUp()
+        cfg.CONF.register_opts(a_cfg.ROOT_HELPER_OPTS, 'AGENT')
+        self.utils_exec_p = mock.patch(
+            'neutron.agent.linux.utils.execute')
+        self.utils_exec = self.utils_exec_p.start()
+        self.addCleanup(self.utils_exec_p.stop)
+        self.iptables_cls_p = mock.patch(
+            'neutron.agent.linux.iptables_manager.IptablesManager')
+        iptables_cls = self.iptables_cls_p.start()
+        self.addCleanup(self.iptables_cls_p.stop)
+        self.iptables_inst = mock.Mock()
+        self.v4filter_inst = mock.Mock()
+        self.v6filter_inst = mock.Mock()
+        self.v4filter_inst.chains = []
+        self.v6filter_inst.chains = []
+        self.iptables_inst.ipv4 = {'filter': self.v4filter_inst}
+        self.iptables_inst.ipv6 = {'filter': self.v6filter_inst}
+        iptables_cls.return_value = self.iptables_inst
+
+        self.router_info_inst = mock.Mock()
+        self.router_info_inst.iptables_manager = self.iptables_inst
+
+        self.firewall = fwaas.IptablesFwaasDriver()
+
+    def _fake_rules_v4(self, fwid):
+        rule_list = []
+        rule1 = {'enabled': True,
+                 'action': 'allow',
+                 'ip_version': 4,
+                 'protocol': 'tcp',
+                 'destination_port': '80',
+                 'source_ip_address': '10.24.4.2'}
+        rule2 = {'enabled': True,
+                 'action': 'deny',
+                 'ip_version': 4,
+                 'protocol': 'tcp',
+                 'destination_port': '22'}
+        ingress_chain = ('iv4%s' % fwid)[:11]
+        self.v4filter_inst.chains.append(ingress_chain)
+        egress_chain = ('ov4%s' % fwid)[:11]
+        self.v4filter_inst.chains.append(egress_chain)
+        rule_list.append(rule1)
+        rule_list.append(rule2)
+        return rule_list
+
+    def _fake_firewall_no_rule(self):
+        rule_list = []
+        fw_inst = {'id': FAKE_FW_ID,
+                   'admin_state_up': True,
+                   'tenant_id': 'tenant-uuid',
+                   'firewall_rule_list': rule_list}
+        return fw_inst
+
+    def _fake_firewall(self, rule_list):
+        fw_inst = {'id': FAKE_FW_ID,
+                   'admin_state_up': True,
+                   'tenant_id': 'tenant-uuid',
+                   'firewall_rule_list': rule_list}
+        return fw_inst
+
+    def _fake_firewall_with_admin_down(self, rule_list):
+        fw_inst = {'id': FAKE_FW_ID,
+                   'admin_state_up': False,
+                   'tenant_id': 'tenant-uuid',
+                   'firewall_rule_list': rule_list}
+        return fw_inst
+
+    def _fake_apply_list(self):
+        apply_list = []
+        apply_list.append(self.router_info_inst)
+        return apply_list
+
+    def _setup_firewall_with_rules(self, func):
+        apply_list = self._fake_apply_list()
+        rule_list = self._fake_rules_v4(FAKE_FW_ID)
+        firewall = self._fake_firewall(rule_list)
+        func(apply_list, firewall)
+        invalid_rule = '-m state --state INVALID -j DROP'
+        est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
+        rule1 = '-p tcp --dport 80  -s 10.24.4.2  -j ACCEPT'
+        rule2 = '-p tcp --dport 22    -j DROP'
+        ingress_chain = 'iv4%s' % firewall['id']
+        egress_chain = 'ov4%s' % firewall['id']
+        bname = fwaas.iptables_manager.binary_name
+        ipt_mgr_ichain = '%s-%s' % (bname, ingress_chain[:11])
+        ipt_mgr_echain = '%s-%s' % (bname, egress_chain[:11])
+        calls = [call.ensure_remove_chain('iv4fake-fw-uuid'),
+                 call.ensure_remove_chain('ov4fake-fw-uuid'),
+                 call.ensure_remove_chain('fwaas-default-policy'),
+                 call.add_chain('fwaas-default-policy'),
+                 call.add_rule('fwaas-default-policy', '-j DROP'),
+                 call.add_chain(ingress_chain),
+                 call.add_rule(ingress_chain, invalid_rule),
+                 call.add_rule(ingress_chain, est_rule),
+                 call.add_chain(egress_chain),
+                 call.add_rule(egress_chain, invalid_rule),
+                 call.add_rule(egress_chain, est_rule),
+                 call.add_rule(ingress_chain, rule1),
+                 call.add_rule(egress_chain, rule1),
+                 call.add_rule(ingress_chain, rule2),
+                 call.add_rule(egress_chain, rule2),
+                 call.add_rule('FORWARD', '-o qr-+ -j %s' % ipt_mgr_ichain),
+                 call.add_rule('FORWARD', '-i qr-+ -j %s' % ipt_mgr_echain),
+                 call.add_rule('FORWARD', '-o qr-+ -j %s-fwaas-defau' % bname),
+                 call.add_rule('FORWARD', '-i qr-+ -j %s-fwaas-defau' % bname)]
+        self.v4filter_inst.assert_has_calls(calls)
+
+    def test_create_firewall_no_rules(self):
+        apply_list = self._fake_apply_list()
+        firewall = self._fake_firewall_no_rule()
+        self.firewall.create_firewall(apply_list, firewall)
+        invalid_rule = '-m state --state INVALID -j DROP'
+        est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT'
+        ingress_chain = ('iv4%s' % firewall['id'])
+        egress_chain = ('ov4%s' % firewall['id'])
+        bname = fwaas.iptables_manager.binary_name
+        calls = [call.ensure_remove_chain('iv4fake-fw-uuid'),
+                 call.ensure_remove_chain('ov4fake-fw-uuid'),
+                 call.ensure_remove_chain('fwaas-default-policy'),
+                 call.add_chain('fwaas-default-policy'),
+                 call.add_rule('fwaas-default-policy', '-j DROP'),
+                 call.add_chain(ingress_chain),
+                 call.add_rule(ingress_chain, invalid_rule),
+                 call.add_rule(ingress_chain, est_rule),
+                 call.add_chain(egress_chain),
+                 call.add_rule(egress_chain, invalid_rule),
+                 call.add_rule(egress_chain, est_rule),
+                 call.add_rule('FORWARD', '-o qr-+ -j %s-fwaas-defau' % bname),
+                 call.add_rule('FORWARD', '-i qr-+ -j %s-fwaas-defau' % bname)]
+        self.v4filter_inst.assert_has_calls(calls)
+
+    def test_create_firewall_with_rules(self):
+        self._setup_firewall_with_rules(self.firewall.create_firewall)
+
+    def test_update_firewall_with_rules(self):
+        self._setup_firewall_with_rules(self.firewall.update_firewall)
+
+    def test_delete_firewall(self):
+        apply_list = self._fake_apply_list()
+        firewall = self._fake_firewall_no_rule()
+        self.firewall.delete_firewall(apply_list, firewall)
+        ingress_chain = 'iv4%s' % firewall['id']
+        egress_chain = 'ov4%s' % firewall['id']
+        calls = [call.ensure_remove_chain(ingress_chain),
+                 call.ensure_remove_chain(egress_chain),
+                 call.ensure_remove_chain('fwaas-default-policy')]
+        self.v4filter_inst.assert_has_calls(calls)
+
+    def test_create_firewall_with_admin_down(self):
+        rule_list = self._fake_rules_v4(FAKE_FW_ID)
+        apply_list = self._fake_apply_list()
+        firewall = self._fake_firewall_with_admin_down(rule_list)
+        self.firewall.create_firewall(apply_list, firewall)
+        calls = [call.ensure_remove_chain('iv4fake-fw-uuid'),
+                 call.ensure_remove_chain('ov4fake-fw-uuid'),
+                 call.ensure_remove_chain('fwaas-default-policy'),
+                 call.add_chain('fwaas-default-policy'),
+                 call.add_rule('fwaas-default-policy', '-j DROP')]
+        self.v4filter_inst.assert_has_calls(calls)