]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Iptables security group implementation for LinuxBridge
authorNachi Ueno <nachi@nttmcl.com>
Wed, 7 Nov 2012 19:00:53 +0000 (11:00 -0800)
committerAaron Rosen <arosen@nicira.com>
Tue, 8 Jan 2013 18:19:23 +0000 (10:19 -0800)
Implements bp quantum-security-groups-iptables-lb
- Added firewall driver
- Added iptables based firewall driver
- Implemented security groups for rpc support mixin classes

Change-Id: I974d2f1cae75ce4a55c2b5d820a0b42ff5661309

18 files changed:
quantum/agent/firewall.py [new file with mode: 0644]
quantum/agent/linux/iptables_firewall.py [new file with mode: 0644]
quantum/agent/linux/iptables_manager.py
quantum/agent/securitygroups_rpc.py [new file with mode: 0644]
quantum/common/constants.py
quantum/common/topics.py
quantum/common/utils.py
quantum/db/securitygroups_db.py
quantum/db/securitygroups_rpc_base.py [new file with mode: 0644]
quantum/extensions/securitygroup.py
quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
quantum/plugins/linuxbridge/db/l2network_db_v2.py
quantum/plugins/linuxbridge/lb_quantum_plugin.py
quantum/tests/unit/linuxbridge/test_lb_security_group.py [new file with mode: 0644]
quantum/tests/unit/linuxbridge/test_linuxbridge_plugin.py
quantum/tests/unit/test_extension_security_group.py
quantum/tests/unit/test_iptables_firewall.py [new file with mode: 0644]
quantum/tests/unit/test_security_groups_rpc.py [new file with mode: 0644]

