# Copyright 2012 Locaweb. # 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. import inspect import os import mock from oslo.config import cfg from neutron.agent.common import config as a_cfg from neutron.agent.linux import iptables_comments as ic from neutron.agent.linux import iptables_manager from neutron.tests import base from neutron.tests import tools IPTABLES_ARG = {'bn': iptables_manager.binary_name, 'snat_out_comment': ic.SNAT_OUT} NAT_DUMP = ('# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n' '[0:0] -A %(bn)s-snat -j ' '%(bn)s-float-snat\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) FILTER_DUMP = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) COMMENTED_NAT_DUMP = ( '# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat ' '-m comment --comment "%(snat_out_comment)s"\n' '[0:0] -A %(bn)s-snat -j ' '%(bn)s-float-snat\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) class IptablesCommentsTestCase(base.BaseTestCase): def setUp(self): super(IptablesCommentsTestCase, self).setUp() cfg.CONF.register_opts(a_cfg.IPTABLES_OPTS, 'AGENT') cfg.CONF.set_override('comment_iptables_rules', True, 'AGENT') self.root_helper = 'sudo' self.iptables = (iptables_manager. IptablesManager(root_helper=self.root_helper)) self.execute = mock.patch.object(self.iptables, "execute").start() def test_comments_short_enough(self): for attr in dir(ic): if not attr.startswith('__') and len(getattr(ic, attr)) > 255: self.fail("Iptables comment %s is longer than 255 characters." % attr) def test_add_filter_rule(self): filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' '[0:0] -A %(bn)s-filter -j DROP\n' '[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j ' '%(bn)s-filter\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) raw_dump = _generate_raw_dump(IPTABLES_ARG) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=( raw_dump + COMMENTED_NAT_DUMP + filter_dump_mod), root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=( raw_dump + COMMENTED_NAT_DUMP + FILTER_DUMP), root_helper=self.root_helper ), None), ] tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain('filter') self.iptables.ipv4['filter'].add_rule('filter', '-j DROP') self.iptables.ipv4['filter'].add_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' %(bn)s-filter' % IPTABLES_ARG) self.iptables.apply() self.iptables.ipv4['filter'].remove_rule('filter', '-j DROP') self.iptables.ipv4['filter'].remove_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' %(bn)s-filter' % IPTABLES_ARG) self.iptables.ipv4['filter'].remove_chain('filter') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def _generate_raw_dump(iptables_args): return ('# Generated by iptables_manager\n' '*raw\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) RAW_DUMP = _generate_raw_dump(IPTABLES_ARG) class IptablesManagerStateFulTestCase(base.BaseTestCase): def setUp(self): super(IptablesManagerStateFulTestCase, self).setUp() cfg.CONF.register_opts(a_cfg.IPTABLES_OPTS, 'AGENT') cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT') self.root_helper = 'sudo' self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper) self.execute = mock.patch.object(self.iptables, "execute").start() def test_binary_name(self): self.assertEqual(iptables_manager.binary_name, os.path.basename(inspect.stack()[-1][1])[:16]) def test_get_chain_name(self): name = '0123456789' * 5 # 28 chars is the maximum length of iptables chain name. self.assertEqual(iptables_manager.get_chain_name(name, wrap=False), name[:28]) # 11 chars is the maximum length of chain name of iptable_manager # if binary_name is prepended. self.assertEqual(iptables_manager.get_chain_name(name, wrap=True), name[:11]) def _extend_with_ip6tables_filter(self, expected_calls, filter_dump): expected_calls.insert(2, ( mock.call(['ip6tables-save', '-c'], root_helper=self.root_helper), '')) expected_calls.insert(3, ( mock.call(['ip6tables-restore', '-c'], process_input=filter_dump, root_helper=self.root_helper), None)) expected_calls.extend([ (mock.call(['ip6tables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['ip6tables-restore', '-c'], process_input=filter_dump, root_helper=self.root_helper), None)]) def _test_add_and_remove_chain_custom_binary_name_helper(self, use_ipv6): bn = ("abcdef" * 5) self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, binary_name=bn, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() iptables_args = {'bn': bn[:16]} filter_dump = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) filter_dump_ipv6 = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) nat_dump = ('# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n' '[0:0] -A %(bn)s-snat -j ' '%(bn)s-float-snat\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) raw_dump = _generate_raw_dump(iptables_args) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=raw_dump + nat_dump + filter_dump_mod, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=raw_dump + nat_dump + filter_dump, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, filter_dump_ipv6) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain('filter') self.iptables.apply() self.iptables.ipv4['filter'].empty_chain('filter') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_add_and_remove_chain_custom_binary_name(self): self._test_add_and_remove_chain_custom_binary_name_helper(False) def test_add_and_remove_chain_custom_binary_name_with_ipv6(self): self._test_add_and_remove_chain_custom_binary_name_helper(True) def _test_empty_chain_custom_binary_name_helper(self, use_ipv6): bn = ("abcdef" * 5)[:16] self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, binary_name=bn, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() iptables_args = {'bn': bn} filter_dump = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' '[0:0] -A %(bn)s-filter -s 0/0 -d 192.168.0.2\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) nat_dump = ('# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n' '[0:0] -A %(bn)s-snat -j ' '%(bn)s-float-snat\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) raw_dump = _generate_raw_dump(iptables_args) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=raw_dump + nat_dump + filter_dump_mod, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=raw_dump + nat_dump + filter_dump, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, filter_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain('filter') self.iptables.ipv4['filter'].add_rule('filter', '-s 0/0 -d 192.168.0.2') self.iptables.apply() self.iptables.ipv4['filter'].remove_chain('filter') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_empty_chain_custom_binary_name(self): self._test_empty_chain_custom_binary_name_helper(False) def test_empty_chain_custom_binary_name_with_ipv6(self): self._test_empty_chain_custom_binary_name_helper(True) def _test_add_and_remove_chain_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + filter_dump_mod, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + FILTER_DUMP, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, FILTER_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain('filter') self.iptables.apply() self.iptables.ipv4['filter'].remove_chain('filter') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_add_and_remove_chain(self): self._test_add_and_remove_chain_helper(False) def test_add_and_remove_chain_with_ipv6(self): self._test_add_and_remove_chain_helper(True) def _test_add_filter_rule_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-filter - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' '[0:0] -A %(bn)s-filter -j DROP\n' '[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j ' '%(bn)s-filter\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + filter_dump_mod, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + FILTER_DUMP, root_helper=self.root_helper ), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, FILTER_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain('filter') self.iptables.ipv4['filter'].add_rule('filter', '-j DROP') self.iptables.ipv4['filter'].add_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' %(bn)s-filter' % IPTABLES_ARG) self.iptables.apply() self.iptables.ipv4['filter'].remove_rule('filter', '-j DROP') self.iptables.ipv4['filter'].remove_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' %(bn)s-filter' % IPTABLES_ARG) self.iptables.ipv4['filter'].remove_chain('filter') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_add_filter_rule(self): self._test_add_filter_rule_helper(False) def test_add_filter_rule_with_ipv6(self): self._test_add_filter_rule_helper(True) def _test_rule_with_wrap_target_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() name = '0123456789' * 5 wrap = "%s-%s" % (iptables_manager.binary_name, iptables_manager.get_chain_name(name)) iptables_args = {'bn': iptables_manager.binary_name, 'wrap': wrap} filter_dump_mod = ('# Generated by iptables_manager\n' '*filter\n' ':neutron-filter-top - [0:0]\n' ':%(wrap)s - [0:0]\n' ':%(bn)s-FORWARD - [0:0]\n' ':%(bn)s-INPUT - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-local - [0:0]\n' '[0:0] -A FORWARD -j neutron-filter-top\n' '[0:0] -A OUTPUT -j neutron-filter-top\n' '[0:0] -A neutron-filter-top -j %(bn)s-local\n' '[0:0] -A INPUT -j %(bn)s-INPUT\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A FORWARD -j %(bn)s-FORWARD\n' '[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j ' '%(wrap)s\n' 'COMMIT\n' '# Completed by iptables_manager\n' % iptables_args) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + filter_dump_mod, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + FILTER_DUMP, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, FILTER_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['filter'].add_chain(name) self.iptables.ipv4['filter'].add_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' $%s' % name) self.iptables.apply() self.iptables.ipv4['filter'].remove_rule('INPUT', '-s 0/0 -d 192.168.0.2 -j' ' $%s' % name) self.iptables.ipv4['filter'].remove_chain(name) self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_rule_with_wrap_target(self): self._test_rule_with_wrap_target_helper(False) def test_rule_with_wrap_target_with_ipv6(self): self._test_rule_with_wrap_target_helper(True) def _test_add_nat_rule_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() nat_dump = ('# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n' '[0:0] -A %(bn)s-snat -j %(bn)s-float-snat\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) nat_dump_mod = ('# Generated by iptables_manager\n' '*nat\n' ':neutron-postrouting-bottom - [0:0]\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-POSTROUTING - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-float-snat - [0:0]\n' ':%(bn)s-nat - [0:0]\n' ':%(bn)s-snat - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n' '[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n' '[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n' '[0:0] -A %(bn)s-snat -j %(bn)s-float-snat\n' '[0:0] -A %(bn)s-PREROUTING -d 192.168.0.3 -j ' '%(bn)s-nat\n' '[0:0] -A %(bn)s-nat -p tcp --dport 8080 -j ' 'REDIRECT --to-port 80\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + nat_dump_mod + FILTER_DUMP, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + nat_dump + FILTER_DUMP, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, FILTER_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['nat'].add_chain('nat') self.iptables.ipv4['nat'].add_rule('PREROUTING', '-d 192.168.0.3 -j ' '%(bn)s-nat' % IPTABLES_ARG) self.iptables.ipv4['nat'].add_rule('nat', '-p tcp --dport 8080' + ' -j REDIRECT --to-port 80') self.iptables.apply() self.iptables.ipv4['nat'].remove_rule('nat', '-p tcp --dport 8080 -j' ' REDIRECT --to-port 80') self.iptables.ipv4['nat'].remove_rule('PREROUTING', '-d 192.168.0.3 -j ' '%(bn)s-nat' % IPTABLES_ARG) self.iptables.ipv4['nat'].remove_chain('nat') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_add_nat_rule(self): self._test_add_nat_rule_helper(False) def test_add_nat_rule_with_ipv6(self): self._test_add_nat_rule_helper(True) def _test_add_raw_rule_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() raw_dump_mod = ('# Generated by iptables_manager\n' '*raw\n' ':%(bn)s-OUTPUT - [0:0]\n' ':%(bn)s-PREROUTING - [0:0]\n' ':%(bn)s-raw - [0:0]\n' '[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n' '[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n' '[0:0] -A %(bn)s-PREROUTING -j CT --notrack\n' 'COMMIT\n' '# Completed by iptables_manager\n' % IPTABLES_ARG) expected_calls_and_values = [ (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=raw_dump_mod + NAT_DUMP + FILTER_DUMP, root_helper=self.root_helper), None), (mock.call(['iptables-save', '-c'], root_helper=self.root_helper), ''), (mock.call(['iptables-restore', '-c'], process_input=RAW_DUMP + NAT_DUMP + FILTER_DUMP, root_helper=self.root_helper), None), ] if use_ipv6: self._extend_with_ip6tables_filter(expected_calls_and_values, FILTER_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) self.iptables.ipv4['raw'].add_chain('raw') self.iptables.ipv4['raw'].add_rule('PREROUTING', '-j CT --notrack') self.iptables.apply() self.iptables.ipv4['raw'].remove_rule('PREROUTING', '-j CT --notrack') self.iptables.ipv4['raw'].remove_chain('raw') self.iptables.apply() tools.verify_mock_calls(self.execute, expected_calls_and_values) def test_add_raw_rule(self): self._test_add_raw_rule_helper(False) def test_add_raw_rule_with_ipv6(self): self._test_add_raw_rule_helper(True) def test_add_rule_to_a_nonexistent_chain(self): self.assertRaises(LookupError, self.iptables.ipv4['filter'].add_rule, 'nonexistent', '-j DROP') def test_remove_nonexistent_chain(self): with mock.patch.object(iptables_manager, "LOG") as log: self.iptables.ipv4['filter'].remove_chain('nonexistent') log.warn.assert_called_once_with( 'Attempted to remove chain %s which does not exist', 'nonexistent') def test_remove_nonexistent_rule(self): with mock.patch.object(iptables_manager, "LOG") as log: self.iptables.ipv4['filter'].remove_rule('nonexistent', '-j DROP') log.warn.assert_called_once_with( 'Tried to remove rule that was not there: ' '%(chain)r %(rule)r %(wrap)r %(top)r', {'wrap': True, 'top': False, 'rule': '-j DROP', 'chain': 'nonexistent'}) def test_iptables_failure_with_no_failing_line_number(self): with mock.patch.object(iptables_manager, "LOG") as log: # generate Runtime errors on iptables-restore calls def iptables_restore_failer(*args, **kwargs): if 'iptables-restore' in args[0]: self.input_lines = kwargs['process_input'].split('\n') # don't provide a specific failure message so all lines # are logged raise RuntimeError() return FILTER_DUMP self.execute.side_effect = iptables_restore_failer # _apply_synchronized calls iptables-restore so it should raise # a RuntimeError self.assertRaises(RuntimeError, self.iptables._apply_synchronized) # The RuntimeError should have triggered a log of the input to the # process that it failed to execute. Verify by comparing the log # call to the 'process_input' arg given to the failed iptables-restore # call. # Failure without a specific line number in the error should cause # all lines to be logged with numbers. logged = ['%7d. %s' % (n, l) for n, l in enumerate(self.input_lines, 1)] log.error.assert_called_once_with(_( 'IPTablesManager.apply failed to apply the ' 'following set of iptables rules:\n%s'), '\n'.join(logged) ) def test_iptables_failure_on_specific_line(self): with mock.patch.object(iptables_manager, "LOG") as log: # generate Runtime errors on iptables-restore calls def iptables_restore_failer(*args, **kwargs): if 'iptables-restore' in args[0]: self.input_lines = kwargs['process_input'].split('\n') # pretend line 11 failed msg = ("Exit code: 1\nStdout: ''\n" "Stderr: 'iptables-restore: line 11 failed\n'") raise RuntimeError(msg) return FILTER_DUMP self.execute.side_effect = iptables_restore_failer # _apply_synchronized calls iptables-restore so it should raise # a RuntimeError self.assertRaises(RuntimeError, self.iptables._apply_synchronized) # The RuntimeError should have triggered a log of the input to the # process that it failed to execute. Verify by comparing the log # call to the 'process_input' arg given to the failed iptables-restore # call. # Line 11 of the input was marked as failing so lines (11 - context) # to (11 + context) should be logged ctx = iptables_manager.IPTABLES_ERROR_LINES_OF_CONTEXT log_start = max(0, 11 - ctx) log_end = 11 + ctx logged = ['%7d. %s' % (n, l) for n, l in enumerate(self.input_lines[log_start:log_end], log_start + 1)] log.error.assert_called_once_with(_( 'IPTablesManager.apply failed to apply the ' 'following set of iptables rules:\n%s'), '\n'.join(logged) ) def test_get_traffic_counters_chain_notexists(self): with mock.patch.object(iptables_manager, "LOG") as log: acc = self.iptables.get_traffic_counters('chain1') self.assertIsNone(acc) self.assertEqual(0, self.execute.call_count) log.warn.assert_called_once_with( 'Attempted to get traffic counters of chain %s which ' 'does not exist', 'chain1') def _test_get_traffic_counters_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() exp_packets = 800 exp_bytes = 131802 iptables_dump = ( 'Chain OUTPUT (policy ACCEPT 400 packets, 65901 bytes)\n' ' pkts bytes target prot opt in out source' ' destination \n' ' 400 65901 chain1 all -- * * 0.0.0.0/0' ' 0.0.0.0/0 \n' ' 400 65901 chain2 all -- * * 0.0.0.0/0' ' 0.0.0.0/0 \n') expected_calls_and_values = [ (mock.call(['iptables', '-t', 'filter', '-L', 'OUTPUT', '-n', '-v', '-x'], root_helper=self.root_helper), iptables_dump), (mock.call(['iptables', '-t', 'raw', '-L', 'OUTPUT', '-n', '-v', '-x'], root_helper=self.root_helper), ''), (mock.call(['iptables', '-t', 'nat', '-L', 'OUTPUT', '-n', '-v', '-x'], root_helper=self.root_helper), ''), ] if use_ipv6: expected_calls_and_values.append( (mock.call(['ip6tables', '-t', 'filter', '-L', 'OUTPUT', '-n', '-v', '-x'], root_helper=self.root_helper), iptables_dump)) exp_packets *= 2 exp_bytes *= 2 tools.setup_mock_calls(self.execute, expected_calls_and_values) acc = self.iptables.get_traffic_counters('OUTPUT') self.assertEqual(acc['pkts'], exp_packets) self.assertEqual(acc['bytes'], exp_bytes) tools.verify_mock_calls(self.execute, expected_calls_and_values, any_order=True) def test_get_traffic_counters(self): self._test_get_traffic_counters_helper(False) def test_get_traffic_counters_with_ipv6(self): self._test_get_traffic_counters_helper(True) def _test_get_traffic_counters_with_zero_helper(self, use_ipv6): self.iptables = iptables_manager.IptablesManager( root_helper=self.root_helper, use_ipv6=use_ipv6) self.execute = mock.patch.object(self.iptables, "execute").start() exp_packets = 800 exp_bytes = 131802 iptables_dump = ( 'Chain OUTPUT (policy ACCEPT 400 packets, 65901 bytes)\n' ' pkts bytes target prot opt in out source' ' destination \n' ' 400 65901 chain1 all -- * * 0.0.0.0/0' ' 0.0.0.0/0 \n' ' 400 65901 chain2 all -- * * 0.0.0.0/0' ' 0.0.0.0/0 \n') expected_calls_and_values = [ (mock.call(['iptables', '-t', 'filter', '-L', 'OUTPUT', '-n', '-v', '-x', '-Z'], root_helper=self.root_helper), iptables_dump), (mock.call(['iptables', '-t', 'raw', '-L', 'OUTPUT', '-n', '-v', '-x', '-Z'], root_helper=self.root_helper), ''), (mock.call(['iptables', '-t', 'nat', '-L', 'OUTPUT', '-n', '-v', '-x', '-Z'], root_helper=self.root_helper), '') ] if use_ipv6: expected_calls_and_values.append( (mock.call(['ip6tables', '-t', 'filter', '-L', 'OUTPUT', '-n', '-v', '-x', '-Z'], root_helper=self.root_helper), iptables_dump)) exp_packets *= 2 exp_bytes *= 2 tools.setup_mock_calls(self.execute, expected_calls_and_values) acc = self.iptables.get_traffic_counters('OUTPUT', zero=True) self.assertEqual(acc['pkts'], exp_packets) self.assertEqual(acc['bytes'], exp_bytes) tools.verify_mock_calls(self.execute, expected_calls_and_values, any_order=True) def test_get_traffic_counters_with_zero(self): self._test_get_traffic_counters_with_zero_helper(False) def test_get_traffic_counters_with_zero_with_ipv6(self): self._test_get_traffic_counters_with_zero_helper(True) def _test_find_last_entry(self, find_str): filter_list = [':neutron-filter-top - [0:0]', ':%(bn)s-FORWARD - [0:0]', ':%(bn)s-INPUT - [0:0]', ':%(bn)s-local - [0:0]', ':%(wrap)s - [0:0]', ':%(bn)s-OUTPUT - [0:0]', '[0:0] -A FORWARD -j neutron-filter-top', '[0:0] -A OUTPUT -j neutron-filter-top' % IPTABLES_ARG] return self.iptables._find_last_entry(filter_list, find_str) def test_find_last_entry_old_dup(self): find_str = 'neutron-filter-top' match_str = '[0:0] -A OUTPUT -j neutron-filter-top' ret_str = self._test_find_last_entry(find_str) self.assertEqual(ret_str, match_str) def test_find_last_entry_none(self): find_str = 'neutron-filter-NOTFOUND' ret_str = self._test_find_last_entry(find_str) self.assertIsNone(ret_str) class IptablesManagerStateLessTestCase(base.BaseTestCase): def setUp(self): super(IptablesManagerStateLessTestCase, self).setUp() cfg.CONF.register_opts(a_cfg.IPTABLES_OPTS, 'AGENT') cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT') self.iptables = (iptables_manager.IptablesManager(state_less=True)) def test_nat_not_found(self): self.assertNotIn('nat', self.iptables.ipv4)