# Copyright 2012, Nachi Ueno, NTT MCL, 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.

import copy
from unittest import mock

from neutron_lib import constants
from neutron_lib import exceptions
from oslo_config import cfg
import testtools

from neutron.agent import firewall
from neutron.agent.linux import ip_conntrack
from neutron.agent.linux import ipset_manager
from neutron.agent.linux import iptables_comments as ic
from neutron.agent.linux import iptables_firewall
from neutron.common import utils
from neutron.conf.agent import common as agent_config
from neutron.conf.agent import securitygroups_rpc as security_config
from neutron.tests import base
from neutron.tests.unit.api.v2 import test_base


_uuid = test_base._uuid
# TODO(mangelajo): replace all 'IPv4', 'IPv6' to constants
FAKE_PREFIX = {'IPv4': '10.0.0.0/24',
               'IPv6': 'fe80::/48'}
FAKE_IP = {'IPv4': '10.0.0.1',
           'IPv6': 'fe80::1'}
# TODO(mangelajo): replace all '*_sgid' strings for the constants
FAKE_SGID = 'fake_sgid'
OTHER_SGID = 'other_sgid'
_IPv6 = constants.IPv6
_IPv4 = constants.IPv4

RAW_TABLE_OUTPUT = """
# Generated by iptables-save v1.4.21 on Fri Jul 31 16:13:28 2015
*raw
:PREROUTING ACCEPT [11561:3470468]
:OUTPUT ACCEPT [11504:4064044]
:neutron-openvswi-OUTPUT - [0:0]
:neutron-openvswi-PREROUTING - [0:0]
-A PREROUTING -j neutron-openvswi-PREROUTING
 -A OUTPUT -j neutron-openvswi-OUTPUT
-A neutron-openvswi-PREROUTING -m physdev --physdev-in qvbe804433b-61 -j CT --zone 4097
-A neutron-openvswi-PREROUTING -m physdev --physdev-in tape804433b-61 -j CT --zone 4097
-A neutron-openvswi-PREROUTING -m physdev --physdev-in qvb95c24827-02 -j CT --zone 4098
-A neutron-openvswi-PREROUTING -m physdev --physdev-in tap95c24827-02 -j CT --zone 4098
-A neutron-openvswi-PREROUTING -m physdev --physdev-in qvb61634509-31 -j CT --zone 4098
-A neutron-openvswi-PREROUTING -m physdev --physdev-in tap61634509-31 -j CT --zone 4098
-A neutron-openvswi-PREROUTING -m physdev --physdev-in qvb8f46cf18-12 -j CT --zone 4105
-A neutron-openvswi-PREROUTING -m physdev --physdev-in tap8f46cf18-12 -j CT --zone 4105
COMMIT
# Completed on Fri Jul 31 16:13:28 2015
"""  # noqa


class BaseIptablesFirewallTestCase(base.BaseTestCase):
    def setUp(self):
        super(BaseIptablesFirewallTestCase, self).setUp()
        mock.patch('eventlet.spawn_n').start()
        security_config.register_securitygroups_opts()
        agent_config.register_root_helper(cfg.CONF)
        cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT')
        self.utils_exec_p = mock.patch(
            'neutron.agent.linux.utils.execute')
        self.utils_exec = self.utils_exec_p.start()
        self.iptables_cls_p = mock.patch(
            'neutron.agent.linux.iptables_manager.IptablesManager')
        iptables_cls = self.iptables_cls_p.start()
        self.iptables_inst = mock.Mock()
        self.v4filter_inst = mock.Mock()
        self.v6filter_inst = mock.Mock()
        self.iptables_inst.ipv4 = {'filter': self.v4filter_inst,
                                   'raw': self.v4filter_inst
                                   }
        self.iptables_inst.ipv6 = {'filter': self.v6filter_inst,
                                   'raw': self.v6filter_inst
                                   }
        iptables_cls.return_value = self.iptables_inst

        self.iptables_inst.get_rules_for_table.return_value = (
            RAW_TABLE_OUTPUT.splitlines())
        self.firewall = iptables_firewall.IptablesFirewallDriver()
        self.utils_exec.reset_mock()
        self.firewall.iptables = self.iptables_inst
        # don't mess with sysctl knobs in unit tests
        self.firewall._enabled_netfilter_for_bridges = True
        # initial data has 1, 2, and 9 in use, see RAW_TABLE_OUTPUT above.
        self._dev_zone_map = {'61634509-31': 4098, '8f46cf18-12': 4105,
                              '95c24827-02': 4098, 'e804433b-61': 4097}
        get_rules_for_table_func = lambda x: RAW_TABLE_OUTPUT.split('\n')
        filtered_ports = {port_id: self._fake_port()
                          for port_id in self._dev_zone_map}
        self.firewall.ipconntrack = ip_conntrack.IpConntrackManager(
              get_rules_for_table_func, filtered_ports=filtered_ports,
              unfiltered_ports=dict())

    def _fake_port(self):
        return {'device': 'tapfake_dev',
                'mac_address': 'ff:ff:ff:ff:ff:ff',
                'network_id': 'fake_net',
                'fixed_ips': [FAKE_IP['IPv4'],
                              FAKE_IP['IPv6']]}