diff --git a/quantum/agent/firewall.py b/quantum/agent/firewall.py
new file mode 100644 (file)
index 0000000..4795ed3
--- /dev/null
@@ -0,0 +1,105 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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 abc
+import contextlib
+
+
+class FirewallDriver(object):
+    """ Firewall Driver base class.
+
+    Defines methods that any driver providing security groups
+    and provider firewall functionality should implement.
+    Note port attribute should have information of security group ids and
+    security group rules.
+
+    the dict of port should have
+      device : interface name
+      fixed_ips: ips of the device
+      mac_address: mac_address of the device
+      security_groups: [sgid, sgid]
+      security_group_rules : [ rule, rule ]
+      the rule must contain ethertype and direction
+      the rule may contain security_group_id,
+          protocol, port_min, port_max
+          source_ip_prefix, source_port_min,
+          source_port_max, dest_ip_prefix,
+      Note: source_group_ip in REST API should be converted by this rule
+      if direction is ingress:
+        source_group_ip will be a soruce_prefix_ip
+      if direction is egress:
+        source_group_ip will be a dest_prefix_ip
+      Note: source_group_id in REST API should be converted by this rule
+      if direction is ingress:
+        source_group_id will be a list of soruce_prefix_ip
+      if direction is egress:
+        source_group_id will be a list of dest_prefix_ip
+    """
+
+    __metaclass__ = abc.ABCMeta
+
+    def prepare_port_filter(self, port):
+        """Prepare filters for the port.
+
+        This method should be called before the port is created.
+        """
+        raise NotImplementedError()
+
+    def apply_port_filter(self, port):
+        """Apply port filter.
+
+        Once this method returns, the port should be firewalled
+        appropriately. This method should as far as possible be a
+        no-op. It's vastly preferred to get everything set up in
+        prepare_port_filter.
+        """
+        raise NotImplementedError()
+
+    def update_port_filter(self, port):
+        """Refresh security group rules from data store
+
+        Gets called when an port gets added to or removed from
+        the security group the port is a member of or if the
+        group gains or looses a rule.
+        """
+        raise NotImplementedError()
+
+    def remove_port_filter(self, port):
+        """Stop filtering port"""
+        raise NotImplementedError()
+
+    def filter_defer_apply_on(self):
+        """Defer application of filtering rule"""
+        pass
+
+    def filter_defer_apply_off(self):
+        """Turn off deferral of rules and apply the rules now"""
+        pass
+
+    @property
+    def ports(self):
+        """ returns filterd ports"""
+        pass
+
+    @contextlib.contextmanager
+    def defer_apply(self):
+        """defer apply context"""
+        self.filter_defer_apply_on()
+        try:
+            yield
+        finally:
+            self.filter_defer_apply_off()
diff --git a/quantum/agent/linux/iptables_firewall.py b/quantum/agent/linux/iptables_firewall.py
new file mode 100644 (file)
index 0000000..44adcae
--- /dev/null
@@ -0,0 +1,280 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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 netaddr
+
+from quantum.agent import firewall
+from quantum.common import constants
+from quantum.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+SG_CHAIN = 'sg-chain'
+INGRESS_DIRECTION = 'ingress'
+EGRESS_DIRECTION = 'egress'
+CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i',
+                     EGRESS_DIRECTION: 'o'}
+IPTABLES_DIRECTION = {INGRESS_DIRECTION: 'physdev-out',
+                      EGRESS_DIRECTION: 'physdev-in'}
+
+
+class IptablesFirewallDriver(firewall.FirewallDriver):
+    """Driver which enforces security groups through iptables rules."""
+
+    def __init__(self, iptables_manager):
+        self.iptables = iptables_manager
+
+        # list of port which has security group
+        self.filtered_ports = {}
+        self._add_fallback_chain_v4v6()
+
+    @property
+    def ports(self):
+        return self.filtered_ports
+
+    def prepare_port_filter(self, port):
+        LOG.debug(_("Preparing device (%s) filter"), port['device'])
+        self._remove_chains()
+        self.filtered_ports[port['device']] = port
+        # each security group has it own chains
+        self._setup_chains()
+        self.iptables.apply()
+
+    def update_port_filter(self, port):
+        LOG.debug(_("Updating device (%s) filter"), port['device'])
+        if not port['device'] in self.filtered_ports:
+            LOG.info(_('Attempted to update port filter which is not '
+                     'filtered %s') % port['device'])
+            return
+        self._remove_chains()
+        self.filtered_ports[port['device']] = port
+        self._setup_chains()
+        self.iptables.apply()
+
+    def remove_port_filter(self, port):
+        LOG.debug(_("Removing device (%s) filter"), port['device'])
+        if not self.filtered_ports.get(port['device']):
+            LOG.info(_('Attempted to remove port filter which is not '
+                     'filtered %r'), port)
+            return
+        self._remove_chains()
+        self.filtered_ports.pop(port['device'], None)
+        self._setup_chains()
+        self.iptables.apply()
+
+    def _setup_chains(self):
+        """Setup ingress and egress chain for a port. """
+        self._add_chain_by_name_v4v6(SG_CHAIN)
+        for port in self.filtered_ports.values():
+            self._setup_chain(port, INGRESS_DIRECTION)
+            self._setup_chain(port, EGRESS_DIRECTION)
+            self.iptables.ipv4['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
+            self.iptables.ipv6['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
+
+    def _remove_chains(self):
+        """Remove ingress and egress chain for a port"""
+        for port in self.filtered_ports.values():
+            self._remove_chain(port, INGRESS_DIRECTION)
+            self._remove_chain(port, EGRESS_DIRECTION)
+        self._remove_chain_by_name_v4v6(SG_CHAIN)
+
+    def _setup_chain(self, port, DIRECTION):
+        self._add_chain(port, DIRECTION)
+        self._add_rule_by_security_group(port, DIRECTION)
+
+    def _remove_chain(self, port, DIRECTION):
+        chain_name = self._port_chain_name(port, DIRECTION)
+        self._remove_chain_by_name_v4v6(chain_name)
+
+    def _add_fallback_chain_v4v6(self):
+        self.iptables.ipv4['filter'].add_chain('sg-fallback')
+        self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
+        self.iptables.ipv6['filter'].add_chain('sg-fallback')
+        self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
+
+    def _add_chain_by_name_v4v6(self, chain_name):
+        self.iptables.ipv6['filter'].add_chain(chain_name)
+        self.iptables.ipv4['filter'].add_chain(chain_name)
+
+    def _remove_chain_by_name_v4v6(self, chain_name):
+        self.iptables.ipv4['filter'].ensure_remove_chain(chain_name)
+        self.iptables.ipv6['filter'].ensure_remove_chain(chain_name)
+
+    def _add_rule_to_chain_v4v6(self, chain_name, ipv4_rules, ipv6_rules):
+        for rule in ipv4_rules:
+            self.iptables.ipv4['filter'].add_rule(chain_name, rule)
+
+        for rule in ipv6_rules:
+            self.iptables.ipv6['filter'].add_rule(chain_name, rule)
+
+    def _add_chain(self, port, direction):
+        chain_name = self._port_chain_name(port, direction)
+        self._add_chain_by_name_v4v6(chain_name)
+
+        # Note(nati) jump to the security group chain (SG_CHAIN)
+        # This is needed because the packet may much two rule in port
+        # if the two port is in the same host
+        # We accept the packet at the end of SG_CHAIN.
+
+        # jump to the security group chain
+        device = port['device']
+        jump_rule = ['-m physdev --physdev-is-bridged --%s '
+                     '%s -j $%s' % (IPTABLES_DIRECTION[direction],
+                                    device,
+                                    SG_CHAIN)]
+        self._add_rule_to_chain_v4v6('FORWARD', jump_rule, jump_rule)
+
+        # jump to the chain based on the device
+        jump_rule = ['-m physdev --physdev-is-bridged --%s '
+                     '%s -j $%s' % (IPTABLES_DIRECTION[direction],
+                                    device,
+                                    chain_name)]
+        self._add_rule_to_chain_v4v6(SG_CHAIN, jump_rule, jump_rule)
+
+        if direction == EGRESS_DIRECTION:
+            self._add_rule_to_chain_v4v6('INPUT', jump_rule, jump_rule)
+
+    def _split_sgr_by_ethertype(self, security_group_rules):
+        ipv4_sg_rules = []
+        ipv6_sg_rules = []
+        for rule in security_group_rules:
+            if rule.get('ethertype') == constants.IPv4:
+                ipv4_sg_rules.append(rule)
+            elif rule.get('ethertype') == constants.IPv6:
+                if rule.get('protocol') == 'icmp':
+                    rule['protocol'] = 'icmpv6'
+                ipv6_sg_rules.append(rule)
+        return ipv4_sg_rules, ipv6_sg_rules
+
+    def _select_sgr_by_direction(self, port, direction):
+        return [rule
+                for rule in port.get('security_group_rules', [])
+                if rule['direction'] == direction]
+
+    def _arp_spoofing_rule(self, port):
+        return ['-m mac ! --mac-source %s -j DROP' % port['mac_address']]
+
+    def _ip_spoofing_rule(self, port, ipv4_rules, ipv6_rules):
+        #Note(nati) allow dhcp or RA packet
+        ipv4_rules += ['-p udp --sport 68 --dport 67 -j RETURN']
+        ipv6_rules += ['-p icmpv6 -j RETURN']
+        for ip in port['fixed_ips']:
+            if netaddr.IPAddress(ip).version == 4:
+                ipv4_rules += ['! -s %s -j DROP' % ip]
+            else:
+                ipv6_rules += ['! -s %s -j DROP' % ip]
+
+    def _drop_dhcp_rule(self):
+        #Note(nati) Drop dhcp packet from VM
+        return ['-p udp --sport 67 --dport 68 -j DROP']
+
+    def _add_rule_by_security_group(self, port, direction):
+        chain_name = self._port_chain_name(port, direction)
+        # select rules for current direction
+        security_group_rules = self._select_sgr_by_direction(port, direction)
+        # split groups by ip version
+        # for ipv4, iptables command is used
+        # for ipv6, iptables6 command is used
+        ipv4_sg_rules, ipv6_sg_rules = self._split_sgr_by_ethertype(
+            security_group_rules)
+        ipv4_iptables_rule = []
+        ipv6_iptables_rule = []
+        if direction == EGRESS_DIRECTION:
+            ipv4_iptables_rule += self._arp_spoofing_rule(port)
+            ipv6_iptables_rule += self._arp_spoofing_rule(port)
+            self._ip_spoofing_rule(port,
+                                   ipv4_iptables_rule,
+                                   ipv6_iptables_rule)
+            ipv4_iptables_rule += self._drop_dhcp_rule()
+        ipv4_iptables_rule += self._convert_sgr_to_iptables_rules(
+            ipv4_sg_rules)
+        ipv6_iptables_rule += self._convert_sgr_to_iptables_rules(
+            ipv6_sg_rules)
+        self._add_rule_to_chain_v4v6(chain_name,
+                                     ipv4_iptables_rule,
+                                     ipv6_iptables_rule)
+
+    def _convert_sgr_to_iptables_rules(self, security_group_rules):
+        iptables_rules = []
+        self._drop_invalid_packets(iptables_rules)
+        self._allow_established(iptables_rules)
+        for rule in security_group_rules:
+            args = ['-j RETURN']
+            args += self._protocol_arg(rule.get('protocol'))
+            args += self._port_arg('dport',
+                                   rule.get('protocol'),
+                                   rule.get('port_range_min'),
+                                   rule.get('port_range_max'))
+            args += self._port_arg('sport',
+                                   rule.get('protocol'),
+                                   rule.get('source_port_range_min'),
+                                   rule.get('source_port_range_max'))
+            args += self._ip_prefix_arg('s',
+                                        rule.get('source_ip_prefix'))
+            args += self._ip_prefix_arg('d',
+                                        rule.get('dest_ip_prefix'))
+            iptables_rules += [' '.join(args)]
+
+        iptables_rules += ['-j $sg-fallback']
+
+        return iptables_rules
+
+    def _drop_invalid_packets(self, iptables_rules):
+        # Always drop invalid packets
+        iptables_rules += ['-m state --state ' 'INVALID -j DROP']
+        return iptables_rules
+
+    def _allow_established(self, iptables_rules):
+        # Allow established connections
+        iptables_rules += ['-m state --state ESTABLISHED,RELATED -j RETURN']
+        return iptables_rules
+
+    def _protocol_arg(self, protocol):
+        if protocol:
+            return ['-p', protocol]
+        return []
+
+    def _port_arg(self, direction, protocol, port_range_min, port_range_max):
+        if not (protocol in ['udp', 'tcp'] and port_range_min):
+            return []
+
+        if port_range_min == port_range_max:
+            return ['--%s' % direction, '%s' % (port_range_min,)]
+        else:
+            return ['-m', 'multiport',
+                    '--%ss' % direction,
+                    '%s:%s' % (port_range_min, port_range_max)]
+
+    def _ip_prefix_arg(self, direction, ip_prefix):
+        #NOTE (nati) : source_group_id is converted to list of source_
+        # ip_prefix in server side
+        if ip_prefix:
+            return ['-%s' % direction, ip_prefix]
+        return []
+
+    def _port_chain_name(self, port, direction):
+        #Note (nati) make chain name short less than 28 char
+        # with extra prefix
+        # ( see comment in iptables_manager )
+        return '%s%s' % (CHAIN_NAME_PREFIX[direction],
+                         port['device'][3:13])
+
+    def filter_defer_apply_on(self):
+        self.iptables.defer_apply_on()
+
+    def filter_defer_apply_off(self):
+        self.iptables.defer_apply_off()
index 5201820f030b9c1f035bd3b952535a95ad0ebfdf..6c716c39f9bbe436e5a48841a3f15c79ad51c757 100644 (file)
@@ -25,6 +25,7 @@ import inspect
 import os
 
 from quantum.agent.linux import utils
+from quantum.openstack.common import lockutils
 from quantum.openstack.common import log as logging
 
 LOG = logging.getLogger(__name__)
@@ -91,6 +92,24 @@ class IptablesTable(object):
         else:
             self.unwrapped_chains.add(name)
 
+    def _select_chain_set(self, wrap):
+        if wrap:
+            return self.chains
+        else:
+            return self.unwrapped_chains
+
+    def ensure_remove_chain(self, name, wrap=True):
+        """Ensure the chain is removed.
+
+        This removal "cascades". All rule in the chain are removed, as are
+        all rules in other chains that jump to it.
+        """
+        chain_set = self._select_chain_set(wrap)
+        if name not in chain_set:
+            return
+
+        self.remove_chain(name, wrap)
+
     def remove_chain(self, name, wrap=True):
         """Remove named chain.
 
@@ -100,10 +119,7 @@ class IptablesTable(object):
         If the chain is not found, this is merely logged.
 
         """
-        if wrap:
-            chain_set = self.chains
-        else:
-            chain_set = self.unwrapped_chains
+        chain_set = self._select_chain_set(wrap)
 
         if name not in chain_set:
             LOG.warn(('Attempted to remove chain %s which does not exist'),
@@ -112,7 +128,6 @@ class IptablesTable(object):
 
         chain_set.remove(name)
         self.rules = filter(lambda r: r.chain != name, self.rules)
-
         if wrap:
             jump_snippet = '-j %s-%s' % (binary_name, name)
         else:
@@ -201,6 +216,7 @@ class IptablesManager(object):
         self.use_ipv6 = use_ipv6
         self.root_helper = root_helper
         self.namespace = namespace
+        self.iptables_apply_deferred = False
 
         self.ipv4 = {'filter': IptablesTable()}
         self.ipv6 = {'filter': IptablesTable()}
@@ -261,7 +277,21 @@ class IptablesManager(object):
             self.ipv4['nat'].add_chain('float-snat')
             self.ipv4['nat'].add_rule('snat', '-j $float-snat')
 
+    def defer_apply_on(self):
+        self.iptables_apply_deferred = True
+
+    def defer_apply_off(self):
+        self.iptables_apply_deferred = False
+        self._apply()
+
     def apply(self):
+        if self.iptables_apply_deferred:
+            return
+
+        self._apply()
+
+    @lockutils.synchronized('iptables', 'quantum-', external=True)
+    def _apply(self):
         """Apply the current in-memory set of iptables rules.
 
         This will blow away any rules left over from previous runs of the
diff --git a/quantum/agent/securitygroups_rpc.py b/quantum/agent/securitygroups_rpc.py
new file mode 100644 (file)
index 0000000..e947c66
--- /dev/null
@@ -0,0 +1,179 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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.
+#
+
+from quantum.agent.linux import iptables_firewall
+from quantum.agent.linux import iptables_manager
+from quantum.common import topics
+from quantum.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+SG_RPC_VERSION = "1.1"
+
+
+class SecurityGroupServerRpcApiMixin(object):
+    """A mix-in that enable SecurityGroup support in plugin rpc
+    """
+    def security_group_rules_for_devices(self, context, devices):
+        LOG.debug(_("Get security group rules "
+                    "for devices via rpc %r"), devices)
+        return self.call(context,
+                         self.make_msg('security_group_rules_for_devices',
+                                       devices=devices),
+                         version=SG_RPC_VERSION,
+                         topic=self.topic)
+
+
+class SecurityGroupAgentRpcCallbackMixin(object):
+    """A mix-in that enable SecurityGroup agent
+    support in agent implementations.
+    """
+
+    def security_groups_rule_updated(self, context, **kwargs):
+        """ callback for security group rule update
+
+        :param security_groups: list of updated security_groups
+        """
+        security_groups = kwargs.get('security_groups', [])
+        LOG.debug(
+            _("Security group rule updated on remote: %s"), security_groups)
+        self.agent.security_groups_rule_updated(security_groups)
+
+    def security_groups_member_updated(self, context, **kwargs):
+        """ callback for security group member update
+
+        :param security_groups: list of updated security_groups
+        """
+        security_groups = kwargs.get('security_groups', [])
+        LOG.debug(
+            _("Security group member updated on remote: %s"), security_groups)
+        self.agent.security_groups_member_updated(security_groups)
+
+    def security_groups_provider_updated(self, context, **kwargs):
+        """ callback for security group provider update
+
+        """
+        LOG.debug(_("Provider rule updated"))
+        self.agent.security_groups_provider_updated()
+
+
+class SecurityGroupAgentRpcMixin(object):
+    """A mix-in that enable SecurityGroup agent
+    support in agent implementations.
+    """
+
+    def init_firewall(self):
+        LOG.debug(_("Init firewall settings"))
+        ip_manager = iptables_manager.IptablesManager(
+            root_helper=self.root_helper,
+            use_ipv6=True)
+        self.firewall = iptables_firewall.IptablesFirewallDriver(ip_manager)
+
+    def prepare_devices_filter(self, device_ids):
+        if not device_ids:
+            return
+        LOG.info(_("Preparing filters for devices %s"), device_ids)
+        devices = self.plugin_rpc.security_group_rules_for_devices(
+            self.context, list(device_ids))
+        with self.firewall.defer_apply():
+            for device in devices.values():
+                self.firewall.prepare_port_filter(device)
+
+    def security_groups_rule_updated(self, security_groups):
+        LOG.info(_("Security group "
+                   "rule updated %r"), security_groups)
+        self._security_group_updated(
+            security_groups,
+            'security_groups')
+
+    def security_groups_member_updated(self, security_groups):
+        LOG.info(_("Security group "
+                   "member updated %r"), security_groups)
+        self._security_group_updated(
+            security_groups,
+            'security_group_source_groups')
+
+    def _security_group_updated(self, security_groups, attribute):
+        #check need update or not
+        for device in self.firewall.ports.values():
+            if set(device.get(attribute,
+                              [])).intersection(
+                    set(security_groups)):
+                    self.refresh_firewall()
+                    return
+
+    def security_groups_provider_updated(self):
+        LOG.info(_("Provider rule updated"))
+        self.refresh_firewall()
+
+    def remove_devices_filter(self, device_ids):
+        if not device_ids:
+            return
+        LOG.info(_("Remove device filter for %r"), device_ids)
+        with self.firewall.defer_apply():
+            for device_id in device_ids:
+                device = self.firewall.ports.get(device_id)
+                if not device:
+                    continue
+                self.firewall.remove_port_filter(device)
+
+    def refresh_firewall(self):
+        LOG.info(_("Refresh firewall rules"))
+        device_ids = self.firewall.ports.keys()
+        if not device_ids:
+            return
+        devices = self.plugin_rpc.security_group_rules_for_devices(
+            self.context, device_ids)
+        with self.firewall.defer_apply():
+            for device in devices.values():
+                LOG.debug(_("Update port filter for %s"), device)
+                self.firewall.update_port_filter(device)
+
+
+class SecurityGroupAgentRpcApiMixin(object):
+
+    def _get_security_group_topic(self):
+        return topics.get_topic_name(self.topic,
+                                     topics.SECURITY_GROUP,
+                                     topics.UPDATE)
+
+    def security_groups_rule_updated(self, context, security_groups):
+        """ notify rule updated security groups """
+        if not security_groups:
+            return
+        self.fanout_cast(context,
+                         self.make_msg('security_groups_rule_updated',
+                                       security_groups=security_groups),
+                         version=SG_RPC_VERSION,
+                         topic=self._get_security_group_topic())
+
+    def security_groups_member_updated(self, context, security_groups):
+        """ notify member updated security groups """
+        if not security_groups:
+            return
+        self.fanout_cast(context,
+                         self.make_msg('security_groups_member_updated',
+                                       security_groups=security_groups),
+                         version=SG_RPC_VERSION,
+                         topic=self._get_security_group_topic())
+
+    def security_groups_provider_updated(self, context):
+        """ notify provider updated security groups """
+        self.fanout_cast(context,
+                         self.make_msg('security_groups_provider_updated'),
+                         version=SG_RPC_VERSION,
+                         topic=self._get_security_group_topic())
index 2f4efb64db7231e5fea681438b19df0b0e88dd16..9eac348e2ca96e3f64b9f0d8a35a44a4a0df683c 100644 (file)
@@ -26,5 +26,10 @@ PORT_STATUS_ERROR = 'ERROR'
 DEVICE_OWNER_ROUTER_INTF = "network:router_interface"
 DEVICE_OWNER_ROUTER_GW = "network:router_gateway"
 DEVICE_OWNER_FLOATINGIP = "network:floatingip"
+DEVICE_OWNER_DHCP = "network:dhcp"
+
 FLOATINGIP_KEY = '_floatingips'
 INTERFACE_KEY = '_interfaces'
+
+IPv4 = 'IPv4'
+IPv6 = 'IPv6'
index 15621ef59d00e8e1593ac5055fa697e02c726937..c9f3b115c03576db4fb7da59b6a120b93eaa9716 100644 (file)
@@ -16,6 +16,7 @@
 NETWORK = 'network'
 SUBNET = 'subnet'
 PORT = 'port'
+SECURITY_GROUP = 'security_group'
 
 CREATE = 'create'
 DELETE = 'delete'
index aeca0484e9b11bc302bc7424d9231f56b5d98fe8..cc91c39af516a75f3c6e27b763fb8ce76f7c336e 100644 (file)
@@ -150,3 +150,15 @@ def parse_mappings(mapping_list, unique_values=True):
 
 def get_hostname():
     return socket.getfqdn()
+
+
+def compare_elements(a, b):
+    """ compare elements if a and b have same elements
+
+    This method doesn't consider ordering
+    """
+    if a is None:
+        a = []
+    if b is None:
+        b = []
+    return set(a) == set(b)
index f160522c446ab4287e929dd325c1a764bd2f0574..7678699789abf98819a0182b769599c3e2ebb867 100644 (file)
@@ -22,6 +22,8 @@ from sqlalchemy import orm
 from sqlalchemy.orm import exc
 from sqlalchemy.orm import scoped_session
 
+from quantum.api.v2 import attributes as attr
+from quantum.common import utils
 from quantum.db import model_base
 from quantum.db import models_v2
 from quantum.extensions import securitygroup as ext_sg
@@ -403,9 +405,10 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
     def _extend_port_dict_security_group(self, context, port):
         filters = {'port_id': [port['id']]}
         fields = {'security_group_id': None}
-        port[ext_sg.SECURITYGROUP] = []
         security_group_id = self._get_port_security_group_bindings(
             context, filters, fields)
+
+        port[ext_sg.SECURITYGROUP] = []
         for security_group_id in security_group_id:
             port[ext_sg.SECURITYGROUP].append(
                 security_group_id['security_group_id'])
@@ -413,7 +416,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
 
     def _process_port_create_security_group(self, context, port_id,
                                             security_group_id):
-        if not security_group_id:
+        if not attr.is_attr_set(security_group_id):
             return
         for security_group_id in security_group_id:
             self._create_port_security_group_binding(context, port_id,
@@ -445,8 +448,10 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
 
     def _validate_security_groups_on_port(self, context, port):
         p = port['port']
-        if not p.get(ext_sg.SECURITYGROUP):
+        if not attr.is_attr_set(p.get(ext_sg.SECURITYGROUP)):
             return
+        if p.get('device_owner') and p['device_owner'].startswith('network:'):
+            raise ext_sg.SecurityGroupInvalidDeviceOwner()
 
         valid_groups = self.get_security_groups(context, fields={'id': None})
         valid_groups_set = set([x['id'] for x in valid_groups])
@@ -455,3 +460,17 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
         if invalid_sg_set:
             msg = ' '.join(str(x) for x in invalid_sg_set)
             raise ext_sg.SecurityGroupNotFound(id=msg)
+
+    def _ensure_default_security_group_on_port(self, context, port):
+        # we don't apply security groups for dhcp, router
+        if (port['port'].get('device_owner') and
+                port['port']['device_owner'].startswith('network:')):
+            return
+        tenant_id = self._get_tenant_id_for_create(context,
+                                                   port['port'])
+        default_sg = self._ensure_default_security_group(context, tenant_id)
+        if attr.is_attr_set(port['port'].get(ext_sg.SECURITYGROUP)):
+            sgids = port['port'].get(ext_sg.SECURITYGROUP)
+        else:
+            sgids = [default_sg]
+        port['port'][ext_sg.SECURITYGROUP] = sgids
diff --git a/quantum/db/securitygroups_rpc_base.py b/quantum/db/securitygroups_rpc_base.py
new file mode 100644 (file)
index 0000000..cbb7ba7
--- /dev/null
@@ -0,0 +1,259 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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 netaddr
+
+from quantum.common import constants as q_const
+from quantum.db import models_v2
+from quantum.db import securitygroups_db as sg_db
+from quantum.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+IP_MASK = {q_const.IPv4: 32,
+           q_const.IPv6: 128}
+
+
+DIRECTION_IP_PREFIX = {'ingress': 'source_ip_prefix',
+                       'egress': 'dest_ip_prefix'}
+
+
+class SecurityGroupServerRpcMixin(sg_db.SecurityGroupDbMixin):
+
+    def create_security_group_rule(self, context, security_group_rule):
+        bulk_rule = {'security_group_rules': [security_group_rule]}
+        rule = self.create_security_group_rule_bulk_native(context,
+                                                           bulk_rule)[0]
+        sgids = [rule['security_group_id']]
+        self.notifier.security_groups_rule_updated(context, sgids)
+        return rule
+
+    def create_security_group_rule_bulk(self, context,
+                                        security_group_rule):
+        rules = super(SecurityGroupServerRpcMixin,
+                      self).create_security_group_rule_bulk_native(
+                          context, security_group_rule)
+        sgids = set([r['security_group_id'] for r in rules])
+        self.notifier.security_groups_rule_updated(context, list(sgids))
+        return rules
+
+    def delete_security_group_rule(self, context, sgrid):
+        rule = self.get_security_group_rule(context, sgrid)
+        super(SecurityGroupServerRpcMixin,
+              self).delete_security_group_rule(context, sgrid)
+        self.notifier.security_groups_rule_updated(context,
+                                                   [rule['security_group_id']])
+
+
+class SecurityGroupServerRpcCallbackMixin(object):
+    """A mix-in that enable SecurityGroup agent
+
+    support in plugin implementations.
+    """
+
+    def security_group_rules_for_devices(self, context, **kwargs):
+        """ return security group rules for each port
+
+        also convert source_group_id rule
+        to source_ip_prefix rule
+
+        :params devices: list of devices
+        :returns: port correspond to the devices with security group rules
+        """
+        devices = kwargs.get('devices')
+
+        ports = {}
+        for device in devices:
+            port = self.get_port_from_device(device)
+            if not port:
+                continue
+            if port['device_owner'].startswith('network:'):
+                continue
+            ports[port['id']] = port
+        return self._security_group_rules_for_ports(context, ports)
+
+    def _select_rules_for_ports(self, context, ports):
+        if not ports:
+            return []
+        sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
+        sg_binding_sgid = sg_db.SecurityGroupPortBinding.security_group_id
+
+        sgr_sgid = sg_db.SecurityGroupRule.security_group_id
+
+        query = context.session.query(sg_db.SecurityGroupPortBinding,
+                                      sg_db.SecurityGroupRule)
+        query = query.join(sg_db.SecurityGroupRule,
+                           sgr_sgid == sg_binding_sgid)
+        query = query.filter(sg_binding_port.in_(ports.keys()))
+        return query.all()
+
+    def _select_ips_for_source_group(self, context, source_group_ids):
+        ips_by_group = {}
+        if not source_group_ids:
+            return ips_by_group
+        for source_group_id in source_group_ids:
+            ips_by_group[source_group_id] = []
+
+        ip_port = models_v2.IPAllocation.port_id
+        sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
+        sg_binding_sgid = sg_db.SecurityGroupPortBinding.security_group_id
+
+        query = context.session.query(sg_binding_sgid,
+                                      models_v2.IPAllocation.ip_address)
+        query = query.join(models_v2.IPAllocation,
+                           ip_port == sg_binding_port)
+        query = query.filter(sg_binding_sgid.in_(source_group_ids))
+        ip_in_db = query.all()
+        for security_group_id, ip_address in ip_in_db:
+            ips_by_group[security_group_id].append(ip_address)
+        return ips_by_group
+
+    def _select_source_group_ids(self, ports):
+        source_group_ids = []
+        for port in ports.values():
+            for rule in port.get('security_group_rules'):
+                source_group_id = rule.get('source_group_id')
+                if source_group_id:
+                    source_group_ids.append(source_group_id)
+        return source_group_ids
+
+    def _select_network_ids(self, ports):
+        return set((port['network_id'] for port in ports.values()))
+
+    def _select_dhcp_ips_for_network_ids(self, context, network_ids):
+        if not network_ids:
+            return {}
+        query = context.session.query(models_v2.Port,
+                                      models_v2.IPAllocation.ip_address)
+        query = query.join(models_v2.IPAllocation)
+        query = query.filter(models_v2.Port.network_id.in_(network_ids))
+        owner = q_const.DEVICE_OWNER_DHCP
+        query = query.filter(models_v2.Port.device_owner == owner)
+        ips = {}
+
+        for network_id in network_ids:
+            ips[network_id] = []
+
+        for port, ip in query.all():
+            ips[port['network_id']].append(ip)
+        return ips
+
+    def _convert_source_group_id_to_ip_prefix(self, context, ports):
+        source_group_ids = self._select_source_group_ids(ports)
+        ips = self._select_ips_for_source_group(context, source_group_ids)
+        for port in ports.values():
+            updated_rule = []
+            for rule in port.get('security_group_rules'):
+                source_group_id = rule.get('source_group_id')
+                direction = rule.get('direction')
+                direction_ip_prefix = DIRECTION_IP_PREFIX[direction]
+                if not source_group_id:
+                    updated_rule.append(rule)
+                    continue
+
+                port['security_group_source_groups'].append(source_group_id)
+                base_rule = rule
+                for ip in ips[source_group_id]:
+                    if ip in port.get('fixed_ips', []):
+                        continue
+                    ip_rule = base_rule.copy()
+                    version = netaddr.IPAddress(ip).version
+                    ethertype = 'IPv%s' % version
+                    if base_rule['ethertype'] != ethertype:
+                        continue
+                    ip_rule[direction_ip_prefix] = "%s/%s" % (
+                        ip, IP_MASK[ethertype])
+                    updated_rule.append(ip_rule)
+            port['security_group_rules'] = updated_rule
+        return ports
+
+    def _add_default_egress_rule(self, port, ethertype, ips):
+        """ Adding default egress rule which allows all egress traffic. """
+        egress_rule = [r for r in port['security_group_rules']
+                       if (r['direction'] == 'egress' and
+                           r['ethertype'] == ethertype)]
+        if len(egress_rule) > 0:
+            return
+        for ip in port['fixed_ips']:
+            version = netaddr.IPAddress(ip).version
+            if "IPv%s" % version == ethertype:
+                default_egress_rule = {'direction': 'egress',
+                                       'ethertype': ethertype}
+                port['security_group_rules'].append(default_egress_rule)
+                return
+
+    def _add_ingress_dhcp_rule(self, port, ips):
+        dhcp_ips = ips.get(port['network_id'])
+        for dhcp_ip in dhcp_ips:
+            if not netaddr.IPAddress(dhcp_ip).version == 4:
+                return
+
+            dhcp_rule = {'direction': 'ingress',
+                         'ethertype': q_const.IPv4,
+                         'protocol': 'udp',
+                         'port_range_min': 68,
+                         'port_range_max': 68,
+                         'source_port_range_min': 67,
+                         'source_port_range_max': 67}
+            dhcp_rule['source_ip_prefix'] = "%s/%s" % (dhcp_ip,
+                                                       IP_MASK[q_const.IPv4])
+            port['security_group_rules'].append(dhcp_rule)
+
+    def _add_ingress_ra_rule(self, port, ips):
+        ra_ips = ips.get(port['network_id'])
+        for ra_ip in ra_ips:
+            if not netaddr.IPAddress(ra_ip).version == 6:
+                return
+
+            ra_rule = {'direction': 'ingress',
+                       'ethertype': q_const.IPv6,
+                       'protocol': 'icmp'}
+            ra_rule['source_ip_prefix'] = "%s/%s" % (ra_ip,
+                                                     IP_MASK[q_const.IPv6])
+            port['security_group_rules'].append(ra_rule)
+
+    def _apply_provider_rule(self, context, ports):
+        network_ids = self._select_network_ids(ports)
+        ips = self._select_dhcp_ips_for_network_ids(context, network_ids)
+        for port in ports.values():
+            self._add_default_egress_rule(port, q_const.IPv4, ips)
+            self._add_default_egress_rule(port, q_const.IPv6, ips)
+            self._add_ingress_ra_rule(port, ips)
+            self._add_ingress_dhcp_rule(port, ips)
+
+    def _security_group_rules_for_ports(self, context, ports):
+        rules_in_db = self._select_rules_for_ports(context, ports)
+        for (binding, rule_in_db) in rules_in_db:
+            port_id = binding['port_id']
+            port = ports[port_id]
+            direction = rule_in_db['direction']
+            rule_dict = {
+                'security_group_id': rule_in_db['security_group_id'],
+                'direction': direction,
+                'ethertype': rule_in_db['ethertype'],
+            }
+            for key in ('protocol', 'port_range_min', 'port_range_max',
+                        'source_ip_prefix', 'source_group_id'):
+                if rule_in_db.get(key):
+                    if key == 'source_ip_prefix' and direction == 'egress':
+                        rule_dict['dest_ip_prefix'] = rule_in_db[key]
+                        continue
+                    rule_dict[key] = rule_in_db[key]
+            port['security_group_rules'].append(rule_dict)
+        self._apply_provider_rule(context, ports)
+        return self._convert_source_group_id_to_ip_prefix(context, ports)
index a887da429928882e16e2de4fae21ca2c67a2190d..ed6a151788b38f95fe450fb4da4c6e96a09804db 100644 (file)
@@ -40,6 +40,10 @@ class SecurityGroupInvalidPortValue(qexception.InvalidInput):
     message = _("Invalid value for port %(port)s")
 
 
+class SecurityGroupInvalidDeviceOwner(qexception.InvalidInput):
+    message = _("Security Group can't be applied to network ports.")
+
+
 class SecurityGroupInUse(qexception.InUse):
     message = _("Security Group %(id)s in use.")
 
@@ -208,7 +212,7 @@ EXTENDED_ATTRIBUTES_2_0 = {
     'ports': {SECURITYGROUP: {'allow_post': True,
                               'allow_put': True,
                               'is_visible': True,
-                              'default': None}}}
+                              'default': attr.ATTR_NOT_SPECIFIED}}}
 security_group_quota_opts = [
     cfg.IntOpt('quota_security_group',
                default=10,
index 2b8d6783f1771339af62a32746e6532a401ba96b..f5da45fd191b23062f1aef3cb97d14085153c524 100755 (executable)
@@ -32,6 +32,7 @@ import pyudev
 from quantum.agent.linux import ip_lib
 from quantum.agent.linux import utils
 from quantum.agent import rpc as agent_rpc
+from quantum.agent import securitygroups_rpc as sg_rpc
 from quantum.common import config as logging_config
 from quantum.common import topics
 from quantum.common import utils as q_utils
@@ -388,14 +389,17 @@ class LinuxBridge:
             LOG.debug(_("Done deleting subinterface %s"), interface)
 
 
-class LinuxBridgeRpcCallbacks():
+class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
 
     # Set RPC API version to 1.0 by default.
-    RPC_API_VERSION = '1.0'
+    # history
+    #   1.1 Support Security Group RPC
+    RPC_API_VERSION = '1.1'
 
-    def __init__(self, context, linux_br):
+    def __init__(self, context, agent):
         self.context = context
-        self.linux_br = linux_br
+        self.agent = agent
+        self.linux_br = agent.linux_br
 
     def network_delete(self, context, **kwargs):
         LOG.debug(_("network_delete received"))
@@ -407,6 +411,9 @@ class LinuxBridgeRpcCallbacks():
     def port_update(self, context, **kwargs):
         LOG.debug(_("port_update received"))
         port = kwargs.get('port')
+        if 'security_groups' in port:
+            self.agent.refresh_firewall()
+
         if port['admin_state_up']:
             vlan_id = kwargs.get('vlan_id')
             physical_network = kwargs.get('physical_network')
@@ -429,7 +436,12 @@ class LinuxBridgeRpcCallbacks():
         return dispatcher.RpcDispatcher([self])
 
 
-class LinuxBridgeQuantumAgentRPC:
+class LinuxBridgePluginApi(agent_rpc.PluginApi,
+                           sg_rpc.SecurityGroupServerRpcApiMixin):
+    pass
+
+
+class LinuxBridgeQuantumAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
 
     def __init__(self, interface_mappings, polling_interval,
                  root_helper):
@@ -437,6 +449,7 @@ class LinuxBridgeQuantumAgentRPC:
         self.root_helper = root_helper
         self.setup_linux_bridge(interface_mappings)
         self.setup_rpc(interface_mappings.values())
+        self.init_firewall()
 
     def setup_rpc(self, physical_interfaces):
         if physical_interfaces:
@@ -453,17 +466,18 @@ class LinuxBridgeQuantumAgentRPC:
         LOG.info(_("RPC agent_id: %s"), self.agent_id)
 
         self.topic = topics.AGENT
-        self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
+        self.plugin_rpc = LinuxBridgePluginApi(topics.PLUGIN)
 
         # RPC network init
         self.context = context.get_admin_context_without_session()
         # Handle updates from service
         self.callbacks = LinuxBridgeRpcCallbacks(self.context,
-                                                 self.linux_br)
+                                                 self)
         self.dispatcher = self.callbacks.create_rpc_dispatcher()
         # Define the listening consumers for the agent
         consumers = [[topics.PORT, topics.UPDATE],
-                     [topics.NETWORK, topics.DELETE]]
+                     [topics.NETWORK, topics.DELETE],
+                     [topics.SECURITY_GROUP, topics.UPDATE]]
         self.connection = agent_rpc.create_consumers(self.dispatcher,
                                                      self.topic,
                                                      consumers)
@@ -515,6 +529,7 @@ class LinuxBridgeQuantumAgentRPC:
 
     def treat_devices_added(self, devices):
         resync = False
+        self.prepare_devices_filter(devices)
         for device in devices:
             LOG.debug(_("Port %s added"), device)
             try:
@@ -544,6 +559,7 @@ class LinuxBridgeQuantumAgentRPC:
 
     def treat_devices_removed(self, devices):
         resync = False
+        self.remove_devices_filter(devices)
         for device in devices:
             LOG.info(_("Attachment %s removed"), device)
             try:
@@ -597,10 +613,7 @@ class LinuxBridgeQuantumAgentRPC:
 def main():
     eventlet.monkey_patch()
     cfg.CONF(args=sys.argv, project='quantum')
-
-    # (TODO) gary - swap with common logging
     logging_config.setup_logging(cfg.CONF)
-
     try:
         interface_mappings = q_utils.parse_mappings(
             cfg.CONF.LINUX_BRIDGE.physical_interface_mappings)
index 9e69306587ccd13f221ab40ad9c8ada2c50a5593..ebe27a45bdb3d8eae7eb7c874f430ad73ebf5a1b 100644 (file)
@@ -18,7 +18,9 @@ from sqlalchemy.orm import exc
 
 from quantum.common import exceptions as q_exc
 import quantum.db.api as db
+from quantum import manager
 from quantum.db import models_v2
+from quantum.db import securitygroups_db as sg_db
 from quantum.openstack.common import cfg
 from quantum.openstack.common import log as logging
 from quantum.plugins.linuxbridge.common import config
@@ -192,13 +194,28 @@ def get_port_from_device(device):
     """Get port from database"""
     LOG.debug(_("get_port_from_device() called"))
     session = db.get_session()
-    ports = session.query(models_v2.Port).all()
-    if not ports:
+    sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
+
+    query = session.query(models_v2.Port,
+                          sg_db.SecurityGroupPortBinding.security_group_id)
+    query = query.outerjoin(sg_db.SecurityGroupPortBinding,
+                            models_v2.Port.id == sg_binding_port)
+    query = query.filter(models_v2.Port.id.startswith(device))
+    port_and_sgs = query.all()
+    if not port_and_sgs:
         return
-    for port in ports:
-        if port['id'].startswith(device):
-            return port
-    return
+    port = port_and_sgs[0][0]
+    plugin = manager.QuantumManager.get_plugin()
+    port_dict = plugin._make_port_dict(port)
+    port_dict['security_groups'] = []
+    for port_in_db, sg_id in port_and_sgs:
+        if sg_id:
+            port_dict['security_groups'].append(sg_id)
+    port_dict['security_group_rules'] = []
+    port_dict['security_group_source_groups'] = []
+    port_dict['fixed_ips'] = [ip['ip_address']
+                              for ip in port['fixed_ips']]
+    return port_dict
 
 
 def set_port_status(port_id, status):
index 4f47a19eacd379291c3f8e2f5f6b5fb4bf61b229..fb7b53a7cfb06d3cf26e3ebcb22b25c353943c35 100644 (file)
 
 import sys
 
+from quantum.agent import securitygroups_rpc as sg_rpc
 from quantum.api.v2 import attributes
 from quantum.common import constants as q_const
 from quantum.common import exceptions as q_exc
 from quantum.common import rpc as q_rpc
 from quantum.common import topics
+from quantum.common import utils
 from quantum.db import api as db_api
 from quantum.db import db_base_plugin_v2
 from quantum.db import dhcp_rpc_base
 from quantum.db import l3_db
 from quantum.db import l3_rpc_base
 from quantum.db import quota_db
+from quantum.db import securitygroups_rpc_base as sg_db_rpc
 from quantum.extensions import portbindings
 from quantum.extensions import providernet as provider
+from quantum.extensions import securitygroup as ext_sg
 from quantum.openstack.common import cfg
 from quantum.openstack.common import log as logging
 from quantum.openstack.common import rpc
@@ -41,11 +45,13 @@ LOG = logging.getLogger(__name__)
 
 
 class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
-                              l3_rpc_base.L3RpcCallbackMixin):
+                              l3_rpc_base.L3RpcCallbackMixin,
+                              sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
 
-    # Set RPC API version to 1.0 by default.
-    RPC_API_VERSION = '1.0'
+    RPC_API_VERSION = '1.1'
     # Device names start with "tap"
+    # history
+    #   1.1 Support Security Group RPC
     TAP_PREFIX_LEN = 3
 
     def create_rpc_dispatcher(self):
@@ -56,13 +62,20 @@ class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
         '''
         return q_rpc.PluginRpcDispatcher([self])
 
+    @classmethod
+    def get_port_from_device(cls, device):
+        port = db.get_port_from_device(device[cls.TAP_PREFIX_LEN:])
+        if port:
+            port['device'] = device
+        return port
+
     def get_device_details(self, rpc_context, **kwargs):
         """Agent requests device details"""
         agent_id = kwargs.get('agent_id')
         device = kwargs.get('device')
         LOG.debug(_("Device %(device)s details requested from %(agent_id)s"),
                   locals())
-        port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
+        port = self.get_port_from_device(device)
         if port:
             binding = db.get_network_binding(db_api.get_session(),
                                              port['network_id'])
@@ -86,7 +99,7 @@ class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
         device = kwargs.get('device')
         LOG.debug(_("Device %(device)s no longer exists on %(agent_id)s"),
                   locals())
-        port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
+        port = self.get_port_from_device(device)
         if port:
             entry = {'device': device,
                      'exists': True}
@@ -99,7 +112,8 @@ class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
         return entry
 
 
-class AgentNotifierApi(proxy.RpcProxy):
+class AgentNotifierApi(proxy.RpcProxy,
+                       sg_rpc.SecurityGroupAgentRpcApiMixin):
     '''Agent side of the linux bridge rpc API.
 
     API version history:
@@ -112,6 +126,7 @@ class AgentNotifierApi(proxy.RpcProxy):
     def __init__(self, topic):
         super(AgentNotifierApi, self).__init__(
             topic=topic, default_version=self.BASE_RPC_API_VERSION)
+        self.topic = topic
         self.topic_network_delete = topics.get_topic_name(topic,
                                                           topics.NETWORK,
                                                           topics.DELETE)
@@ -135,7 +150,8 @@ class AgentNotifierApi(proxy.RpcProxy):
 
 
 class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
-                          l3_db.L3_NAT_db_mixin):
+                          l3_db.L3_NAT_db_mixin,
+                          sg_db_rpc.SecurityGroupServerRpcMixin):
     """Implement the Quantum abstractions using Linux bridging.
 
     A new VLAN is created for each network.  An agent is relied upon
@@ -157,7 +173,8 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
     # is qualified by class
     __native_bulk_support = True
 
-    supported_extension_aliases = ["provider", "router", "binding", "quotas"]
+    supported_extension_aliases = ["provider", "router", "binding", "quotas",
+                                   "security-group"]
 
     network_view = "extension:provider_network:view"
     network_set = "extension:provider_network:set"
@@ -333,6 +350,11 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
 
         session = context.session
         with session.begin(subtransactions=True):
+            #set up default security groups
+            tenant_id = self._get_tenant_id_for_create(
+                context, network['network'])
+            self._ensure_default_security_group(context, tenant_id)
+
             if not network_type:
                 # tenant network
                 network_type = self.tenant_network_type
@@ -374,8 +396,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
         session = context.session
         with session.begin(subtransactions=True):
             binding = db.get_network_binding(session, id)
-            result = super(LinuxBridgePluginV2, self).delete_network(context,
-                                                                     id)
+            super(LinuxBridgePluginV2, self).delete_network(context, id)
             if binding.vlan_id != constants.LOCAL_VLAN_ID:
                 db.release_network(session, binding.physical_network,
                                    binding.vlan_id, self.network_vlan_ranges)
@@ -412,31 +433,70 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
             port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
         return port
 
-    def create_port(self, context, port):
-        port = super(LinuxBridgePluginV2, self).create_port(context, port)
-        return self._extend_port_dict_binding(context, port)
-
     def get_port(self, context, id, fields=None):
         port = super(LinuxBridgePluginV2, self).get_port(context, id, fields)
-        return self._fields(self._extend_port_dict_binding(context, port),
-                            fields)
+        self._extend_port_dict_security_group(context, port)
+        self._extend_port_dict_binding(context, port),
+        return self._fields(port, fields)
 
     def get_ports(self, context, filters=None, fields=None):
         ports = super(LinuxBridgePluginV2, self).get_ports(context, filters,
                                                            fields)
+        #TODO(nati) filter by security group
+        for port in ports:
+            self._extend_port_dict_security_group(context, port)
         return [self._fields(self._extend_port_dict_binding(context, port),
                              fields) for port in ports]
 
+    def create_port(self, context, port):
+        session = context.session
+        with session.begin(subtransactions=True):
+            self._ensure_default_security_group_on_port(context, port)
+            self._validate_security_groups_on_port(context, port)
+            sgids = port['port'].get(ext_sg.SECURITYGROUP)
+            port = super(LinuxBridgePluginV2,
+                         self).create_port(context, port)
+            self._process_port_create_security_group(
+                context, port['id'], sgids)
+            self._extend_port_dict_security_group(context, port)
+        if port['device_owner'] == q_const.DEVICE_OWNER_DHCP:
+            self.notifier.security_groups_provider_updated(context)
+        else:
+            self.notifier.security_groups_member_updated(
+                context, port.get(ext_sg.SECURITYGROUP))
+        return self._extend_port_dict_binding(context, port)
+
     def update_port(self, context, id, port):
-        original_port = super(LinuxBridgePluginV2, self).get_port(context,
-                                                                  id)
-        port = super(LinuxBridgePluginV2, self).update_port(context, id, port)
+        self._validate_security_groups_on_port(context, port)
+        original_port = self.get_port(context, id)
+        session = context.session
+        port_updated = False
+        with session.begin(subtransactions=True):
+            # delete the port binding and read it with the new rules
+            if ext_sg.SECURITYGROUP in port['port']:
+                self._delete_port_security_group_bindings(context, id)
+                self._process_port_create_security_group(
+                    context,
+                    id,
+                    port['port'][ext_sg.SECURITYGROUP])
+                port_updated = True
+
+            port = super(LinuxBridgePluginV2, self).update_port(
+                context, id, port)
+            self._extend_port_dict_security_group(context, port)
+
         if original_port['admin_state_up'] != port['admin_state_up']:
-            binding = db.get_network_binding(context.session,
-                                             port['network_id'])
-            self.notifier.port_update(context, port,
-                                      binding.physical_network,
-                                      binding.vlan_id)
+            port_updated = True
+
+        if (original_port['fixed_ips'] != port['fixed_ips'] or
+            not utils.compare_elements(
+                original_port.get(ext_sg.SECURITYGROUP),
+                port.get(ext_sg.SECURITYGROUP))):
+            self.notifier.security_groups_member_updated(
+                context, port.get(ext_sg.SECURITYGROUP))
+
+        if port_updated:
+            self._notify_port_updated(context, port)
         return self._extend_port_dict_binding(context, port)
 
     def delete_port(self, context, id, l3_port_check=True):
@@ -445,5 +505,19 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
         # and l3-router.  If so, we should prevent deletion.
         if l3_port_check:
             self.prevent_l3_port_deletion(context, id)
-        self.disassociate_floatingips(context, id)
-        return super(LinuxBridgePluginV2, self).delete_port(context, id)
+
+        session = context.session
+        with session.begin(subtransactions=True):
+            self.disassociate_floatingips(context, id)
+            port = self.get_port(context, id)
+            self._delete_port_security_group_bindings(context, id)
+            super(LinuxBridgePluginV2, self).delete_port(context, id)
+            self.notifier.security_groups_member_updated(
+                context, port.get(ext_sg.SECURITYGROUP))
+
+    def _notify_port_updated(self, context, port):
+        binding = db.get_network_binding(context.session,
+                                         port['network_id'])
+        self.notifier.port_update(context, port,
+                                  binding.physical_network,
+                                  binding.vlan_id)
diff --git a/quantum/tests/unit/linuxbridge/test_lb_security_group.py b/quantum/tests/unit/linuxbridge/test_lb_security_group.py
new file mode 100644 (file)
index 0000000..ea7c660
--- /dev/null
@@ -0,0 +1,137 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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 mock
+from mock import call
+
+from quantum.api.v2 import attributes
+from quantum.extensions import securitygroup as ext_sg
+from quantum.plugins.linuxbridge.db import l2network_db_v2 as lb_db
+from quantum.tests.unit import test_extension_security_group as test_sg
+
+PLUGIN_NAME = ('quantum.plugins.linuxbridge.'
+               'lb_quantum_plugin.LinuxBridgePluginV2')
+AGENT_NAME = ('quantum.plugins.linuxbridge.'
+              'agent.linuxbridg_quantum_agent.LinuxBridgeQuantumAgentRPC')
+NOTIFIER = ('quantum.plugins.linuxbridge.'
+            'lb_quantum_plugin.AgentNotifierApi')
+
+
+class LinuxBridgeSecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase):
+    _plugin_name = PLUGIN_NAME
+
+    def setUp(self, plugin=None):
+        self.addCleanup(mock.patch.stopall)
+        notifier_p = mock.patch(NOTIFIER)
+        notifier_cls = notifier_p.start()
+        self.notifier = mock.Mock()
+        notifier_cls.return_value = self.notifier
+        self._attribute_map_bk_ = {}
+        for item in attributes.RESOURCE_ATTRIBUTE_MAP:
+            self._attribute_map_bk_[item] = (attributes.
+                                             RESOURCE_ATTRIBUTE_MAP[item].
+                                             copy())
+        super(LinuxBridgeSecurityGroupsTestCase, self).setUp(PLUGIN_NAME)
+
+    def tearDown(self):
+        super(LinuxBridgeSecurityGroupsTestCase, self).tearDown()
+        attributes.RESOURCE_ATTRIBUTE_MAP = self._attribute_map_bk_
+
+
+class TestLinuxBridgeSecurityGroups(LinuxBridgeSecurityGroupsTestCase,
+                                    test_sg.TestSecurityGroups):
+
+    def test_security_group_rule_updated(self):
+        name = 'webservers'
+        description = 'my webservers'
+        with self.security_group(name, description) as sg:
+            with self.security_group(name, description) as sg2:
+                security_group_id = sg['security_group']['id']
+                direction = "ingress"
+                source_group_id = sg2['security_group']['id']
+                protocol = 'tcp'
+                port_range_min = 88
+                port_range_max = 88
+                with self.security_group_rule(security_group_id, direction,
+                                              protocol, port_range_min,
+                                              port_range_max,
+                                              source_group_id=source_group_id
+                                              ):
+                    pass
+            self.notifier.assert_has_calls(
+                [call.security_groups_rule_updated(mock.ANY,
+                                                   [security_group_id]),
+                 call.security_groups_rule_updated(mock.ANY,
+                                                   [security_group_id])])
+
+    def test_security_group_member_updated(self):
+        with self.network() as n:
+            with self.subnet(n):
+                with self.security_group() as sg:
+                    security_group_id = sg['security_group']['id']
+                    res = self._create_port('json', n['network']['id'])
+                    port = self.deserialize('json', res)
+
+                    data = {'port': {'fixed_ips': port['port']['fixed_ips'],
+                                     'name': port['port']['name'],
+                                     ext_sg.SECURITYGROUP:
+                                     [security_group_id]}}
+
+                    req = self.new_update_request('ports', data,
+                                                  port['port']['id'])
+                    res = self.deserialize('json', req.get_response(self.api))
+                    self.assertEquals(res['port'][ext_sg.SECURITYGROUP][0],
+                                      security_group_id)
+                    self._delete('ports', port['port']['id'])
+                    self.notifier.assert_has_calls(
+                        [call.security_groups_member_updated(
+                            mock.ANY, [mock.ANY]),
+                         call.security_groups_member_updated(
+                             mock.ANY, [security_group_id])])
+
+
+class TestLinuxBridgeSecurityGroupsDB(LinuxBridgeSecurityGroupsTestCase):
+    def test_security_group_get_port_from_device(self):
+        with self.network() as n:
+            with self.subnet(n):
+                with self.security_group() as sg:
+                    security_group_id = sg['security_group']['id']
+                    res = self._create_port('json', n['network']['id'])
+                    port = self.deserialize('json', res)
+                    fixed_ips = port['port']['fixed_ips']
+                    data = {'port': {'fixed_ips': fixed_ips,
+                                     'name': port['port']['name'],
+                                     ext_sg.SECURITYGROUP:
+                                     [security_group_id]}}
+
+                    req = self.new_update_request('ports', data,
+                                                  port['port']['id'])
+                    res = self.deserialize('json', req.get_response(self.api))
+                    port_id = res['port']['id']
+                    device_id = port_id[:8]
+                    port_dict = lb_db.get_port_from_device(device_id)
+                    self.assertEqual(port_id, port_dict['id'])
+                    self.assertEqual([security_group_id],
+                                     port_dict[ext_sg.SECURITYGROUP])
+                    self.assertEqual([], port_dict['security_group_rules'])
+                    self.assertEqual([fixed_ips[0]['ip_address']],
+                                     port_dict['fixed_ips'])
+                    self._delete('ports', port['port']['id'])
+
+    def test_security_group_get_port_from_device_with_no_port(self):
+        port_dict = lb_db.get_port_from_device('bad_device_id')
+        self.assertEqual(None, port_dict)
index dd510e7a3bbb337fc6e40ab48732e014f08a5cfb..c27a2098fcce07ebdccbac7663a5e615819c59b3 100644 (file)
@@ -21,14 +21,15 @@ from quantum.manager import QuantumManager
 from quantum.openstack.common import cfg
 from quantum.tests.unit import test_db_plugin as test_plugin
 
+PLUGIN_NAME = ('quantum.plugins.linuxbridge.'
+               'lb_quantum_plugin.LinuxBridgePluginV2')
 
-class LinuxBridgePluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
 
-    _plugin_name = ('quantum.plugins.linuxbridge.'
-                    'lb_quantum_plugin.LinuxBridgePluginV2')
+class LinuxBridgePluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
+    _plugin_name = PLUGIN_NAME
 
     def setUp(self):
-        super(LinuxBridgePluginV2TestCase, self).setUp(self._plugin_name)
+        super(LinuxBridgePluginV2TestCase, self).setUp(PLUGIN_NAME)
 
 
 class TestLinuxBridgeBasicGet(test_plugin.TestBasicGet,
index b71915996d46082904335422d992e63d6bc21328..cc925815f1cfda14579af854cffb7f8152a38967 100644 (file)
@@ -77,6 +77,7 @@ class SecurityGroupsTestCase(test_db_plugin.QuantumDbPluginV2TestCase):
         data = {'security_group_rule': {'security_group_id': security_group_id,
                                         'direction': direction,
                                         'protocol': protocol,
+                                        'ethertype': ethertype,
                                         'port_range_min': port_range_min,
                                         'port_range_max': port_range_max,
                                         'tenant_id': tenant_id,
@@ -181,12 +182,13 @@ class SecurityGroupTestPlugin(db_base_plugin_v2.QuantumDbPluginV2,
     def update_port(self, context, id, port):
         session = context.session
         with session.begin(subtransactions=True):
-            self._validate_security_groups_on_port(context, port)
-            # delete the port binding and read it with the new rules
-            self._delete_port_security_group_bindings(context, id)
-            self._process_port_create_security_group(context, id,
-                                                     port['port'].get(
-                                                     ext_sg.SECURITYGROUP))
+            if ext_sg.SECURITYGROUP in port['port']:
+                self._validate_security_groups_on_port(context, port)
+                # delete the port binding and read it with the new rules
+                self._delete_port_security_group_bindings(context, id)
+                self._process_port_create_security_group(context, id,
+                                                         port['port'].get(
+                                                         ext_sg.SECURITYGROUP))
             port = super(SecurityGroupTestPlugin, self).update_port(
                 context, id, port)
             self._extend_port_dict_security_group(context, port)
@@ -204,7 +206,7 @@ class SecurityGroupDBTestCase(SecurityGroupsTestCase):
         test_config['plugin_name_v2'] = DB_PLUGIN_KLASS
         ext_mgr = SecurityGroupTestExtensionManager()
         test_config['extension_manager'] = ext_mgr
-        super(SecurityGroupDBTestCase, self).setUp()
+        super(SecurityGroupDBTestCase, self).setUp(plugin)
 
 
 class TestSecurityGroups(SecurityGroupDBTestCase):
@@ -580,6 +582,17 @@ class TestSecurityGroups(SecurityGroupDBTestCase):
                     res = self.deserialize('json', req.get_response(self.api))
                     self.assertEqual(res['port'][ext_sg.SECURITYGROUP][0],
                                      sg['security_group']['id'])
+
+                    # Test update port without security group
+                    data = {'port': {'fixed_ips': port['port']['fixed_ips'],
+                                     'name': port['port']['name']}}
+
+                    req = self.new_update_request('ports', data,
+                                                  port['port']['id'])
+                    res = self.deserialize('json', req.get_response(self.api))
+                    self.assertEqual(res['port'][ext_sg.SECURITYGROUP][0],
+                                     sg['security_group']['id'])
+
                     self._delete('ports', port['port']['id'])
 
     def test_update_port_with_multiple_security_groups(self):
@@ -606,12 +619,14 @@ class TestSecurityGroups(SecurityGroupDBTestCase):
                     port = self.deserialize('json', res)
 
                     data = {'port': {'fixed_ips': port['port']['fixed_ips'],
-                                     'name': port['port']['name']}}
+                                     'name': port['port']['name'],
+                                     'security_groups': []}}
 
                     req = self.new_update_request('ports', data,
                                                   port['port']['id'])
                     res = self.deserialize('json', req.get_response(self.api))
-                    self.assertEqual(res['port'][ext_sg.SECURITYGROUP], [])
+                    self.assertEqual(res['port'].get(ext_sg.SECURITYGROUP),
+                                     [])
                     self._delete('ports', port['port']['id'])
 
     def test_create_port_with_bad_security_group(self):
diff --git a/quantum/tests/unit/test_iptables_firewall.py b/quantum/tests/unit/test_iptables_firewall.py
new file mode 100644 (file)
index 0000000..9262138
--- /dev/null
@@ -0,0 +1,912 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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 mock
+from mock import call
+import unittest2 as unittest
+
+from quantum.agent.linux.iptables_firewall import IptablesFirewallDriver
+from quantum.tests.unit import test_api_v2
+
+_uuid = test_api_v2._uuid
+FAKE_PREFIX = {'IPv4': '10.0.0.0/24',
+               'IPv6': 'fe80::0/48'}
+FAKE_IP = {'IPv4': '10.0.0.1',
+           'IPv6': 'fe80::1'}
+
+
+class IptablesFirewallTestCase(unittest.TestCase):
+    def setUp(self):
+        self.utils_exec_p = mock.patch(
+            'quantum.agent.linux.utils.execute')
+        self.utils_exec = self.utils_exec_p.start()
+        self.iptables_cls_p = mock.patch(
+            'quantum.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}
+        self.iptables_inst.ipv6 = {'filter': self.v6filter_inst}
+        iptables_cls.return_value = self.iptables_inst
+
+        self.firewall = IptablesFirewallDriver(self.iptables_inst)
+
+    def tearDown(self):
+        self.iptables_cls_p.stop()
+        self.utils_exec_p.stop()
+
+    def _fake_port(self):
+        return {'device': 'tapfake_dev',
+                'mac_address': 'ff:ff:ff:ff',
+                'fixed_ips': [FAKE_IP['IPv4'],
+                              FAKE_IP['IPv6']]}
+
+    def test_prepare_port_filter_with_no_sg(self):
+        port = self._fake_port()
+        self.firewall.prepare_port_filter(port)
+        calls = [call.add_chain('sg-fallback'),
+                 call.add_rule('sg-fallback', '-j DROP'),
+                 call.ensure_remove_chain('sg-chain'),
+                 call.add_chain('sg-chain'),
+                 call.add_chain('ifake_dev'),
+                 call.add_rule('FORWARD',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-out tapfake_dev '
+                               '-j $sg-chain'),
+                 call.add_rule('sg-chain',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-out tapfake_dev '
+                               '-j $ifake_dev'),
+                 call.add_rule(
+                     'ifake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ifake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ifake_dev', '-j $sg-fallback'),
+                 call.add_chain('ofake_dev'),
+                 call.add_rule('FORWARD',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-in tapfake_dev '
+                               '-j $sg-chain'),
+                 call.add_rule('sg-chain',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-in tapfake_dev '
+                               '-j $ofake_dev'),
+                 call.add_rule('INPUT',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-in tapfake_dev '
+                               '-j $ofake_dev'),
+                 call.add_rule(
+                     'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-p udp --sport 68 --dport 67 -j RETURN'),
+                 call.add_rule('ofake_dev', '! -s 10.0.0.1 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-p udp --sport 67 --dport 68 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ofake_dev', '-j $sg-fallback'),
+                 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 = call.add_rule('ifake_dev', '-j RETURN')
+        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 = call.add_rule('ifake_dev', '-j RETURN -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp')
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p icmp')
+        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 = call.add_rule(
+            'ifake_dev', '-j RETURN -p icmp -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp --dport 10')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p tcp -m multiport --dports 10:100')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p tcp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp')
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp --dport 10')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p udp -m multiport --dports 10:100')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p udp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        egress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    def test_filter_ipv4_egress(self):
+        rule = {'ethertype': 'IPv4',
+                'direction': 'egress'}
+        egress = call.add_rule('ofake_dev', '-j RETURN')
+        ingress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    def test_filter_ipv4_egress_prefix(self):
+        prefix = FAKE_PREFIX['IPv4']
+        rule = {'ethertype': 'IPv4',
+                'direction': 'egress',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p tcp')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -p tcp -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p icmp')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev', '-j RETURN -p icmp -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p tcp --dport 10')
+        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 = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p tcp -m multiport --dports 10:100')
+        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,
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p tcp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p udp')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -p udp -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p udp --dport 10')
+        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 = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p udp -m multiport --dports 10:100')
+        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,
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p udp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        ingress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    def test_filter_ipv6_ingress(self):
+        rule = {'ethertype': 'IPv6',
+                'direction': 'ingress'}
+        ingress = call.add_rule('ifake_dev', '-j RETURN')
+        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 = call.add_rule('ifake_dev', '-j RETURN -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp')
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p tcp --dport 10')
+        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 = call.add_rule('ifake_dev', '-j RETURN -p icmpv6')
+        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 = call.add_rule(
+            'ifake_dev', '-j RETURN -p icmpv6 -s %s' % prefix)
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p tcp -m multiport --dports 10:100')
+        egress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p tcp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp')
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp -s %s' % prefix)
+        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 = call.add_rule('ifake_dev', '-j RETURN -p udp --dport 10')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p udp -m multiport --dports 10:100')
+        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 = call.add_rule(
+            'ifake_dev',
+            '-j RETURN -p udp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        egress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    def test_filter_ipv6_egress(self):
+        rule = {'ethertype': 'IPv6',
+                'direction': 'egress'}
+        egress = call.add_rule('ofake_dev', '-j RETURN')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p tcp')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -p tcp -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p icmpv6')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev', '-j RETURN -p icmpv6 -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p tcp --dport 10')
+        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 = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p tcp -m multiport --dports 10:100')
+        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,
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p tcp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p udp')
+        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',
+                'source_ip_prefix': prefix}
+        egress = call.add_rule('ofake_dev', '-j RETURN -p udp -s %s' % prefix)
+        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 = call.add_rule('ofake_dev', '-j RETURN -p udp --dport 10')
+        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 = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p udp -m multiport --dports 10:100')
+        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,
+                'source_ip_prefix': prefix}
+        egress = call.add_rule(
+            'ofake_dev',
+            '-j RETURN -p udp -m multiport '
+            '--dports 10:100 -s %s' % prefix)
+        ingress = None
+        self._test_prepare_port_filter(rule, ingress, egress)
+
+    def _test_prepare_port_filter(self,
+                                  rule,
+                                  ingress_expected_call=None,
+                                  egress_expected_call=None):
+        port = self._fake_port()
+        ethertype = rule['ethertype']
+        prefix = FAKE_IP[ethertype]
+        filter_inst = self.v4filter_inst
+        dhcp_rule = call.add_rule(
+            'ofake_dev',
+            '-p udp --sport 68 --dport 67 -j RETURN')
+
+        if ethertype == 'IPv6':
+            filter_inst = self.v6filter_inst
+            dhcp_rule = call.add_rule('ofake_dev', '-p icmpv6 -j RETURN')
+
+        sg = [rule]
+        port['security_group_rules'] = sg
+        self.firewall.prepare_port_filter(port)
+        calls = [call.add_chain('sg-fallback'),
+                 call.add_rule('sg-fallback', '-j DROP'),
+                 call.ensure_remove_chain('sg-chain'),
+                 call.add_chain('sg-chain'),
+                 call.add_chain('ifake_dev'),
+                 call.add_rule('FORWARD',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-out tapfake_dev '
+                               '-j $sg-chain'),
+                 call.add_rule('sg-chain',
+                               '-m physdev --physdev-is-bridged '
+                               '--physdev-out tapfake_dev '
+                               '-j $ifake_dev'),
+                 call.add_rule(
+                     'ifake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ifake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN')]
+
+        if ingress_expected_call:
+            calls.append(ingress_expected_call)
+
+        calls += [call.add_rule('ifake_dev', '-j $sg-fallback'),
+                  call.add_chain('ofake_dev'),
+                  call.add_rule('FORWARD',
+                                '-m physdev --physdev-is-bridged '
+                                '--physdev-in tapfake_dev '
+                                '-j $sg-chain'),
+                  call.add_rule('sg-chain',
+                                '-m physdev --physdev-is-bridged '
+                                '--physdev-in tapfake_dev '
+                                '-j $ofake_dev'),
+                  call.add_rule('INPUT',
+                                '-m physdev --physdev-is-bridged '
+                                '--physdev-in tapfake_dev '
+                                '-j $ofake_dev'),
+                  call.add_rule(
+                      'ofake_dev',
+                      '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
+                  dhcp_rule,
+                  call.add_rule('ofake_dev', '! -s %s -j DROP' % prefix)]
+
+        if ethertype == 'IPv4':
+            calls.append(call.add_rule(
+                'ofake_dev',
+                '-p udp --sport 67 --dport 68 -j DROP'))
+
+        calls += [call.add_rule(
+                  'ofake_dev', '-m state --state INVALID -j DROP'),
+                  call.add_rule(
+                  'ofake_dev',
+                  '-m state --state ESTABLISHED,RELATED -j RETURN')]
+
+        if egress_expected_call:
+            calls.append(egress_expected_call)
+
+        calls += [call.add_rule('ofake_dev', '-j $sg-fallback'),
+                  call.add_rule('sg-chain', '-j ACCEPT')]
+
+        filter_inst.assert_has_calls(calls)
+
+    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 = [call.add_chain('sg-fallback'),
+                 call.add_rule('sg-fallback', '-j DROP'),
+                 call.ensure_remove_chain('sg-chain'),
+                 call.add_chain('sg-chain'),
+                 call.add_chain('ifake_dev'),
+                 call.add_rule(
+                     'FORWARD',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-out tapfake_dev -j $sg-chain'),
+                 call.add_rule(
+                     'sg-chain',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-out tapfake_dev -j $ifake_dev'),
+                 call.add_rule(
+                     'ifake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ifake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ifake_dev', '-j RETURN'),
+                 call.add_rule('ifake_dev', '-j $sg-fallback'),
+                 call.add_chain('ofake_dev'),
+                 call.add_rule(
+                     'FORWARD',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $sg-chain'),
+                 call.add_rule(
+                     'sg-chain',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $ofake_dev'),
+                 call.add_rule(
+                     'INPUT',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $ofake_dev'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-p udp --sport 68 --dport 67 -j RETURN'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '! -s 10.0.0.1 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-p udp --sport 67 --dport 68 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ofake_dev', '-j $sg-fallback'),
+                 call.add_rule('sg-chain', '-j ACCEPT'),
+                 call.ensure_remove_chain('ifake_dev'),
+                 call.ensure_remove_chain('ofake_dev'),
+                 call.ensure_remove_chain('sg-chain'),
+                 call.add_chain('sg-chain'),
+                 call.add_chain('ifake_dev'),
+                 call.add_rule(
+                     'FORWARD',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-out tapfake_dev -j $sg-chain'),
+                 call.add_rule(
+                     'sg-chain',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-out tapfake_dev -j $ifake_dev'),
+                 call.add_rule(
+                     'ifake_dev',
+                     '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ifake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ifake_dev', '-j $sg-fallback'),
+                 call.add_chain('ofake_dev'),
+                 call.add_rule(
+                     'FORWARD',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $sg-chain'),
+                 call.add_rule(
+                     'sg-chain',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $ofake_dev'),
+                 call.add_rule(
+                     'INPUT',
+                     '-m physdev --physdev-is-bridged '
+                     '--physdev-in tapfake_dev -j $ofake_dev'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
+                 call.add_rule(
+                     'ofake_dev', '-p udp --sport 68 --dport 67 -j RETURN'),
+                 call.add_rule(
+                     'ofake_dev', '! -s 10.0.0.1 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev', '-p udp --sport 67 --dport 68 -j DROP'),
+                 call.add_rule(
+                     'ofake_dev', '-m state --state INVALID -j DROP'),
+                 call.add_rule(
+                     'ofake_dev',
+                     '-m state --state ESTABLISHED,RELATED -j RETURN'),
+                 call.add_rule('ofake_dev', '-j RETURN'),
+                 call.add_rule('ofake_dev', '-j $sg-fallback'),
+                 call.add_rule('sg-chain', '-j ACCEPT'),
+                 call.ensure_remove_chain('ifake_dev'),
+                 call.ensure_remove_chain('ofake_dev'),
+                 call.ensure_remove_chain('sg-chain'),
+                 call.add_chain('sg-chain')]
+
+        self.v4filter_inst.assert_has_calls(calls)
+
+    def test_remove_unknown_port(self):
+        port = self._fake_port()
+        self.firewall.remove_port_filter(port)
+        # checking no exception occures
+        self.v4filter_inst.assert_has_calls([])
+
+    def test_defer_apply(self):
+        with self.firewall.defer_apply():
+            pass
+        self.iptables_inst.assert_has_calls([call.defer_apply_on(),
+                                             call.defer_apply_off()])
+
+    def test_filter_defer_with_exception(self):
+        try:
+            with self.firewall.defer_apply():
+                raise Exception("same exception")
+        except:
+            pass
+        self.iptables_inst.assert_has_calls([call.defer_apply_on(),
+                                             call.defer_apply_off()])
diff --git a/quantum/tests/unit/test_security_groups_rpc.py b/quantum/tests/unit/test_security_groups_rpc.py
new file mode 100644 (file)
index 0000000..abd2ae3
--- /dev/null
@@ -0,0 +1,1091 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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.
+
+from contextlib import nested
+import mock
+from mock import call
+import unittest2 as unittest
+
+import mox
+
+from quantum.agent import firewall as firewall_base
+from quantum.agent.linux import iptables_manager
+from quantum.agent import rpc as agent_rpc
+from quantum.agent import securitygroups_rpc as sg_rpc
+from quantum import context
+from quantum.db import securitygroups_rpc_base as sg_db_rpc
+from quantum.openstack.common.rpc import proxy
+from quantum.tests.unit import test_extension_security_group as test_sg
+from quantum.tests.unit import test_iptables_firewall as test_fw
+
+
+class FakeSGCallback(sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
+    def get_port_from_device(self, device):
+        device = self.devices.get(device)
+        if device:
+            device['security_group_rules'] = []
+            device['security_group_source_groups'] = []
+            device['fixed_ips'] = [ip['ip_address']
+                                   for ip in device['fixed_ips']]
+        return device
+
+
+class SGServerRpcCallBackMixinTestCase(test_sg.SecurityGroupDBTestCase):
+    def setUp(self):
+        super(SGServerRpcCallBackMixinTestCase, self).setUp()
+        self.rpc = FakeSGCallback()
+
+    def test_security_group_rules_for_devices_ipv4_ingress(self):
+        fake_prefix = test_fw.FAKE_PREFIX['IPv4']
+        with self.network() as n:
+            with nested(self.subnet(n),
+                        self.security_group()) as (subnet_v4,
+                                                   sg1):
+                sg1_id = sg1['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'tcp', '22',
+                    '22')
+                rule2 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'tcp', '23',
+                    '23', fake_prefix)
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule'],
+                                             rule2['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    security_groups=[sg1_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': 'ingress',
+                             'protocol': 'tcp', 'ethertype': 'IPv4',
+                             'port_range_max': 22,
+                             'security_group_id': sg1_id,
+                             'port_range_min': 22},
+                            {'direction': 'ingress', 'protocol': 'tcp',
+                             'ethertype': 'IPv4',
+                             'port_range_max': 23, 'security_group_id': sg1_id,
+                             'port_range_min': 23,
+                             'source_ip_prefix': fake_prefix},
+                            {'ethertype': 'IPv4', 'direction': 'egress'},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+
+    def test_security_group_rules_for_devices_ipv4_egress(self):
+        fake_prefix = test_fw.FAKE_PREFIX['IPv4']
+        with self.network() as n:
+            with nested(self.subnet(n),
+                        self.security_group()) as (subnet_v4,
+                                                   sg1):
+                sg1_id = sg1['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'egress', 'tcp', '22',
+                    '22')
+                rule2 = self._build_security_group_rule(
+                    sg1_id,
+                    'egress', 'udp', '23',
+                    '23', fake_prefix)
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule'],
+                                             rule2['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    security_groups=[sg1_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': 'egress',
+                             'protocol': 'tcp', 'ethertype': 'IPv4',
+                             'port_range_max': 22,
+                             'security_group_id': sg1_id,
+                             'port_range_min': 22},
+                            {'direction': 'egress', 'protocol': 'udp',
+                             'ethertype': 'IPv4',
+                             'port_range_max': 23, 'security_group_id': sg1_id,
+                             'port_range_min': 23,
+                             'dest_ip_prefix': fake_prefix},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+
+    def test_security_group_rules_for_devices_ipv4_source_group(self):
+
+        with self.network() as n:
+            with nested(self.subnet(n),
+                        self.security_group(),
+                        self.security_group()) as (subnet_v4,
+                                                   sg1,
+                                                   sg2):
+                sg1_id = sg1['security_group']['id']
+                sg2_id = sg2['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'tcp', '24',
+                    '25', source_group_id=sg2['security_group']['id'])
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    security_groups=[sg1_id,
+                                     sg2_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+
+                res2 = self._create_port(
+                    'json', n['network']['id'],
+                    security_groups=[sg2_id])
+                ports_rest2 = self.deserialize('json', res2)
+                port_id2 = ports_rest2['port']['id']
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': u'ingress',
+                             'source_ip_prefix': u'10.0.0.3/32',
+                             'protocol': u'tcp', 'ethertype': u'IPv4',
+                             'port_range_max': 25, 'port_range_min': 24,
+                             'source_group_id': sg2_id,
+                             'security_group_id': sg1_id},
+                            {'ethertype': 'IPv4', 'direction': 'egress'},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+                self._delete('ports', port_id2)
+
+    def test_security_group_rules_for_devices_ipv6_ingress(self):
+        fake_prefix = test_fw.FAKE_PREFIX['IPv6']
+        with self.network() as n:
+            with nested(self.subnet(n,
+                                    cidr=fake_prefix,
+                                    ip_version=6),
+                        self.security_group()) as (subnet_v6,
+                                                   sg1):
+                sg1_id = sg1['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'tcp', '22',
+                    '22',
+                    ethertype='IPv6')
+                rule2 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'udp', '23',
+                    '23', fake_prefix,
+                    ethertype='IPv6')
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule'],
+                                             rule2['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    fixed_ips=[{'subnet_id': subnet_v6['subnet']['id']}],
+                    security_groups=[sg1_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': 'ingress',
+                             'protocol': 'tcp', 'ethertype': 'IPv6',
+                             'port_range_max': 22,
+                             'security_group_id': sg1_id,
+                             'port_range_min': 22},
+                            {'direction': 'ingress', 'protocol': 'udp',
+                             'ethertype': 'IPv6',
+                             'port_range_max': 23, 'security_group_id': sg1_id,
+                             'port_range_min': 23,
+                             'source_ip_prefix': fake_prefix},
+                            {'ethertype': 'IPv6', 'direction': 'egress'},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+
+    def test_security_group_rules_for_devices_ipv6_egress(self):
+        fake_prefix = test_fw.FAKE_PREFIX['IPv6']
+        with self.network() as n:
+            with nested(self.subnet(n,
+                                    cidr=fake_prefix,
+                                    ip_version=6),
+                        self.security_group()) as (subnet_v6,
+                                                   sg1):
+                sg1_id = sg1['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'egress', 'tcp', '22',
+                    '22',
+                    ethertype='IPv6')
+                rule2 = self._build_security_group_rule(
+                    sg1_id,
+                    'egress', 'udp', '23',
+                    '23', fake_prefix,
+                    ethertype='IPv6')
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule'],
+                                             rule2['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    fixed_ips=[{'subnet_id': subnet_v6['subnet']['id']}],
+                    security_groups=[sg1_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': 'egress',
+                             'protocol': 'tcp', 'ethertype': 'IPv6',
+                             'port_range_max': 22,
+                             'security_group_id': sg1_id,
+                             'port_range_min': 22},
+                            {'direction': 'egress', 'protocol': 'udp',
+                             'ethertype': 'IPv6',
+                             'port_range_max': 23, 'security_group_id': sg1_id,
+                             'port_range_min': 23,
+                             'dest_ip_prefix': fake_prefix},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+
+    def test_security_group_rules_for_devices_ipv6_source_group(self):
+        fake_prefix = test_fw.FAKE_PREFIX['IPv6']
+        with self.network() as n:
+            with nested(self.subnet(n,
+                                    cidr=fake_prefix,
+                                    ip_version=6),
+                        self.security_group(),
+                        self.security_group()) as (subnet_v6,
+                                                   sg1,
+                                                   sg2):
+                sg1_id = sg1['security_group']['id']
+                sg2_id = sg2['security_group']['id']
+                rule1 = self._build_security_group_rule(
+                    sg1_id,
+                    'ingress', 'tcp', '24',
+                    '25',
+                    ethertype='IPv6',
+                    source_group_id=sg2['security_group']['id'])
+                rules = {
+                    'security_group_rules': [rule1['security_group_rule']]}
+                res = self._create_security_group_rule('json', rules)
+                self.deserialize('json', res)
+                self.assertEquals(res.status_int, 201)
+
+                res1 = self._create_port(
+                    'json', n['network']['id'],
+                    fixed_ips=[{'subnet_id': subnet_v6['subnet']['id']}],
+                    security_groups=[sg1_id,
+                                     sg2_id])
+                ports_rest1 = self.deserialize('json', res1)
+                port_id1 = ports_rest1['port']['id']
+                self.rpc.devices = {port_id1: ports_rest1['port']}
+                devices = [port_id1, 'no_exist_device']
+
+                res2 = self._create_port(
+                    'json', n['network']['id'],
+                    fixed_ips=[{'subnet_id': subnet_v6['subnet']['id']}],
+                    security_groups=[sg2_id])
+                ports_rest2 = self.deserialize('json', res2)
+                port_id2 = ports_rest2['port']['id']
+
+                ctx = context.get_admin_context()
+                ports_rpc = self.rpc.security_group_rules_for_devices(
+                    ctx, devices=devices)
+                port_rpc = ports_rpc[port_id1]
+                expected = [{'direction': 'ingress',
+                             'source_ip_prefix': 'fe80::3/128',
+                             'protocol': 'tcp', 'ethertype': 'IPv6',
+                             'port_range_max': 25, 'port_range_min': 24,
+                             'source_group_id': sg2_id,
+                             'security_group_id': sg1_id},
+                            {'ethertype': 'IPv6', 'direction': 'egress'},
+                            ]
+                self.assertEquals(port_rpc['security_group_rules'],
+                                  expected)
+                self._delete('ports', port_id1)
+                self._delete('ports', port_id2)
+
+
+class SGAgentRpcCallBackMixinTestCase(unittest.TestCase):
+    def setUp(self):
+        self.rpc = sg_rpc.SecurityGroupAgentRpcCallbackMixin()
+        self.rpc.agent = mock.Mock()
+
+    def test_security_groups_rule_updated(self):
+        self.rpc.security_groups_rule_updated(None,
+                                              security_groups=['fake_sgid'])
+        self.rpc.agent.assert_has_calls(
+            [call.security_groups_rule_updated(['fake_sgid'])])
+
+    def test_security_groups_member_updated(self):
+        self.rpc.security_groups_member_updated(None,
+                                                security_groups=['fake_sgid'])
+        self.rpc.agent.assert_has_calls(
+            [call.security_groups_member_updated(['fake_sgid'])])
+
+    def test_security_groups_provider_updated(self):
+        self.rpc.security_groups_provider_updated(None)
+        self.rpc.agent.assert_has_calls(
+            [call.security_groups_provider_updated()])
+
+
+class SecurityGroupAgentRpcTestCase(unittest.TestCase):
+    def setUp(self):
+        self.agent = sg_rpc.SecurityGroupAgentRpcMixin()
+        self.agent.context = None
+        self.addCleanup(mock.patch.stopall)
+        mock.patch('quantum.agent.linux.iptables_manager').start()
+        self.agent.root_helper = 'sudo'
+        self.agent.init_firewall()
+        self.firewall = mock.Mock()
+        firewall_object = firewall_base.FirewallDriver()
+        self.firewall.defer_apply.side_effect = firewall_object.defer_apply
+        self.agent.firewall = self.firewall
+        rpc = mock.Mock()
+        self.agent.plugin_rpc = rpc
+        self.fake_device = {'device': 'fake_device',
+                            'security_groups': ['fake_sgid1', 'fake_sgid2'],
+                            'security_group_source_groups': ['fake_sgid2'],
+                            'security_group_rules': [{'security_group_id':
+                                                      'fake_sgid1',
+                                                      'source_group_id':
+                                                      'fake_sgid2'}]}
+        fake_devices = {'fake_device': self.fake_device}
+        self.firewall.ports = fake_devices
+        rpc.security_group_rules_for_devices.return_value = fake_devices
+
+    def test_prepare_and_remove_devices_filter(self):
+        self.agent.prepare_devices_filter(['fake_device'])
+        self.agent.remove_devices_filter(['fake_device'])
+        # ignore device which is not filtered
+        self.firewall.assert_has_calls([call.defer_apply(),
+                                        call.prepare_port_filter(
+                                            self.fake_device),
+                                        call.defer_apply(),
+                                        call.remove_port_filter(
+                                            self.fake_device),
+                                        ])
+
+    def test_security_groups_rule_updated(self):
+        self.agent.refresh_firewall = mock.Mock()
+        self.agent.prepare_devices_filter(['fake_port_id'])
+        self.agent.security_groups_rule_updated(['fake_sgid1', 'fake_sgid3'])
+        self.agent.refresh_firewall.assert_has_calls(
+            [call.refresh_firewall()])
+
+    def test_security_groups_rule_not_updated(self):
+        self.agent.refresh_firewall = mock.Mock()
+        self.agent.prepare_devices_filter(['fake_port_id'])
+        self.agent.security_groups_rule_updated(['fake_sgid3', 'fake_sgid4'])
+        self.agent.refresh_firewall.assert_has_calls([])
+
+    def test_security_groups_member_updated(self):
+        self.agent.refresh_firewall = mock.Mock()
+        self.agent.prepare_devices_filter(['fake_port_id'])
+        self.agent.security_groups_member_updated(['fake_sgid2', 'fake_sgid3'])
+        self.agent.refresh_firewall.assert_has_calls(
+            [call.refresh_firewall()])
+
+    def test_security_groups_member_not_updated(self):
+        self.agent.refresh_firewall = mock.Mock()
+        self.agent.prepare_devices_filter(['fake_port_id'])
+        self.agent.security_groups_member_updated(['fake_sgid3', 'fake_sgid4'])
+        self.agent.refresh_firewall.assert_has_calls([])
+
+    def test_security_groups_provider_updated(self):
+        self.agent.refresh_firewall = mock.Mock()
+        self.agent.security_groups_provider_updated()
+        self.agent.refresh_firewall.assert_has_calls(
+            [call.refresh_firewall()])
+
+    def test_refresh_firewall(self):
+        self.agent.prepare_devices_filter(['fake_port_id'])
+        self.agent.refresh_firewall()
+        calls = [call.defer_apply(),
+                 call.prepare_port_filter(self.fake_device),
+                 call.defer_apply(),
+                 call.update_port_filter(self.fake_device)]
+        self.firewall.assert_has_calls(calls)
+
+
+class FakeSGRpcApi(agent_rpc.PluginApi,
+                   sg_rpc.SecurityGroupServerRpcApiMixin):
+    pass
+
+
+class SecurityGroupServerRpcApiTestCase(unittest.TestCase):
+    def setUp(self):
+        self.rpc = FakeSGRpcApi('fake_topic')
+        self.rpc.call = mock.Mock()
+
+    def test_security_group_rules_for_devices(self):
+        self.rpc.security_group_rules_for_devices(None, ['fake_device'])
+        self.rpc.call.assert_has_calls(
+            [call(None,
+             {'args':
+                 {'devices': ['fake_device']},
+             'method':
+                 'security_group_rules_for_devices'},
+             version=sg_rpc.SG_RPC_VERSION,
+             topic='fake_topic')])
+
+
+class FakeSGNotifierAPI(proxy.RpcProxy,
+                        sg_rpc.SecurityGroupAgentRpcApiMixin):
+    pass
+
+
+class SecurityGroupAgentRpcApiTestCase(unittest.TestCase):
+    def setUp(self):
+        self.notifier = FakeSGNotifierAPI(topic='fake',
+                                          default_version='1.0')
+        self.notifier.fanout_cast = mock.Mock()
+
+    def test_security_groups_rule_updated(self):
+        self.notifier.security_groups_rule_updated(
+            None, security_groups=['fake_sgid'])
+        self.notifier.fanout_cast.assert_has_calls(
+            [call(None,
+                  {'args':
+                      {'security_groups': ['fake_sgid']},
+                      'method': 'security_groups_rule_updated'},
+                  version=sg_rpc.SG_RPC_VERSION,
+                  topic='fake-security_group-update')])
+
+    def test_security_groups_member_updated(self):
+        self.notifier.security_groups_member_updated(
+            None, security_groups=['fake_sgid'])
+        self.notifier.fanout_cast.assert_has_calls(
+            [call(None,
+                  {'args':
+                      {'security_groups': ['fake_sgid']},
+                      'method': 'security_groups_member_updated'},
+                  version=sg_rpc.SG_RPC_VERSION,
+                  topic='fake-security_group-update')])
+
+    def test_security_groups_rule_not_updated(self):
+        self.notifier.security_groups_rule_updated(
+            None, security_groups=[])
+        self.assertEquals(False, self.notifier.fanout_cast.called)
+
+    def test_security_groups_member_not_updated(self):
+        self.notifier.security_groups_member_updated(
+            None, security_groups=[])
+        self.assertEquals(False, self.notifier.fanout_cast.called)
+
+#Note(nati) bn -> binary_name
+# id -> device_id
+
+PHYSDEV_RULE = '-m physdev --physdev-is-bridged'
+
+IPTABLES_ARG = {'bn': iptables_manager.binary_name,
+                'physdev': PHYSDEV_RULE}
+
+CHAINS_NAT = 'OUTPUT|POSTROUTING|PREROUTING|float-snat|snat'
+IPTABLES_ARG['chains'] = CHAINS_NAT
+
+IPTABLES_NAT = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-postrouting-bottom - [0:0]
+-A PREROUTING -j %(bn)s-PREROUTING
+-A OUTPUT -j %(bn)s-OUTPUT
+-A POSTROUTING -j %(bn)s-POSTROUTING
+-A POSTROUTING -j quantum-postrouting-bottom
+-A quantum-postrouting-bottom -j %(bn)s-snat
+-A %(bn)s-snat -j %(bn)s-float-snat
+""" % IPTABLES_ARG
+
+CHAINS_EMPTY = 'FORWARD|INPUT|OUTPUT|local|sg-chain|sg-fallback'
+CHAINS_1 = CHAINS_EMPTY + '|i_port1|o_port1'
+CHAINS_2 = CHAINS_1 + '|i_port2|o_port2'
+
+IPTABLES_ARG['chains'] = CHAINS_1
+
+IPTABLES_FILTER_1 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port1 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
+-A %(bn)s-o_port1 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_FILTER_1_2 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port1 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port1 -j RETURN -s 10.0.0.4
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
+-A %(bn)s-o_port1 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_ARG['chains'] = CHAINS_2
+
+IPTABLES_FILTER_2 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port1 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port1 -j RETURN -s 10.0.0.4
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
+-A %(bn)s-o_port1 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2
+-A %(bn)s-i_port2 -m state --state INVALID -j DROP
+-A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port2 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port2 -j RETURN -s 10.0.0.3
+-A %(bn)s-i_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
+-A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
+-A %(bn)s-o_port2 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port2 -m state --state INVALID -j DROP
+-A %(bn)s-o_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port2 -j RETURN
+-A %(bn)s-o_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_FILTER_2_2 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port1 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
+-A %(bn)s-o_port1 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2
+-A %(bn)s-i_port2 -m state --state INVALID -j DROP
+-A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port2 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port2 -j RETURN -s 10.0.0.3
+-A %(bn)s-i_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
+-A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
+-A %(bn)s-o_port2 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port2 -m state --state INVALID -j DROP
+-A %(bn)s-o_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port2 -j RETURN
+-A %(bn)s-o_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_FILTER_2_3 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port1 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port1 -j RETURN -s 10.0.0.4
+-A %(bn)s-i_port1 -j RETURN -p icmp
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
+-A %(bn)s-o_port1 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2
+-A %(bn)s-i_port2 -m state --state INVALID -j DROP
+-A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port2 -j RETURN -p udp --dport 68 --sport 67 -s 10.0.0.2
+-A %(bn)s-i_port2 -j RETURN -p tcp --dport 22
+-A %(bn)s-i_port2 -j RETURN -s 10.0.0.3
+-A %(bn)s-i_port2 -j RETURN -p icmp
+-A %(bn)s-i_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
+-A %(bn)s-o_port2 -p udp --sport 68 --dport 67 -j RETURN
+-A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
+-A %(bn)s-o_port2 -p udp --sport 67 --dport 68 -j DROP
+-A %(bn)s-o_port2 -m state --state INVALID -j DROP
+-A %(bn)s-o_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port2 -j RETURN
+-A %(bn)s-o_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_ARG['chains'] = CHAINS_EMPTY
+IPTABLES_FILTER_EMPTY = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+""" % IPTABLES_ARG
+
+IPTABLES_ARG['chains'] = CHAINS_1
+IPTABLES_FILTER_V6_1 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p icmpv6 -j RETURN
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_ARG['chains'] = CHAINS_2
+IPTABLES_FILTER_V6_2 = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port1 -j %(bn)s-i_port1
+-A %(bn)s-i_port1 -m state --state INVALID -j DROP
+-A %(bn)s-i_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port1 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port1 -j %(bn)s-o_port1
+-A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
+-A %(bn)s-o_port1 -p icmpv6 -j RETURN
+-A %(bn)s-o_port1 -m state --state INVALID -j DROP
+-A %(bn)s-o_port1 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port1 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-out tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-out tap_port2 -j %(bn)s-i_port2
+-A %(bn)s-i_port2 -m state --state INVALID -j DROP
+-A %(bn)s-i_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-i_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-FORWARD %(physdev)s --physdev-in tap_port2 -j %(bn)s-sg-chain
+-A %(bn)s-sg-chain %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-INPUT %(physdev)s --physdev-in tap_port2 -j %(bn)s-o_port2
+-A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
+-A %(bn)s-o_port2 -p icmpv6 -j RETURN
+-A %(bn)s-o_port2 -m state --state INVALID -j DROP
+-A %(bn)s-o_port2 -m state --state ESTABLISHED,RELATED -j RETURN
+-A %(bn)s-o_port2 -j %(bn)s-sg-fallback
+-A %(bn)s-sg-chain -j ACCEPT
+""" % IPTABLES_ARG
+
+IPTABLES_ARG['chains'] = CHAINS_EMPTY
+IPTABLES_FILTER_V6_EMPTY = """:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:%(bn)s-(%(chains)s) - [0:0]
+:quantum-filter-top - [0:0]
+-A FORWARD -j quantum-filter-top
+-A OUTPUT -j quantum-filter-top
+-A quantum-filter-top -j %(bn)s-local
+-A INPUT -j %(bn)s-INPUT
+-A OUTPUT -j %(bn)s-OUTPUT
+-A FORWARD -j %(bn)s-FORWARD
+-A %(bn)s-sg-fallback -j DROP
+""" % IPTABLES_ARG
+
+
+class TestSecurityGroupAgentWithIptables(unittest.TestCase):
+    def setUp(self):
+        self.mox = mox.Mox()
+
+        self.addCleanup(mock.patch.stopall)
+        self.addCleanup(self.mox.UnsetStubs)
+
+        self.agent = sg_rpc.SecurityGroupAgentRpcMixin()
+        self.agent.context = None
+
+        self.root_helper = 'sudo'
+        self.agent.root_helper = 'sudo'
+        self.agent.init_firewall()
+
+        self.iptables = self.agent.firewall.iptables
+        self.mox.StubOutWithMock(self.iptables, "execute")
+
+        self.rpc = mock.Mock()
+        self.agent.plugin_rpc = self.rpc
+        rule1 = [{'direction': 'ingress',
+                  'protocol': 'udp',
+                  'ethertype': 'IPv4',
+                  'source_ip_prefix': '10.0.0.2',
+                  'source_port_range_min': 67,
+                  'source_port_range_max': 67,
+                  'port_range_min': 68,
+                  'port_range_max': 68},
+                 {'direction': 'ingress',
+                  'protocol': 'tcp',
+                  'ethertype': 'IPv4',
+                  'port_range_min': 22,
+                  'port_range_max': 22},
+                 {'direction': 'egress',
+                  'ethertype': 'IPv4'}]
+        rule2 = rule1[:]
+        rule2 += [{'direction': 'ingress',
+                  'source_ip_prefix': '10.0.0.4',
+                  'ethertype': 'IPv4'}]
+        rule3 = rule2[:]
+        rule3 += [{'direction': 'ingress',
+                  'protocol': 'icmp',
+                  'ethertype': 'IPv4'}]
+        rule4 = rule1[:]
+        rule4 += [{'direction': 'ingress',
+                  'source_ip_prefix': '10.0.0.3',
+                  'ethertype': 'IPv4'}]
+        rule5 = rule4[:]
+        rule5 += [{'direction': 'ingress',
+                  'protocol': 'icmp',
+                  'ethertype': 'IPv4'}]
+        self.devices1 = {'tap_port1': self._device('tap_port1',
+                                                   '10.0.0.3',
+                                                   '12:34:56:78:9a:bc',
+                                                   rule1)}
+        self.devices2 = {'tap_port1': self._device('tap_port1',
+                                                   '10.0.0.3',
+                                                   '12:34:56:78:9a:bc',
+                                                   rule2),
+                         'tap_port2': self._device('tap_port2',
+                                                   '10.0.0.4',
+                                                   '12:34:56:78:9a:bd',
+                                                   rule4)}
+        self.devices3 = {'tap_port1': self._device('tap_port1',
+                                                   '10.0.0.3',
+                                                   '12:34:56:78:9a:bc',
+                                                   rule3),
+                         'tap_port2': self._device('tap_port2',
+                                                   '10.0.0.4',
+                                                   '12:34:56:78:9a:bd',
+                                                   rule5)}
+
+    def _device(self, device, ip, mac_address, rule):
+        return {'device': device,
+                'fixed_ips': [ip],
+                'mac_address': mac_address,
+                'security_groups': ['security_group1'],
+                'security_group_rules': rule,
+                'security_group_source_groups': [
+                    'security_group1']}
+
+    def _regex(self, value):
+        value = value.replace('\n', '\\n')
+        value = value.replace('[', '\[')
+        value = value.replace(']', '\]')
+        return mox.Regex(value)
+
+    def _replay_iptables(self, v4_filter, v6_filter):
+        self.iptables.execute(
+            ['iptables-save', '-t', 'filter'],
+            root_helper=self.root_helper).AndReturn('')
+
+        self.iptables.execute(
+            ['iptables-restore'],
+            process_input=self._regex(v4_filter),
+            root_helper=self.root_helper).AndReturn('')
+
+        self.iptables.execute(
+            ['iptables-save', '-t', 'nat'],
+            root_helper=self.root_helper).AndReturn('')
+
+        self.iptables.execute(
+            ['iptables-restore'],
+            process_input=self._regex(IPTABLES_NAT),
+            root_helper=self.root_helper).AndReturn('')
+
+        self.iptables.execute(
+            ['ip6tables-save', '-t', 'filter'],
+            root_helper=self.root_helper).AndReturn('')
+
+        self.iptables.execute(
+            ['ip6tables-restore'],
+            process_input=self._regex(v6_filter),
+            root_helper=self.root_helper).AndReturn('')
+
+    def test_prepare_remove_port(self):
+        self.rpc.security_group_rules_for_devices.return_value = self.devices1
+        self._replay_iptables(IPTABLES_FILTER_1, IPTABLES_FILTER_V6_1)
+        self._replay_iptables(IPTABLES_FILTER_EMPTY, IPTABLES_FILTER_V6_EMPTY)
+        self.mox.ReplayAll()
+
+        self.agent.prepare_devices_filter(['tap_port1'])
+        self.agent.remove_devices_filter(['tap_port1'])
+        self.mox.VerifyAll()
+
+    def test_security_group_member_updated(self):
+        self.rpc.security_group_rules_for_devices.return_value = self.devices1
+        self._replay_iptables(IPTABLES_FILTER_1, IPTABLES_FILTER_V6_1)
+        self._replay_iptables(IPTABLES_FILTER_1_2, IPTABLES_FILTER_V6_1)
+        self._replay_iptables(IPTABLES_FILTER_2, IPTABLES_FILTER_V6_2)
+        self._replay_iptables(IPTABLES_FILTER_2_2, IPTABLES_FILTER_V6_2)
+        self._replay_iptables(IPTABLES_FILTER_1, IPTABLES_FILTER_V6_1)
+        self._replay_iptables(IPTABLES_FILTER_EMPTY, IPTABLES_FILTER_V6_EMPTY)
+        self.mox.ReplayAll()
+
+        self.agent.prepare_devices_filter(['tap_port1'])
+        self.rpc.security_group_rules_for_devices.return_value = self.devices2
+        self.agent.security_groups_member_updated(['security_group1'])
+        self.agent.prepare_devices_filter(['tap_port2'])
+        self.rpc.security_group_rules_for_devices.return_value = self.devices1
+        self.agent.security_groups_member_updated(['security_group1'])
+        self.agent.remove_devices_filter(['tap_port2'])
+        self.agent.remove_devices_filter(['tap_port1'])
+
+        self.mox.VerifyAll()
+
+    def test_security_group_rule_udpated(self):
+        self.rpc.security_group_rules_for_devices.return_value = self.devices2
+        self._replay_iptables(IPTABLES_FILTER_2, IPTABLES_FILTER_V6_2)
+        self._replay_iptables(IPTABLES_FILTER_2_3, IPTABLES_FILTER_V6_2)
+        self.mox.ReplayAll()
+
+        self.agent.prepare_devices_filter(['tap_port1', 'tap_port3'])
+        self.rpc.security_group_rules_for_devices.return_value = self.devices3
+        self.agent.security_groups_rule_updated(['security_group1'])
+
+        self.mox.VerifyAll()