class IptablesFirewallTestCase(BaseIptablesFirewallTestCase):

    def test__get_port_device_name(self):
        self.assertEqual(
            "name",
            self.firewall._get_port_device_name({'device': 'name'}))
        self.assertEqual(
            "name",
            self.firewall._get_port_device_name(
                {'device': '%s_name' % constants.TAP_DEVICE_PREFIX}))

    def test_prepare_port_filter_with_no_sg(self):
        port = self._fake_port()
        self.firewall.prepare_port_filter(port)
        calls = [mock.call.add_chain('sg-fallback'),
                 mock.call.add_rule(
                     'sg-fallback', '-j DROP',
                     comment=ic.UNMATCH_DROP),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $ifake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_chain('ofake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule('INPUT',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.INPUT_TO_SG),
                 mock.call.add_chain('sfake_dev'),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-s 10.0.0.1/32 -m mac --mac-source FF:FF:FF:FF:FF:FF '
                     '-j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev', '-j DROP',
                     comment=ic.PAIR_DROP),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
                     '--sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 67 --dport 68 -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-j $sg-fallback',
                     top=False, comment=None),
                 mock.call.add_rule('sg-chain', '-j ACCEPT')]

        self.v4filter_inst.assert_has_calls(calls)

    def test_filter_ipv4_ingress(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress'}
        ingress = mock.call.add_rule('ifake_dev', '-j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev', '-s %s -j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'tcp'}
        ingress = mock.call.add_rule(
            'ifake_dev', '-p tcp -j RETURN', top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'tcp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-s %s -p tcp -j RETURN' % prefix,
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_icmp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'icmp'}
        ingress = mock.call.add_rule('ifake_dev', '-p icmp -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_icmp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'icmp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev', '-s %s -p icmp -j RETURN' % prefix,
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p tcp -m tcp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_bad_vrrp_with_dport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'vrrp',
                'port_range_min': 10,
                'port_range_max': 10}
        # Dest port isn't support with VRRP, so don't send it
        # down to iptables.
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p vrrp -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp_port_by_num(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': '6',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p tcp -m tcp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp_mport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p tcp -m tcp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_tcp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100,
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-s %s -p tcp -m tcp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udp'}
        ingress = mock.call.add_rule(
            'ifake_dev', '-p udp -j RETURN', top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-s %s -p udp -j RETURN' % prefix,
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p udp -m udp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udp_mport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p udp -m udp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100,
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-s %s -p udp -m udp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_dccp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'dccp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p dccp -m dccp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_sctp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'sctp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p sctp -m sctp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udplite_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udplite',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p udplite -m multiport --dports 10 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_udplite_mport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'udplite',
                'port_range_min': 10,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p udplite -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_protocol_blank(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': ''}
        ingress = mock.call.add_rule('ifake_dev', '-j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_protocol_zero(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': '0'}
        ingress = mock.call.add_rule('ifake_dev', '-j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_protocol_encap(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': 'encap'}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p encap -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_protocol_encap_by_num(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': '98'}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p encap -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_ingress_protocol_999_local(self):
        # There is no protocol 999, so let's return a mapping
        # that says there is and make sure the rule is created
        # using the name and not the number.
        rule = {'ethertype': 'IPv4',
                'direction': 'ingress',
                'protocol': '999'}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p fooproto -j RETURN',
                                     top=False, comment=None)
        egress = None
        with mock.patch.object(self.firewall,
                               '_local_protocol_name_map') as lpnm:
            lpnm.return_value = {'999': 'fooproto'}
            self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress'}
        egress = mock.call.add_rule('ofake_dev', '-j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_dest_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev', '-d %s -j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_source_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'source_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev', '-s %s -j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_tcp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'tcp'}
        egress = mock.call.add_rule(
            'ofake_dev', '-p tcp -j RETURN', top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_tcp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'tcp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule('ofake_dev',
                                    '-d %s -p tcp -j RETURN' % prefix,
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'icmp'}
        egress = mock.call.add_rule('ofake_dev', '-p icmp -j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'icmp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev', '-d %s -p icmp -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp_type(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 8,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p icmp -m icmp --icmp-type 8 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp_type_name(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 'echo-request',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p icmp -m icmp --icmp-type echo-request '
            '-j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp_type_code(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 8,
                'port_range_max': 0,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p icmp -m icmp --icmp-type 8/0 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_icmp_type_code_protocol_num(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': '1',
                'port_range_min': 8,
                'port_range_max': 0,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p icmp -m icmp --icmp-type 8/0 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_tcp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 10}
        egress = mock.call.add_rule('ofake_dev',
                                    '-p tcp -m tcp --dport 10 -j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_tcp_mport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-p tcp -m tcp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_tcp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p tcp -m tcp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_udp(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'udp'}
        egress = mock.call.add_rule(
            'ofake_dev', '-p udp -j RETURN', top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_udp_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'udp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule('ofake_dev',
                                    '-d %s -p udp -j RETURN' % prefix,
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_udp_port(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 10}
        egress = mock.call.add_rule('ofake_dev',
                                    '-p udp -m udp --dport 10 -j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_udp_mport(self):
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-p udp -m udp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv4_egress_udp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv4']
        rule = {'ethertype': 'IPv4',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p udp -m udp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress'}
        ingress = mock.call.add_rule('ifake_dev', '-j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev', '-s %s -j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_tcp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'tcp'}
        ingress = mock.call.add_rule(
            'ifake_dev', '-p tcp -j RETURN', top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_tcp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'tcp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-s %s -p tcp -j RETURN' % prefix,
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_tcp_port(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p tcp -m tcp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_icmp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'icmp'}
        ingress = mock.call.add_rule(
            'ifake_dev', '-p ipv6-icmp -j RETURN', top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_icmp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'icmp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev', '-s %s -p ipv6-icmp -j RETURN' % prefix,
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_tcp_mport(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p tcp -m tcp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def _test_filter_ingress_tcp_min_port_0(self, ethertype):
        rule = {'ethertype': ethertype,
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 0,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p tcp -m tcp -m multiport --dports 0:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ingress_tcp_min_port_0_for_ipv4(self):
        self._test_filter_ingress_tcp_min_port_0('IPv4')

    def test_filter_ingress_tcp_min_port_0_for_ipv6(self):
        self._test_filter_ingress_tcp_min_port_0('IPv6')

    def test_filter_ipv6_ingress_tcp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100,
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-s %s -p tcp -m tcp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_udp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'udp'}
        ingress = mock.call.add_rule(
            'ifake_dev', '-p udp -j RETURN', top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_udp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'udp',
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-s %s -p udp -j RETURN' % prefix,
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_udp_port(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 10}
        ingress = mock.call.add_rule('ifake_dev',
                                     '-p udp -m udp --dport 10 -j RETURN',
                                     top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_udp_mport(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-p udp -m udp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_ingress_udp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'ingress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100,
                'source_ip_prefix': prefix}
        ingress = mock.call.add_rule(
            'ifake_dev',
            '-s %s -p udp -m udp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        egress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress'}
        egress = mock.call.add_rule('ofake_dev', '-j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev', '-d %s -j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_tcp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'tcp'}
        egress = mock.call.add_rule(
            'ofake_dev', '-p tcp -j RETURN', top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_tcp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'tcp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule('ofake_dev',
                                    '-d %s -p tcp -j RETURN' % prefix,
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmp'}
        egress = mock.call.add_rule(
            'ofake_dev', '-p ipv6-icmp -j RETURN', top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev', '-d %s -p ipv6-icmp -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_type(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 8,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p ipv6-icmp -m icmp6 --icmpv6-type 8 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_type_name(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 'echo-request',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p ipv6-icmp -m icmp6 --icmpv6-type echo-request '
            '-j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_type_code(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmp',
                'port_range_min': 8,
                'port_range_max': 0,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p ipv6-icmp -m icmp6 --icmpv6-type 8/0 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_type_code_protocol_num(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': '58',
                'port_range_min': 8,
                'port_range_max': 0,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p ipv6-icmp -m icmp6 --icmpv6-type 8/0 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_icmp_type_code_protocol_legacy_name(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'icmpv6',
                'port_range_min': 8,
                'port_range_max': 0,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p ipv6-icmp -m icmp6 --icmpv6-type 8/0 -j RETURN' % prefix,
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_tcp_port(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 10}
        egress = mock.call.add_rule('ofake_dev',
                                    '-p tcp -m tcp --dport 10 -j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_tcp_mport(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-p tcp -m tcp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_tcp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'tcp',
                'port_range_min': 10,
                'port_range_max': 100,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p tcp -m tcp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_udp(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'udp'}
        egress = mock.call.add_rule(
            'ofake_dev', '-p udp -j RETURN', top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_udp_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'udp',
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule('ofake_dev',
                                    '-d %s -p udp -j RETURN' % prefix,
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_udp_port(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 10}
        egress = mock.call.add_rule('ofake_dev',
                                    '-p udp -m udp --dport 10 -j RETURN',
                                    top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_udp_mport(self):
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-p udp -m udp -m multiport --dports 10:100 -j RETURN',
            top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def test_filter_ipv6_egress_udp_mport_prefix(self):
        prefix = FAKE_PREFIX['IPv6']
        rule = {'ethertype': 'IPv6',
                'direction': 'egress',
                'protocol': 'udp',
                'port_range_min': 10,
                'port_range_max': 100,
                'dest_ip_prefix': prefix}
        egress = mock.call.add_rule(
            'ofake_dev',
            '-d %s -p udp -m udp -m multiport --dports 10:100 '
            '-j RETURN' % prefix, top=False, comment=None)
        ingress = None
        self._test_prepare_port_filter(rule, ingress, egress)

    def _test_process_trusted_ports(self, configured):
        port = self._fake_port()
        port['id'] = 'tapfake_dev'

        calls = [
            mock.call.add_chain('sg-fallback'),
            mock.call.add_rule('sg-fallback',
                               '-j DROP', comment=ic.UNMATCH_DROP)]

        if configured:
            self.firewall.trusted_ports.append(port['id'])
        else:
            calls.append(
                mock.call.add_rule('FORWARD',
                                   '-m physdev --physdev-out tapfake_dev '
                                   '--physdev-is-bridged -j ACCEPT',
                                   top=False, comment=ic.TRUSTED_ACCEPT))
            calls.append(
                mock.call.add_rule('FORWARD',
                                   '-m physdev --physdev-in tapfake_dev '
                                   '--physdev-is-bridged -j ACCEPT',
                                   top=False, comment=ic.TRUSTED_ACCEPT))

        self.firewall.process_trusted_ports([port['id']])

        for filter_inst in [self.v4filter_inst, self.v6filter_inst]:
            comb = zip(calls, filter_inst.mock_calls)
            for (l, r) in comb:
                self.assertEqual(l, r)
            filter_inst.assert_has_calls(calls)
        self.assertIn(port['id'], self.firewall.trusted_ports)

    def test_process_trusted_ports(self):
        self._test_process_trusted_ports(False)

    def test_process_trusted_ports_already_configured(self):
        self._test_process_trusted_ports(True)

    def _test_remove_trusted_ports(self, configured):
        port = self._fake_port()
        port['id'] = 'tapfake_dev'

        calls = [
            mock.call.add_chain('sg-fallback'),
            mock.call.add_rule('sg-fallback',
                               '-j DROP', comment=ic.UNMATCH_DROP)]

        if configured:
            self.firewall.trusted_ports.append(port['id'])
            calls.append(
                mock.call.remove_rule('FORWARD',
                                      '-m physdev --physdev-out tapfake_dev '
                                      '--physdev-is-bridged -j ACCEPT'))
            calls.append(
                mock.call.remove_rule('FORWARD',
                                      '-m physdev --physdev-in tapfake_dev '
                                      '--physdev-is-bridged -j ACCEPT'))

        self.firewall.remove_trusted_ports([port['id']])

        for filter_inst in [self.v4filter_inst, self.v6filter_inst]:
            comb = zip(calls, filter_inst.mock_calls)
            for (l, r) in comb:
                self.assertEqual(l, r)
            filter_inst.assert_has_calls(calls)
        self.assertNotIn(port['id'], self.firewall.trusted_ports)

    def test_remove_trusted_ports(self):
        self._test_remove_trusted_ports(True)

    def test_process_remove_ports_not_configured(self):
        self._test_remove_trusted_ports(False)

    def _test_prepare_port_filter(self,
                                  rule,
                                  ingress_expected_call=None,
                                  egress_expected_call=None):
        port = self._fake_port()
        ethertype = rule['ethertype']
        prefix = utils.ip_to_cidr(FAKE_IP[ethertype])
        filter_inst = self.v4filter_inst
        dhcp_rule = [mock.call.add_rule(
            'ofake_dev',
            '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
            '--sport 68 --dport 67 -j RETURN',
            top=False, comment=None)]

        if ethertype == 'IPv6':
            filter_inst = self.v6filter_inst

            dhcp_rule = [mock.call.add_rule('ofake_dev',
                                            '-s ::/128 -d ff02::/16 '
                                            '-p ipv6-icmp -m icmp6 '
                                            '--icmpv6-type %s -j RETURN' %
                                            icmp6_type, top=False,
                                            comment=None) for icmp6_type
                         in constants.ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES]
        sg = [rule]
        port['security_group_rules'] = sg
        self.firewall.prepare_port_filter(port)
        calls = [mock.call.add_chain('sg-fallback'),
                 mock.call.add_rule(
                     'sg-fallback',
                     '-j DROP',
                     comment=ic.UNMATCH_DROP),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $ifake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG)
                 ]
        if ethertype == 'IPv6':
            for icmp6_type in firewall.ICMPV6_ALLOWED_INGRESS_TYPES:
                calls.append(
                    mock.call.add_rule('ifake_dev',
                                       '-p ipv6-icmp -m icmp6 --icmpv6-type '
                                       '%s -j RETURN' %
                                       icmp6_type, top=False, comment=None))
        calls += [
            mock.call.add_rule(
                'ifake_dev',
                '-m state --state RELATED,ESTABLISHED -j RETURN',
                top=False, comment=None
            )
        ]

        if ingress_expected_call:
            calls.append(ingress_expected_call)

        calls += [mock.call.add_rule(
                      'ifake_dev',
                      '-m state --state INVALID -j DROP',
                      top=False, comment=None),
                  mock.call.add_rule('ifake_dev',
                                     '-j $sg-fallback',
                                     top=False, comment=None),
                  mock.call.add_chain('ofake_dev'),
                  mock.call.add_rule('FORWARD',
                                     '-m physdev --physdev-in tapfake_dev '
                                     '--physdev-is-bridged -j $sg-chain',
                                     top=True, comment=ic.VM_INT_SG),
                  mock.call.add_rule('sg-chain',
                                     '-m physdev --physdev-in tapfake_dev '
                                     '--physdev-is-bridged -j $ofake_dev',
                                     top=False, comment=ic.SG_TO_VM_SG),
                  mock.call.add_rule('INPUT',
                                     '-m physdev --physdev-in tapfake_dev '
                                     '--physdev-is-bridged -j $ofake_dev',
                                     top=False, comment=ic.INPUT_TO_SG),
                  mock.call.add_chain('sfake_dev'),
                  mock.call.add_rule(
                      'sfake_dev',
                      '-s %s -m mac --mac-source FF:FF:FF:FF:FF:FF -j RETURN'
                      % prefix,
                      comment=ic.PAIR_ALLOW)]

        if ethertype == 'IPv6':
            calls.append(mock.call.add_rule('sfake_dev',
                '-s fe80::fdff:ffff:feff:ffff/128 -m mac '
                '--mac-source FF:FF:FF:FF:FF:FF -j RETURN',
                comment=ic.PAIR_ALLOW))
        calls.append(mock.call.add_rule('sfake_dev', '-j DROP',
                                        comment=ic.PAIR_DROP))
        calls += dhcp_rule
        calls.append(mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                        top=False, comment=None))
        if ethertype == 'IPv4':
            calls.append(mock.call.add_rule(
                'ofake_dev',
                '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                top=False, comment=None))
            calls.append(mock.call.add_rule(
                'ofake_dev',
                '-p udp -m udp --sport 67 --dport 68 -j DROP',
                top=False, comment=None))
        if ethertype == 'IPv6':
            calls.append(mock.call.add_rule('ofake_dev',
                                            '-p ipv6-icmp -m icmp6 '
                                            '--icmpv6-type %s -j DROP' %
                                            constants.ICMPV6_TYPE_RA,
                                            top=False, comment=None))
            calls.append(mock.call.add_rule('ofake_dev',
                                            '-p ipv6-icmp -j RETURN',
                                            top=False, comment=None))
            calls.append(mock.call.add_rule('ofake_dev', '-p udp -m udp '
                                            '--sport 546 --dport 547 '
                                            '-j RETURN',
                                            top=False, comment=None))
            calls.append(mock.call.add_rule(
                'ofake_dev',
                '-p udp -m udp --sport 547 --dport 546 -j DROP',
                top=False, comment=None))

        calls += [
            mock.call.add_rule(
                'ofake_dev',
                '-m state --state RELATED,ESTABLISHED -j RETURN',
                top=False, comment=None),
        ]

        if egress_expected_call:
            calls.append(egress_expected_call)

        calls += [mock.call.add_rule(
                      'ofake_dev',
                      '-m state --state INVALID -j DROP',
                      top=False, comment=None),
                  mock.call.add_rule('ofake_dev',
                                     '-j $sg-fallback',
                                     top=False, comment=None),
                  mock.call.add_rule('sg-chain', '-j ACCEPT')]
        comb = zip(calls, filter_inst.mock_calls)
        for (l, r) in comb:
            self.assertEqual(l, r)
        filter_inst.assert_has_calls(calls)

    def _test_remove_conntrack_entries(self, ethertype, protocol, direction,
                                       ct_zone):
        port = self._fake_port()
        port['security_groups'] = 'fake_sg_id'
        self.firewall.filtered_ports[port['device']] = port
        self.firewall.updated_rule_sg_ids = set(['fake_sg_id'])
        self.firewall.sg_rules['fake_sg_id'] = [
            {'direction': direction, 'ethertype': ethertype,
             'protocol': protocol}]

        with mock.patch.dict(self.firewall.ipconntrack._device_zone_map,
                             {port['network_id']: ct_zone}):
            self.firewall.filter_defer_apply_on()
            self.firewall.sg_rules['fake_sg_id'] = []
            self.firewall.filter_defer_apply_off()
            if not ct_zone:
                self.assertFalse(self.utils_exec.called)
                return
            # process conntrack updates in the queue
            while not self.firewall.ipconntrack._queue.empty():
                self.firewall.ipconntrack._process_queue()
            cmd = ['conntrack', '-D']
            if protocol is not None:
                if str(protocol) == '0':
                    protocol = 'ip'
                cmd.extend(['-p', str(protocol)])
            if ethertype == 'IPv4':
                cmd.extend(['-f', 'ipv4'])
                if direction == 'ingress':
                    cmd.extend(['-d', '10.0.0.1'])
                else:
                    cmd.extend(['-s', '10.0.0.1'])
            else:
                cmd.extend(['-f', 'ipv6'])
                if direction == 'ingress':
                    cmd.extend(['-d', 'fe80::1'])
                else:
                    cmd.extend(['-s', 'fe80::1'])

            cmd.extend(['-w', ct_zone])
            calls = [
                mock.call(cmd, run_as_root=True, privsep_exec=True,
                          check_exit_code=True, extra_ok_codes=[1])]
            self.utils_exec.assert_has_calls(calls)

    def test_remove_conntrack_entries_for_delete_rule_ipv4(self):
        for direction in ['ingress', 'egress']:
            for pro in [None, 'ip', 'tcp', 'icmp', 'udp', '0']:
                self._test_remove_conntrack_entries(
                    'IPv4', pro, direction, ct_zone=10)

    def test_remove_conntrack_entries_for_delete_rule_ipv4_by_num(self):
        for direction in ['ingress', 'egress']:
            for pro in [None, 0, 6, 1, 17]:
                self._test_remove_conntrack_entries(
                    'IPv4', pro, direction, ct_zone=10)

    def test_remove_conntrack_entries_for_delete_rule_ipv4_no_ct_zone(self):
        for direction in ['ingress', 'egress']:
            for pro in [None, 'tcp', 'icmp', 'udp']:
                self._test_remove_conntrack_entries(
                    'IPv4', pro, direction, ct_zone=None)

    def test_remove_conntrack_entries_for_delete_rule_ipv6(self):
        for direction in ['ingress', 'egress']:
            for pro in [None, 'tcp', 'icmp', 'udp']:
                self._test_remove_conntrack_entries(
                    'IPv6', pro, direction, ct_zone=10)

    def test_remove_conntrack_entries_for_delete_rule_ipv6_no_ct_zone(self):
        for direction in ['ingress', 'egress']:
            for pro in [None, 'tcp', 'icmp', 'udp']:
                self._test_remove_conntrack_entries(
                    'IPv6', pro, direction, ct_zone=None)

    def test_remove_conntrack_entries_for_port_sec_group_change(self):
        self._test_remove_conntrack_entries_for_port_sec_group_change(
            ct_zone=10)

    def test_remove_conntrack_entries_for_port_sec_group_change_no_ct_zone(
            self):

        self._test_remove_conntrack_entries_for_port_sec_group_change(
            ct_zone=None)

    def _get_expected_conntrack_calls(self, ips, ct_zone):
        expected_calls = []
        for ip_item in ips:
            proto = ip_item[0]
            ip = ip_item[1]
            for direction in ['-d', '-s']:
                cmd = ['conntrack', '-D', '-f', proto, direction, ip]
                if ct_zone:
                    cmd.extend(['-w', ct_zone])
                expected_calls.append(
                    mock.call(cmd, run_as_root=True, privsep_exec=True,
                              check_exit_code=True, extra_ok_codes=[1]))
        return expected_calls

    def _test_remove_conntrack_entries_for_port_sec_group_change(self,
                                                                 ct_zone):

        port = self._fake_port()
        port['security_groups'] = ['fake_sg_id']
        self.firewall.filtered_ports[port['device']] = port
        self.firewall.updated_sg_members = set(['tapfake_dev'])
        with mock.patch.dict(self.firewall.ipconntrack._device_zone_map,
                             {port['network_id']: ct_zone}):
            self.firewall.filter_defer_apply_on()
            new_port = copy.deepcopy(port)
            new_port['security_groups'] = ['fake_sg_id2']

            self.firewall.filtered_ports[port['device']] = new_port
            self.firewall.filter_defer_apply_off()
            if not ct_zone:
                self.assertFalse(self.utils_exec.called)
                return
            # process conntrack updates in the queue
            while not self.firewall.ipconntrack._queue.empty():
                self.firewall.ipconntrack._process_queue()
            calls = self._get_expected_conntrack_calls(
                [('ipv4', '10.0.0.1'), ('ipv6', 'fe80::1')], ct_zone)
            self.utils_exec.assert_has_calls(calls)

    def test_remove_conntrack_entries_for_sg_member_changed_ipv4(self):
        for direction in ['ingress', 'egress']:
            self._test_remove_conntrack_entries_sg_member_changed(
                'IPv4', direction, ct_zone=10)

    def test_remove_conntrack_entries_for_sg_member_changed_ipv4_no_ct_zone(
            self):
        for direction in ['ingress', 'egress']:
            self._test_remove_conntrack_entries_sg_member_changed(
                'IPv4', direction, ct_zone=None)

    def test_remove_conntrack_entries_for_sg_member_changed_ipv6(self):
        for direction in ['ingress', 'egress']:
            self._test_remove_conntrack_entries_sg_member_changed(
                'IPv6', direction, ct_zone=10)

    def test_remove_conntrack_entries_for_sg_member_changed_ipv6_no_ct_zone(
            self):
        for direction in ['ingress', 'egress']:
            self._test_remove_conntrack_entries_sg_member_changed(
                'IPv6', direction, ct_zone=None)

    def _test_remove_conntrack_entries_sg_member_changed(self, ethertype,
                                                         direction, ct_zone):
        port = self._fake_port()
        port['security_groups'] = ['fake_sg_id']
        port['security_group_source_groups'] = ['fake_sg_id2']
        port['security_group_rules'] = [{'security_group_id': 'fake_sg_id',
                                         'direction': direction,
                                         'remote_group_id': 'fake_sg_id2',
                                         'ethertype': ethertype}]
        self.firewall.filtered_ports = {port['device']: port}

        if ethertype == "IPv4":
            ethertype = "ipv4"
            members_add = {'IPv4': [('10.0.0.2', 'fa:16:3e:aa:bb:c1'),
                                    ('10.0.0.3', 'fa:16:3e:aa:bb:c2')]}
            members_after_delete = {
                'IPv4': [('10.0.0.3', 'fa:16:3e:aa:bb:c2'), ]}
        else:
            ethertype = "ipv6"
            members_add = {'IPv6': [('fe80::2', 'fa:16:3e:aa:bb:c3'),
                                    ('fe80::3', 'fa:16:3e:aa:bb:c4')]}
            members_after_delete = {
                'IPv6': [('fe80::3', 'fa:16:3e:aa:bb:c4'), ]}

        with mock.patch.dict(self.firewall.ipconntrack._device_zone_map,
                             {port['network_id']: ct_zone}):
            # add ['10.0.0.2', '10.0.0.3'] or ['fe80::2', 'fe80::3']
            self.firewall.security_group_updated('sg_member', ['fake_sg_id2'])
            self.firewall.update_security_group_members(
                'fake_sg_id2', members_add)

            # delete '10.0.0.2' or 'fe80::2'
            self.firewall.security_group_updated('sg_member', ['fake_sg_id2'])
            self.firewall.update_security_group_members(
                'fake_sg_id2', members_after_delete)

            # check conntrack deletion from '10.0.0.1' to '10.0.0.2' or
            # from 'fe80::1' to 'fe80::2'
            ips = {"ipv4": ['10.0.0.1', '10.0.0.2'],
                   "ipv6": ['fe80::1', 'fe80::2']}
            calls = []
            # process conntrack updates in the queue
            while not self.firewall.ipconntrack._queue.empty():
                self.firewall.ipconntrack._process_queue()
            for direction in ['ingress', 'egress']:
                direction = '-d' if direction == 'ingress' else '-s'
                remote_ip_direction = '-s' if direction == '-d' else '-d'
                conntrack_cmd = ['conntrack', '-D', '-f', ethertype,
                                 direction, ips[ethertype][0]]
                if not ct_zone:
                    continue
                conntrack_cmd.extend(['-w', 10])
                conntrack_cmd.extend([remote_ip_direction, ips[ethertype][1]])

                calls.append(mock.call(conntrack_cmd,
                                       run_as_root=True, privsep_exec=True,
                                       check_exit_code=True,
                                       extra_ok_codes=[1]))

        self.utils_exec.assert_has_calls(calls)

    def test_user_sg_rules_deduped_before_call_to_iptables_manager(self):
        port = self._fake_port()
        port['security_group_rules'] = [{'ethertype': 'IPv4',
                                         'direction': 'ingress'}] * 2
        self.firewall.prepare_port_filter(port)
        rules = [''.join(c[1]) for c in self.v4filter_inst.add_rule.mock_calls]
        self.assertEqual(len(set(rules)), len(rules))

    def test_update_delete_port_filter(self):
        port = self._fake_port()
        port['security_group_rules'] = [{'ethertype': 'IPv4',
                                         'direction': 'ingress'}]
        self.firewall.prepare_port_filter(port)
        port['security_group_rules'] = [{'ethertype': 'IPv4',
                                         'direction': 'egress'}]
        self.firewall.update_port_filter(port)
        self.firewall.update_port_filter({'device': 'no-exist-device'})
        self.firewall.remove_port_filter(port)
        self.firewall.remove_port_filter({'device': 'no-exist-device'})
        calls = [mock.call.add_chain('sg-fallback'),
                 mock.call.add_rule(
                     'sg-fallback',
                     '-j DROP',
                     comment=ic.UNMATCH_DROP),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule(
                     'FORWARD',
                     '-m physdev --physdev-out tapfake_dev '
                     '--physdev-is-bridged -j $sg-chain',
                     top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule(
                     'sg-chain',
                     '-m physdev --physdev-out tapfake_dev '
                     '--physdev-is-bridged -j $ifake_dev',
                     top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev', '-j RETURN',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_chain('ofake_dev'),
                 mock.call.add_rule(
                     'FORWARD',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $sg-chain',
                     top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule(
                     'sg-chain',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $ofake_dev',
                     top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'INPUT',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $ofake_dev',
                     top=False, comment=ic.INPUT_TO_SG),
                 mock.call.add_chain('sfake_dev'),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-s 10.0.0.1/32 -m mac --mac-source FF:FF:FF:FF:FF:FF '
                     '-j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev', '-j DROP',
                     comment=ic.PAIR_DROP),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
                     '--sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 67 --dport 68 -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev', '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_rule('sg-chain', '-j ACCEPT'),
                 mock.call.remove_chain('ifake_dev'),
                 mock.call.remove_chain('ofake_dev'),
                 mock.call.remove_chain('sfake_dev'),
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_chain('sg-chain'),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_rule('PREROUTING', mock.ANY,
                                    comment=None),  # zone set
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule(
                     'FORWARD',
                     '-m physdev --physdev-out tapfake_dev '
                     '--physdev-is-bridged -j $sg-chain',
                     top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule(
                     'sg-chain',
                     '-m physdev --physdev-out tapfake_dev '
                     '--physdev-is-bridged -j $ifake_dev',
                     top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_chain('ofake_dev'),
                 mock.call.add_rule(
                     'FORWARD',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $sg-chain',
                     top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule(
                     'sg-chain',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $ofake_dev',
                     top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'INPUT',
                     '-m physdev --physdev-in tapfake_dev '
                     '--physdev-is-bridged -j $ofake_dev',
                     top=False, comment=ic.INPUT_TO_SG),
                 mock.call.add_chain('sfake_dev'),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-s 10.0.0.1/32 -m mac --mac-source FF:FF:FF:FF:FF:FF '
                     '-j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev', '-j DROP',
                     comment=ic.PAIR_DROP),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
                     '--sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 67 --dport 68 -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j RETURN',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_rule('sg-chain', '-j ACCEPT'),
                 mock.call.remove_chain('ifake_dev'),
                 mock.call.remove_chain('ofake_dev'),
                 mock.call.remove_chain('sfake_dev'),
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_rule('PREROUTING', mock.ANY),  # zone set
                 mock.call.remove_chain('sg-chain'),
                 mock.call.add_chain('sg-chain')]

        self.v4filter_inst.assert_has_calls(calls)

    def test_delete_conntrack_from_delete_port(self):
        self._test_delete_conntrack_from_delete_port(ct_zone=10)

    def test_delete_conntrack_from_delete_port_no_ct_zone(self):
        self._test_delete_conntrack_from_delete_port(ct_zone=None)

    def _test_delete_conntrack_from_delete_port(self, ct_zone):
        port = self._fake_port()
        port['security_groups'] = ['fake_sg_id']
        self.firewall.filtered_ports = {'tapfake_dev': port}
        self.firewall.devices_with_updated_sg_members['fake_sg_id2'
                                                      ] = ['tapfake_dev']
        new_port = copy.deepcopy(port)
        new_port['security_groups'] = ['fake_sg_id2']
        new_port['device'] = ['tapfake_dev2']
        new_port['fixed_ips'] = ['10.0.0.2', 'fe80::2']
        self.firewall.sg_members['fake_sg_id2'] = {'IPv4': ['10.0.0.2'],
                                                   'IPv6': ['fe80::2']}
        mock.patch.object(self.firewall.ipconntrack, 'get_device_zone',
                          return_value=ct_zone).start()
        self.firewall.remove_port_filter(port)
        if not ct_zone:
            self.assertFalse(self.utils_exec.called)
            return
        # process conntrack updates in the queue
        while not self.firewall.ipconntrack._queue.empty():
            self.firewall.ipconntrack._process_queue()
        calls = self._get_expected_conntrack_calls(
            [('ipv4', '10.0.0.1'), ('ipv6', 'fe80::1')], ct_zone)
        self.utils_exec.assert_has_calls(calls)

    def test_remove_unknown_port(self):
        port = self._fake_port()
        self.firewall.remove_port_filter(port)
        # checking no exception occurs
        self.assertFalse(self.v4filter_inst.called)

    def test_defer_apply(self):
        with self.firewall.defer_apply():
            pass
        self.iptables_inst.assert_has_calls([mock.call.defer_apply_on(),
                                             mock.call.defer_apply_off()])

    def test_filter_defer_with_exception(self):
        try:
            with self.firewall.defer_apply():
                raise Exception("same exception")
        except Exception:
            pass
        self.iptables_inst.assert_has_calls([mock.call.defer_apply_on(),
                                             mock.call.defer_apply_off()])

    def _mock_chain_applies(self):
        class CopyingMock(mock.MagicMock):
            """Copies arguments so mutable arguments can be asserted on.

            Copied verbatim from unittest.mock documentation.
            """
            def __call__(self, *args, **kwargs):
                args = copy.deepcopy(args)
                kwargs = copy.deepcopy(kwargs)
                return super(CopyingMock, self).__call__(*args, **kwargs)
        # Need to use CopyingMock because _{setup,remove}_chains_apply are
        # usually called with that's modified between calls (i.e.,
        # self.firewall.filtered_ports).
        chain_applies = CopyingMock()
        self.firewall._setup_chains_apply = chain_applies.setup
        self.firewall._remove_chains_apply = chain_applies.remove
        return chain_applies

    def test_mock_chain_applies(self):
        chain_applies = self._mock_chain_applies()
        port_prepare = {'device': 'd1', 'mac_address': 'prepare',
                        'network_id': 'fake_net'}
        port_update = {'device': 'd1', 'mac_address': 'update',
                       'network_id': 'fake_net'}
        self.firewall.prepare_port_filter(port_prepare)
        self.firewall.update_port_filter(port_update)
        self.firewall.remove_port_filter(port_update)
        chain_applies.assert_has_calls([
                                mock.call.setup({'d1': port_prepare}, {}),
                                mock.call.remove({'d1': port_prepare}, {}),
                                mock.call.setup({'d1': port_update}, {}),
                                mock.call.remove({'d1': port_update}, {}),
                                mock.call.setup({}, {})])

    def test_defer_chain_apply_need_pre_defer_copy(self):
        chain_applies = self._mock_chain_applies()
        port = self._fake_port()
        device2port = {port['device']: port}
        self.firewall.prepare_port_filter(port)
        with self.firewall.defer_apply():
            self.firewall.remove_port_filter(port)
        chain_applies.assert_has_calls([mock.call.setup(device2port, {}),
                                        mock.call.remove(device2port, {}),
                                        mock.call.setup({}, {})])

    def test_defer_chain_apply_coalesce_simple(self):
        chain_applies = self._mock_chain_applies()
        port = self._fake_port()
        with self.firewall.defer_apply():
            self.firewall.prepare_port_filter(port)
            self.firewall.update_port_filter(port)
            self.firewall.remove_port_filter(port)
        chain_applies.assert_has_calls([mock.call.remove({}, {}),
                                        mock.call.setup({}, {})])

    def test_defer_chain_apply_coalesce_multiple_ports(self):
        chain_applies = self._mock_chain_applies()
        port1 = {'device': 'd1', 'mac_address': 'mac1', 'network_id': 'net1'}
        port2 = {'device': 'd2', 'mac_address': 'mac2', 'network_id': 'net1'}
        device2port = {'d1': port1, 'd2': port2}
        with self.firewall.defer_apply():
            self.firewall.prepare_port_filter(port1)
            self.firewall.prepare_port_filter(port2)
        chain_applies.assert_has_calls([mock.call.remove({}, {}),
                                        mock.call.setup(device2port, {})])

    def test_ip_spoofing_filter_with_multiple_ips(self):
        port = {'device': 'tapfake_dev',
                'mac_address': 'ff:ff:ff:ff:ff:ff',
                'network_id': 'fake_net',
                'fixed_ips': ['10.0.0.1', 'fe80::1', '10.0.0.2']}
        self.firewall.prepare_port_filter(port)
        calls = [mock.call.add_chain('sg-fallback'),
                 mock.call.add_rule(
                     'sg-fallback', '-j DROP',
                     comment=ic.UNMATCH_DROP),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $ifake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_chain('ofake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule('INPUT',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.INPUT_TO_SG),
                 mock.call.add_chain('sfake_dev'),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-s 10.0.0.1/32 -m mac --mac-source FF:FF:FF:FF:FF:FF '
                     '-j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-s 10.0.0.2/32 -m mac --mac-source FF:FF:FF:FF:FF:FF '
                     '-j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev', '-j DROP',
                     comment=ic.PAIR_DROP),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
                     '--sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 67 --dport 68 -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev',
                                    '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_rule('sg-chain', '-j ACCEPT')]
        self.v4filter_inst.assert_has_calls(calls)

    def test_ip_spoofing_no_fixed_ips(self):
        port = {'device': 'tapfake_dev',
                'mac_address': 'ff:ff:ff:ff:ff:ff',
                'network_id': 'fake_net',
                'fixed_ips': []}
        self.firewall.prepare_port_filter(port)
        calls = [mock.call.add_chain('sg-fallback'),
                 mock.call.add_rule(
                     'sg-fallback', '-j DROP',
                     comment=ic.UNMATCH_DROP),
                 mock.call.add_chain('sg-chain'),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_rule('PREROUTING', mock.ANY,  # zone set
                                    comment=None),
                 mock.call.add_chain('ifake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-out tapfake_dev '
                                    '--physdev-is-bridged -j $ifake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ifake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ifake_dev', '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_chain('ofake_dev'),
                 mock.call.add_rule('FORWARD',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $sg-chain',
                                    top=True, comment=ic.VM_INT_SG),
                 mock.call.add_rule('sg-chain',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.SG_TO_VM_SG),
                 mock.call.add_rule('INPUT',
                                    '-m physdev --physdev-in tapfake_dev '
                                    '--physdev-is-bridged -j $ofake_dev',
                                    top=False, comment=ic.INPUT_TO_SG),
                 mock.call.add_chain('sfake_dev'),
                 mock.call.add_rule(
                     'sfake_dev',
                     '-m mac --mac-source FF:FF:FF:FF:FF:FF -j RETURN',
                     comment=ic.PAIR_ALLOW),
                 mock.call.add_rule(
                     'sfake_dev', '-j DROP',
                     comment=ic.PAIR_DROP),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-s 0.0.0.0/32 -d 255.255.255.255/32 -p udp -m udp '
                     '--sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sfake_dev',
                                    top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 68 --dport 67 -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-p udp -m udp --sport 67 --dport 68 -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state RELATED,ESTABLISHED -j RETURN',
                     top=False, comment=None),
                 mock.call.add_rule(
                     'ofake_dev',
                     '-m state --state INVALID -j DROP',
                     top=False, comment=None),
                 mock.call.add_rule('ofake_dev', '-j $sg-fallback',
                                    top=False, comment=None),
                 mock.call.add_rule('sg-chain', '-j ACCEPT')]
        self.v4filter_inst.assert_has_calls(calls)

    def test__get_sg_members(self):
        sg_info = {_uuid(): {constants.IPv4: [['ip1', None]],
                             constants.IPv6: []},
                   _uuid(): {constants.IPv4: [['ip2', None], ['ip3', None]],
                             constants.IPv6: [['ip4', None]]},
                   }
        sg_ids = list(sg_info.keys())
        self.assertEqual({'ip1'}, self.firewall._get_sg_members(
            sg_info, sg_ids[0], constants.IPv4))
        self.assertEqual(set([]), self.firewall._get_sg_members(
            sg_info, sg_ids[0], constants.IPv6))
        self.assertEqual({'ip2', 'ip3'}, self.firewall._get_sg_members(
            sg_info, sg_ids[1], constants.IPv4))
        self.assertEqual({'ip4'}, self.firewall._get_sg_members(
            sg_info, sg_ids[1], constants.IPv6))


class IptablesFirewallEnhancedIpsetTestCase(BaseIptablesFirewallTestCase):
    def setUp(self):
        super(IptablesFirewallEnhancedIpsetTestCase, self).setUp()
        self.firewall.ipset = mock.Mock()
        self.firewall.ipset.get_name.side_effect = (
            ipset_manager.IpsetManager.get_name)
        self.firewall.ipset.set_name_exists.return_value = True
        self.firewall.ipset.set_members = mock.Mock(return_value=([], []))

    def _fake_port(self, sg_id=FAKE_SGID):
        return {'device': 'tapfake_dev',
                'mac_address': 'ff:ff:ff:ff:ff:ff',
                'network_id': 'fake_net',
                'fixed_ips': [FAKE_IP['IPv4'],
                              FAKE_IP['IPv6']],
                'security_groups': [sg_id],
                'security_group_source_groups': [sg_id]}

    def _fake_sg_rule_for_ethertype(self, ethertype, remote_group):
        return {'direction': 'ingress', 'remote_group_id': remote_group,
                'ethertype': ethertype}

    def _fake_sg_rules(self, sg_id=FAKE_SGID, remote_groups=None):
        remote_groups = remote_groups or {_IPv4: [FAKE_SGID],
                                          _IPv6: [FAKE_SGID]}
        rules = []
        for ip_version, remote_group_list in remote_groups.items():
            for remote_group in remote_group_list:
                rules.append(self._fake_sg_rule_for_ethertype(ip_version,
                                                              remote_group))
        return {sg_id: rules}

    def _fake_sg_members(self, sg_ids=None):
        return {sg_id: copy.copy(FAKE_IP) for sg_id in (sg_ids or [FAKE_SGID])}

    def test_update_security_group_members(self):
        sg_members = {'IPv4': ['10.0.0.1', '10.0.0.2'], 'IPv6': ['fe80::1']}
        self.firewall.update_security_group_members('fake_sgid', sg_members)
        calls = [
            mock.call.set_members('fake_sgid', 'IPv4',
                                  ['10.0.0.1', '10.0.0.2']),
            mock.call.set_members('fake_sgid', 'IPv6',
                                  ['fe80::1'])
        ]
        self.firewall.ipset.assert_has_calls(calls, any_order=True)

    def _setup_fake_firewall_members_and_rules(self, firewall):
        firewall.sg_rules = self._fake_sg_rules()
        firewall.pre_sg_rules = self._fake_sg_rules()
        firewall.sg_members = self._fake_sg_members()
        firewall.pre_sg_members = firewall.sg_members

    def _prepare_rules_and_members_for_removal(self):
        self._setup_fake_firewall_members_and_rules(self.firewall)
        self.firewall.pre_sg_members[OTHER_SGID] = (
            self.firewall.pre_sg_members[FAKE_SGID])

    def test_determine_remote_sgs_to_remove(self):
        self._prepare_rules_and_members_for_removal()
        ports = [self._fake_port()]

        self.assertEqual(
            {_IPv4: set([OTHER_SGID]), _IPv6: set([OTHER_SGID])},
            self.firewall._determine_remote_sgs_to_remove(ports))

    def test_determine_remote_sgs_to_remove_ipv6_unreferenced(self):
        self._prepare_rules_and_members_for_removal()
        ports = [self._fake_port()]
        self.firewall.sg_rules = self._fake_sg_rules(
            remote_groups={_IPv4: [OTHER_SGID, FAKE_SGID],
                           _IPv6: [FAKE_SGID]})
        self.assertEqual(
            {_IPv4: set(), _IPv6: set([OTHER_SGID])},
            self.firewall._determine_remote_sgs_to_remove(ports))

    def test_get_remote_sg_ids_by_ipversion(self):
        self.firewall.sg_rules = self._fake_sg_rules(
            remote_groups={_IPv4: [FAKE_SGID], _IPv6: [OTHER_SGID]})

        ports = [self._fake_port()]

        self.assertEqual(
            {_IPv4: set([FAKE_SGID]), _IPv6: set([OTHER_SGID])},
            self.firewall._get_remote_sg_ids_sets_by_ipversion(ports))

    def test_get_remote_sg_ids(self):
        self.firewall.sg_rules = self._fake_sg_rules(
            remote_groups={_IPv4: [FAKE_SGID, FAKE_SGID, FAKE_SGID],
                           _IPv6: [OTHER_SGID, OTHER_SGID, OTHER_SGID]})

        port = self._fake_port()

        self.assertEqual(
            {_IPv4: set([FAKE_SGID]), _IPv6: set([OTHER_SGID])},
            self.firewall._get_remote_sg_ids(port))

    def test_determine_sg_rules_to_remove(self):
        self.firewall.pre_sg_rules = self._fake_sg_rules(sg_id=OTHER_SGID)
        ports = [self._fake_port()]

        self.assertEqual(set([OTHER_SGID]),
                         self.firewall._determine_sg_rules_to_remove(ports))

    def test_get_sg_ids_set_for_ports(self):
        sg_ids = set([FAKE_SGID, OTHER_SGID])
        ports = [self._fake_port(sg_id) for sg_id in sg_ids]

        self.assertEqual(sg_ids,
                         self.firewall._get_sg_ids_set_for_ports(ports))

    def test_remove_sg_members(self):
        self.firewall.sg_members = self._fake_sg_members([FAKE_SGID,
                                                          OTHER_SGID])
        remote_sgs_to_remove = {_IPv4: set([FAKE_SGID]),
                                _IPv6: set([FAKE_SGID, OTHER_SGID])}
        self.firewall._remove_sg_members(remote_sgs_to_remove)

        self.assertIn(OTHER_SGID, self.firewall.sg_members)
        self.assertNotIn(FAKE_SGID, self.firewall.sg_members)

    def test_remove_unused_security_group_info_clears_unused_rules(self):
        self._setup_fake_firewall_members_and_rules(self.firewall)
        self.firewall.prepare_port_filter(self._fake_port())

        # create another SG which won't be referenced by any filtered port
        fake_sg_rules = self.firewall.sg_rules['fake_sgid']
        self.firewall.pre_sg_rules[OTHER_SGID] = fake_sg_rules
        self.firewall.sg_rules[OTHER_SGID] = fake_sg_rules

        # call the cleanup function, and check the unused sg_rules are out
        self.firewall._remove_unused_security_group_info()
        self.assertNotIn(OTHER_SGID, self.firewall.sg_rules)

    def test_remove_unused_security_group_info(self):
        self.firewall.sg_members = {OTHER_SGID: {_IPv4: [], _IPv6: []}}
        self.firewall.pre_sg_members = self.firewall.sg_members
        self.firewall.sg_rules = self._fake_sg_rules(
            remote_groups={_IPv4: [FAKE_SGID], _IPv6: [FAKE_SGID]})
        self.firewall.pre_sg_rules = self.firewall.sg_rules
        port = self._fake_port()
        self.firewall.filtered_ports['tapfake_dev'] = port
        self.firewall._remove_unused_security_group_info()
        self.assertNotIn(OTHER_SGID, self.firewall.sg_members)

    def test_not_remove_used_security_group_info(self):
        self.firewall.sg_members = {OTHER_SGID: {_IPv4: [], _IPv6: []}}
        self.firewall.pre_sg_members = self.firewall.sg_members
        self.firewall.sg_rules = self._fake_sg_rules(
            remote_groups={_IPv4: [OTHER_SGID], _IPv6: [OTHER_SGID]})
        self.firewall.pre_sg_rules = self.firewall.sg_rules
        port = self._fake_port()
        self.firewall.filtered_ports['tapfake_dev'] = port
        self.firewall._remove_unused_security_group_info()
        self.assertIn(OTHER_SGID, self.firewall.sg_members)

    def test_remove_all_unused_info(self):
        self._setup_fake_firewall_members_and_rules(self.firewall)
        self.firewall.filtered_ports = {}
        self.firewall._remove_unused_security_group_info()
        self.assertFalse(self.firewall.sg_members)
        self.assertFalse(self.firewall.sg_rules)

    def test_single_fallback_accept_rule(self):
        p1, p2 = self._fake_port(), self._fake_port()
        self.firewall._setup_chains_apply(dict(p1=p1, p2=p2), {})
        v4_adds = self.firewall.iptables.ipv4['filter'].add_rule.mock_calls
        v6_adds = self.firewall.iptables.ipv6['filter'].add_rule.mock_calls
        sg_chain_v4_accept = [call for call in v4_adds
                              if call == mock.call('sg-chain', '-j ACCEPT')]
        sg_chain_v6_accept = [call for call in v6_adds
                              if call == mock.call('sg-chain', '-j ACCEPT')]
        self.assertEqual(1, len(sg_chain_v4_accept))
        self.assertEqual(1, len(sg_chain_v6_accept))

    def test_remove_port_filter_with_destroy_ipset_chain(self):
        self.firewall.sg_rules = self._fake_sg_rules()
        port = self._fake_port()
        self.firewall.pre_sg_members = {'fake_sgid': {
            'IPv4': [],
            'IPv6': []}}
        sg_members = {'IPv4': ['10.0.0.1'], 'IPv6': ['fe80::1']}
        self.firewall.update_security_group_members('fake_sgid', sg_members)
        self.firewall.prepare_port_filter(port)
        self.firewall.filter_defer_apply_on()
        self.firewall.sg_members = {'fake_sgid': {
            'IPv4': [],
            'IPv6': []}}
        self.firewall.pre_sg_members = {'fake_sgid': {
            'IPv4': ['10.0.0.1'],
            'IPv6': ['fe80::1']}}
        self.firewall.remove_port_filter(port)
        self.firewall.filter_defer_apply_off()
        calls = [
            mock.call.set_members('fake_sgid', 'IPv4', ['10.0.0.1']),
            mock.call.set_members('fake_sgid', 'IPv6', ['fe80::1']),
            mock.call.get_name('fake_sgid', 'IPv4'),
            mock.call.set_name_exists('NIPv4fake_sgid'),
            mock.call.get_name('fake_sgid', 'IPv6'),
            mock.call.set_name_exists('NIPv6fake_sgid'),
            mock.call.destroy('fake_sgid', 'IPv4'),
            mock.call.destroy('fake_sgid', 'IPv6')]

        self.firewall.ipset.assert_has_calls(calls, any_order=True)

    def test_filter_defer_apply_off_with_sg_only_ipv6_rule(self):
        self.firewall.sg_rules = self._fake_sg_rules()
        self.firewall.pre_sg_rules = self._fake_sg_rules()
        self.firewall.ipset_chains = {'IPv4fake_sgid': ['10.0.0.2'],
                                      'IPv6fake_sgid': ['fe80::1']}
        self.firewall.sg_members = {'fake_sgid': {
            'IPv4': ['10.0.0.2'],
            'IPv6': ['fe80::1']}}
        self.firewall.pre_sg_members = {'fake_sgid': {
            'IPv4': ['10.0.0.2'],
            'IPv6': ['fe80::1']}}
        self.firewall.sg_rules['fake_sgid'].remove(
            {'direction': 'ingress', 'remote_group_id': 'fake_sgid',
             'ethertype': 'IPv4'})
        self.firewall.sg_rules.update()
        self.firewall._defer_apply = True
        port = self._fake_port()
        self.firewall.filtered_ports['tapfake_dev'] = port
        self.firewall._pre_defer_filtered_ports = {}
        self.firewall._pre_defer_unfiltered_ports = {}
        self.firewall.filter_defer_apply_off()
        calls = [mock.call.destroy('fake_sgid', 'IPv4')]

        self.firewall.ipset.assert_has_calls(calls, True)

    def test_sg_rule_expansion_with_remote_ips(self):
        other_ips = [('10.0.0.2', 'fa:16:3e:aa:bb:c1'),
                     ('10.0.0.3', 'fa:16:3e:aa:bb:c2'),
                     ('10.0.0.4', 'fa:16:3e:aa:bb:c3')]
        self.firewall.sg_members = {'fake_sgid': {
            'IPv4': [(FAKE_IP['IPv4'], 'fa:16:3e:aa:bb:c4'), ] + other_ips,
            'IPv6': [(FAKE_IP['IPv6'], 'fa:16:3e:aa:bb:c5'), ]}}

        port = self._fake_port()
        rule = self._fake_sg_rule_for_ethertype(_IPv4, FAKE_SGID)
        rules = self.firewall._expand_sg_rule_with_remote_ips(
            rule, port, 'ingress')
        self.assertEqual(list(rules),
                         [dict(list(rule.items()) +
                               [('source_ip_prefix', '%s/32' % ip)])
                          for ip, _mac in other_ips])

    def test_build_ipv4v6_mac_ip_list(self):
        mac_oth = 'ffff-ff0f-ffff'
        mac_unix = 'FF:FF:FF:0F:FF:FF'
        ipv4 = FAKE_IP['IPv4']
        ipv6 = FAKE_IP['IPv6']
        fake_ipv4_pair = []
        fake_ipv4_pair.append((mac_unix, ipv4))
        fake_ipv6_pair = []
        fake_ipv6_pair.append((mac_unix, ipv6))
        fake_ipv6_pair.append((mac_unix, 'fe80::fdff:ffff:fe0f:ffff'))

        mac_ipv4_pairs = []
        mac_ipv6_pairs = []

        self.firewall._build_ipv4v6_mac_ip_list(mac_oth, ipv4,
                                                mac_ipv4_pairs, mac_ipv6_pairs)
        self.assertEqual(fake_ipv4_pair, mac_ipv4_pairs)
        self.firewall._build_ipv4v6_mac_ip_list(mac_oth, ipv6,
                                                mac_ipv4_pairs, mac_ipv6_pairs)
        self.assertEqual(fake_ipv6_pair, mac_ipv6_pairs)
        # ensure that LLA is not added again for another v6 addr
        ipv62 = 'fe81::1'
        self.firewall._build_ipv4v6_mac_ip_list(mac_oth, ipv62,
                                                mac_ipv4_pairs, mac_ipv6_pairs)
        fake_ipv6_pair.append((mac_unix, ipv62))
        self.assertEqual(fake_ipv6_pair, mac_ipv6_pairs)


class OVSHybridIptablesFirewallTestCase(BaseIptablesFirewallTestCase):

    def test__populate_initial_zone_map(self):
        self.assertEqual(self._dev_zone_map,
                   self.firewall.ipconntrack._device_zone_map)

    def test__generate_device_zone(self):
        # initial data has 4097, 4098, and 4105 in use.
        # we fill from top up first.
        self.assertEqual(4106,
                   self.firewall.ipconntrack._generate_device_zone('test'))

        # once it's maxed out, it scans for gaps
        self.firewall.ipconntrack._device_zone_map['someport'] = (
            ip_conntrack.MAX_CONNTRACK_ZONES)
        for i in range(4099, 4105):
            self.assertEqual(i,
                   self.firewall.ipconntrack._generate_device_zone(i))

        # 4105 and 4106 are taken so next should be 4107
        self.assertEqual(4107,
                   self.firewall.ipconntrack._generate_device_zone('p11'))

        # take out zone 4097 and make sure it's selected
        self.firewall.ipconntrack._device_zone_map.pop('e804433b-61')
        self.assertEqual(4097,
                   self.firewall.ipconntrack._generate_device_zone('p1'))

        # fill it up and then make sure an extra throws an error
        for i in range(ip_conntrack.ZONE_START,
                       ip_conntrack.MAX_CONNTRACK_ZONES):
            self.firewall.ipconntrack._device_zone_map['dev-%s' % i] = i
        with testtools.ExpectedException(exceptions.CTZoneExhaustedError):
            self.firewall.ipconntrack._find_open_zone()

        # with it full, try again, this should trigger a cleanup
        # and return 4097
        self.assertEqual(ip_conntrack.ZONE_START,
                   self.firewall.ipconntrack._generate_device_zone('p12'))
        self.assertEqual({'p12': ip_conntrack.ZONE_START},
                   self.firewall.ipconntrack._device_zone_map)

    def test_get_device_zone(self):
        dev = {'device': 'tap1234', 'network_id': '12345678901234567'}
        # initial data has 4097, 4098, and 4105 in use.
        self.assertEqual(4106, self.firewall.ipconntrack.get_device_zone(dev))
        # should have been truncated to 11 chars
        self._dev_zone_map.update({'12345678901': 4106})
        self.assertEqual(self._dev_zone_map,
               self.firewall.ipconntrack._device_zone_map)

    def test_multiple_firewall_with_common_conntrack(self):
        self.firewall1 = iptables_firewall.OVSHybridIptablesFirewallDriver()
        self.firewall2 = iptables_firewall.OVSHybridIptablesFirewallDriver()
        self.assertEqual(id(self.firewall1.ipconntrack),
                         id(self.firewall2.ipconntrack))
