Implement Allowed Address Pairs
The following patch adds the concept of allowed address pairs. This allows one to add additional ip/mac address pairs on a port to allow traffic that matches those specified values. This is useful in order to leverage dataplane failover mechanisms like vrrp. This patch adds support for the NVP plugin, the OVS plugin, and Ml2. DocImpact implements blueprint: allowed-address-pairs Change-Id: Ie73b3886c5be8e1fc4ade86a0cfb854267f345ac
This commit is contained in:
parent
d0088d333e
commit
d239c85197
@ -28,10 +28,10 @@ LOG = logging.getLogger(__name__)
|
||||
SG_CHAIN = 'sg-chain'
|
||||
INGRESS_DIRECTION = 'ingress'
|
||||
EGRESS_DIRECTION = 'egress'
|
||||
IP_SPOOF_FILTER = 'ip-spoof-filter'
|
||||
SPOOF_FILTER = 'spoof-filter'
|
||||
CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i',
|
||||
EGRESS_DIRECTION: 'o',
|
||||
IP_SPOOF_FILTER: 's'}
|
||||
SPOOF_FILTER: 's'}
|
||||
LINUX_DEV_LEN = 14
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
||||
for port in ports.values():
|
||||
self._remove_chain(port, INGRESS_DIRECTION)
|
||||
self._remove_chain(port, EGRESS_DIRECTION)
|
||||
self._remove_chain(port, IP_SPOOF_FILTER)
|
||||
self._remove_chain(port, SPOOF_FILTER)
|
||||
self._remove_chain_by_name_v4v6(SG_CHAIN)
|
||||
|
||||
def _setup_chain(self, port, DIRECTION):
|
||||
@ -186,34 +186,58 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
||||
if rule['direction'] == direction]
|
||||
|
||||
def _arp_spoofing_rule(self, port):
|
||||
return ['-m mac ! --mac-source %s -j DROP' % port['mac_address']]
|
||||
return '-m mac ! --mac-source %s -j DROP' % port['mac_address']
|
||||
|
||||
def _setup_ip_spoof_filter_chain(self, port, table, addresses, rules):
|
||||
if len(addresses) == 1:
|
||||
rules.append('! -s %s -j DROP' % addresses[0])
|
||||
elif addresses:
|
||||
chain_name = self._port_chain_name(port, IP_SPOOF_FILTER)
|
||||
def _setup_spoof_filter_chain(self, port, table, mac_ip_pairs, rules):
|
||||
if mac_ip_pairs:
|
||||
chain_name = self._port_chain_name(port, SPOOF_FILTER)
|
||||
table.add_chain(chain_name)
|
||||
for ip in addresses:
|
||||
table.add_rule(chain_name, '-s %s -j RETURN' % ip)
|
||||
for mac, ip in mac_ip_pairs:
|
||||
if ip is None:
|
||||
# If fixed_ips is [] this rule will be added to the end
|
||||
# of the list after the allowed_address_pair rules.
|
||||
table.add_rule(chain_name,
|
||||
'-m mac --mac-source %s -j RETURN'
|
||||
% mac)
|
||||
else:
|
||||
table.add_rule(chain_name,
|
||||
'-m mac --mac-source %s -s %s -j RETURN'
|
||||
% (mac, ip))
|
||||
table.add_rule(chain_name, '-j DROP')
|
||||
rules.append('-j $%s' % chain_name)
|
||||
|
||||
def _ip_spoofing_rule(self, port, ipv4_rules, ipv6_rules):
|
||||
def _build_ipv4v6_mac_ip_list(self, mac, ip_address, mac_ipv4_pairs,
|
||||
mac_ipv6_pairs):
|
||||
if netaddr.IPNetwork(ip_address).version == 4:
|
||||
mac_ipv4_pairs.append((mac, ip_address))
|
||||
else:
|
||||
mac_ipv6_pairs.append((mac, ip_address))
|
||||
|
||||
def _spoofing_rule(self, port, ipv4_rules, ipv6_rules):
|
||||
#Note(nati) allow dhcp or RA packet
|
||||
ipv4_rules += ['-p udp -m udp --sport 68 --dport 67 -j RETURN']
|
||||
ipv6_rules += ['-p icmpv6 -j RETURN']
|
||||
ipv4_addresses = []
|
||||
ipv6_addresses = []
|
||||
mac_ipv4_pairs = []
|
||||
mac_ipv6_pairs = []
|
||||
|
||||
if isinstance(port.get('allowed_address_pairs'), list):
|
||||
for address_pair in port['allowed_address_pairs']:
|
||||
self._build_ipv4v6_mac_ip_list(address_pair['mac_address'],
|
||||
address_pair['ip_address'],
|
||||
mac_ipv4_pairs,
|
||||
mac_ipv6_pairs)
|
||||
|
||||
for ip in port['fixed_ips']:
|
||||
if netaddr.IPAddress(ip).version == 4:
|
||||
ipv4_addresses.append(ip)
|
||||
else:
|
||||
ipv6_addresses.append(ip)
|
||||
self._setup_ip_spoof_filter_chain(port, self.iptables.ipv4['filter'],
|
||||
ipv4_addresses, ipv4_rules)
|
||||
self._setup_ip_spoof_filter_chain(port, self.iptables.ipv6['filter'],
|
||||
ipv6_addresses, ipv6_rules)
|
||||
self._build_ipv4v6_mac_ip_list(port['mac_address'], ip,
|
||||
mac_ipv4_pairs, mac_ipv6_pairs)
|
||||
if not port['fixed_ips']:
|
||||
mac_ipv4_pairs.append((port['mac_address'], None))
|
||||
mac_ipv6_pairs.append((port['mac_address'], None))
|
||||
|
||||
self._setup_spoof_filter_chain(port, self.iptables.ipv4['filter'],
|
||||
mac_ipv4_pairs, ipv4_rules)
|
||||
self._setup_spoof_filter_chain(port, self.iptables.ipv6['filter'],
|
||||
mac_ipv6_pairs, ipv6_rules)
|
||||
|
||||
def _drop_dhcp_rule(self):
|
||||
#Note(nati) Drop dhcp packet from VM
|
||||
@ -231,9 +255,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
||||
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,
|
||||
self._spoofing_rule(port,
|
||||
ipv4_iptables_rule,
|
||||
ipv6_iptables_rule)
|
||||
ipv4_iptables_rule += self._drop_dhcp_rule()
|
||||
|
121
neutron/db/allowedaddresspairs_db.py
Normal file
121
neutron/db/allowedaddresspairs_db.py
Normal file
@ -0,0 +1,121 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Nicira Networks, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Aaron Rosen, Nicira, Inc
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
|
||||
|
||||
class AllowedAddressPair(model_base.BASEV2):
|
||||
port_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ports.id', ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
mac_address = sa.Column(sa.String(32), nullable=False, primary_key=True)
|
||||
ip_address = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||
|
||||
port = orm.relationship(
|
||||
models_v2.Port,
|
||||
backref=orm.backref("allowed_address_pairs",
|
||||
lazy="joined", cascade="delete"))
|
||||
|
||||
|
||||
class AllowedAddressPairsMixin(object):
|
||||
"""Mixin class for allowed address pairs."""
|
||||
|
||||
def _process_create_allowed_address_pairs(self, context, port,
|
||||
allowed_address_pairs):
|
||||
if not attr.is_attr_set(allowed_address_pairs):
|
||||
return []
|
||||
with context.session.begin(subtransactions=True):
|
||||
for address_pair in allowed_address_pairs:
|
||||
# use port.mac_address if no mac address in address pair
|
||||
if 'mac_address' not in address_pair:
|
||||
address_pair['mac_address'] = port['mac_address']
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
if ((fixed_ip['ip_address'] == address_pair['ip_address'])
|
||||
and (port['mac_address'] ==
|
||||
address_pair['mac_address'])):
|
||||
#TODO(arosen) - need to query for address pairs
|
||||
# to check for same condition if fixed_ips change to
|
||||
# be an address pair.
|
||||
raise addr_pair.AddressPairMatchesPortFixedIPAndMac()
|
||||
db_pair = AllowedAddressPair(
|
||||
port_id=port['id'],
|
||||
mac_address=address_pair['mac_address'],
|
||||
ip_address=address_pair['ip_address'])
|
||||
context.session.add(db_pair)
|
||||
|
||||
return allowed_address_pairs
|
||||
|
||||
def get_allowed_address_pairs(self, context, port_id):
|
||||
pairs = (context.session.query(AllowedAddressPair).
|
||||
filter_by(port_id=port_id))
|
||||
return [self._make_allowed_address_pairs_dict(pair)
|
||||
for pair in pairs]
|
||||
|
||||
def _extend_port_dict_allowed_address_pairs(self, port_res, port_db):
|
||||
# If port_db is provided, allowed address pairs will be accessed via
|
||||
# sqlalchemy models. As they're loaded together with ports this
|
||||
# will not cause an extra query.
|
||||
allowed_address_pairs = [
|
||||
self._make_allowed_address_pairs_dict(address_pair) for
|
||||
address_pair in port_db.allowed_address_pairs]
|
||||
port_res[addr_pair.ADDRESS_PAIRS] = allowed_address_pairs
|
||||
return port_res
|
||||
|
||||
# Register dict extend functions for ports
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||
attr.PORTS, ['_extend_port_dict_allowed_address_pairs'])
|
||||
|
||||
def _delete_allowed_address_pairs(self, context, id):
|
||||
query = self._model_query(context, AllowedAddressPair)
|
||||
with context.session.begin(subtransactions=True):
|
||||
query.filter(AllowedAddressPair.port_id == id).delete()
|
||||
|
||||
def _make_allowed_address_pairs_dict(self, allowed_address_pairs,
|
||||
fields=None):
|
||||
res = {'mac_address': allowed_address_pairs['mac_address'],
|
||||
'ip_address': allowed_address_pairs['ip_address']}
|
||||
return self._fields(res, fields)
|
||||
|
||||
def _has_address_pairs(self, port):
|
||||
return (attr.is_attr_set(port['port'][addr_pair.ADDRESS_PAIRS])
|
||||
and port['port'][addr_pair.ADDRESS_PAIRS] != [])
|
||||
|
||||
def _check_update_has_allowed_address_pairs(self, port):
|
||||
"""Determine if request has an allowed address pair.
|
||||
|
||||
Return True if the port parameter has a non-empty
|
||||
'allowed_address_pairs' attribute. Otherwise returns False.
|
||||
"""
|
||||
return (addr_pair.ADDRESS_PAIRS in port['port'] and
|
||||
self._has_address_pairs(port))
|
||||
|
||||
def _check_update_deletes_allowed_address_pairs(self, port):
|
||||
"""Determine if request deletes address pair.
|
||||
|
||||
Return True if port has as a allowed address pair and its value
|
||||
is either [] or not is_attr_set, otherwise return False
|
||||
"""
|
||||
return (addr_pair.ADDRESS_PAIRS in port['port'] and
|
||||
not self._has_address_pairs(port))
|
@ -0,0 +1,63 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""allowedaddresspairs
|
||||
|
||||
Revision ID: 1efb85914233
|
||||
Revises: 51b4de912379
|
||||
Create Date: 2013-07-23 12:56:00.402855
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1efb85914233'
|
||||
down_revision = '51b4de912379'
|
||||
|
||||
# Change to ['*'] if this migration applies to all plugins
|
||||
|
||||
migration_for_plugins = [
|
||||
'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
|
||||
'neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2',
|
||||
'neutron.plugins.cisco.network_plugin.PluginV2',
|
||||
'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||
]
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import migration
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
if not migration.should_run(active_plugins, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.create_table(
|
||||
'allowedaddresspairs',
|
||||
sa.Column('port_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('mac_address', sa.String(length=32), nullable=False),
|
||||
sa.Column('ip_address', sa.String(length=64), nullable=False),
|
||||
sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('port_id', 'mac_address', 'ip_address'),
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
if not migration.should_run(active_plugins, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.drop_table('allowedaddresspairs')
|
@ -174,12 +174,20 @@ class SecurityGroupServerRpcCallbackMixin(object):
|
||||
sg_binding_sgid = sg_db.SecurityGroupPortBinding.security_group_id
|
||||
|
||||
query = context.session.query(sg_binding_sgid,
|
||||
models_v2.Port,
|
||||
models_v2.IPAllocation.ip_address)
|
||||
query = query.join(models_v2.IPAllocation,
|
||||
ip_port == sg_binding_port)
|
||||
query = query.join(models_v2.Port,
|
||||
ip_port == models_v2.Port.id)
|
||||
query = query.filter(sg_binding_sgid.in_(remote_group_ids))
|
||||
for security_group_id, ip_address in query:
|
||||
for security_group_id, port, ip_address in query:
|
||||
ips_by_group[security_group_id].append(ip_address)
|
||||
# if there are allowed_address_pairs add them
|
||||
if getattr(port, 'allowed_address_pairs', None):
|
||||
for address_pair in port.allowed_address_pairs:
|
||||
ips_by_group[security_group_id].append(
|
||||
address_pair['ip_address'])
|
||||
return ips_by_group
|
||||
|
||||
def _select_remote_group_ids(self, ports):
|
||||
@ -231,12 +239,12 @@ class SecurityGroupServerRpcCallbackMixin(object):
|
||||
if ip in port.get('fixed_ips', []):
|
||||
continue
|
||||
ip_rule = base_rule.copy()
|
||||
version = netaddr.IPAddress(ip).version
|
||||
version = netaddr.IPNetwork(ip).version
|
||||
ethertype = 'IPv%s' % version
|
||||
if base_rule['ethertype'] != ethertype:
|
||||
continue
|
||||
ip_rule[direction_ip_prefix] = "%s/%s" % (
|
||||
ip, IP_MASK[ethertype])
|
||||
ip_rule[direction_ip_prefix] = str(
|
||||
netaddr.IPNetwork(ip).cidr)
|
||||
updated_rule.append(ip_rule)
|
||||
port['security_group_rules'] = updated_rule
|
||||
return ports
|
||||
|
122
neutron/extensions/allowedaddresspairs.py
Normal file
122
neutron/extensions/allowedaddresspairs.py
Normal file
@ -0,0 +1,122 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Nicira Networks, Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# @author: Aaron Rosen, Nicira, Inc
|
||||
|
||||
import webob.exc
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.common import exceptions as nexception
|
||||
|
||||
|
||||
class AllowedAddressPairsMissingIP(nexception.InvalidInput):
|
||||
message = _("AllowedAddressPair must contain ip_address")
|
||||
|
||||
|
||||
class AddressPairAndPortSecurityRequired(nexception.Conflict):
|
||||
message = _("Port Security must be enabled in order to have allowed "
|
||||
"address pairs on a port.")
|
||||
|
||||
|
||||
class DuplicateAddressPairInRequest(nexception.InvalidInput):
|
||||
message = _("Request contains duplicate address pair: "
|
||||
"mac_address %(mac_address)s ip_address %(ip_address)s.")
|
||||
|
||||
|
||||
class AddressPairMatchesPortFixedIPAndMac(nexception.InvalidInput):
|
||||
message = _("Port's Fixed IP and Mac Address match an address pair entry.")
|
||||
|
||||
|
||||
def _validate_allowed_address_pairs(address_pairs, valid_values=None):
|
||||
unique_check = {}
|
||||
for address_pair in address_pairs:
|
||||
# mac_address is optional, if not set we use the mac on the port
|
||||
if 'mac_address' in address_pair:
|
||||
msg = attr._validate_mac_address(address_pair['mac_address'])
|
||||
if msg:
|
||||
raise webob.exc.HTTPBadRequest(msg)
|
||||
if 'ip_address' not in address_pair:
|
||||
raise AllowedAddressPairsMissingIP()
|
||||
|
||||
mac = address_pair.get('mac_address')
|
||||
ip_address = address_pair['ip_address']
|
||||
if (mac, ip_address) not in unique_check:
|
||||
unique_check[(mac, ip_address)] = None
|
||||
else:
|
||||
raise DuplicateAddressPairInRequest(mac_address=mac,
|
||||
ip_address=ip_address)
|
||||
|
||||
invalid_attrs = set(address_pair.keys()) - set(['mac_address',
|
||||
'ip_address'])
|
||||
if invalid_attrs:
|
||||
msg = (_("Unrecognized attribute(s) '%s'") %
|
||||
', '.join(set(address_pair.keys()) -
|
||||
set(['mac_address', 'ip_address'])))
|
||||
raise webob.exc.HTTPBadRequest(msg)
|
||||
|
||||
if '/' in ip_address:
|
||||
msg = attr._validate_subnet(ip_address)
|
||||
else:
|
||||
msg = attr._validate_ip_address(ip_address)
|
||||
if msg:
|
||||
raise webob.exc.HTTPBadRequest(msg)
|
||||
|
||||
attr.validators['type:validate_allowed_address_pairs'] = (
|
||||
_validate_allowed_address_pairs)
|
||||
|
||||
ADDRESS_PAIRS = 'allowed_address_pairs'
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'ports': {
|
||||
ADDRESS_PAIRS: {'allow_post': True, 'allow_put': True,
|
||||
'convert_list_to':
|
||||
attr.convert_kvp_list_to_dict,
|
||||
'validate': {'type:validate_allowed_address_pairs':
|
||||
None},
|
||||
'enforce_policy': True,
|
||||
'default': attr.ATTR_NOT_SPECIFIED,
|
||||
'is_visible': True},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Allowedaddresspairs(object):
|
||||
"""Extension class supporting allowed address pairs."""
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "Allowed Address Pairs"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "allowed-address-pairs"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Provides allowed address pairs"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return "http://docs.openstack.org/ext/allowedaddresspairs/api/v2.0"
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2013-07-23T10:00:00-00:00"
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
else:
|
||||
return {}
|
@ -23,12 +23,14 @@ from neutron.common import constants as const
|
||||
from neutron.common import exceptions as exc
|
||||
from neutron.common import topics
|
||||
from neutron.db import agentschedulers_db
|
||||
from neutron.db import allowedaddresspairs_db as addr_pair_db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import extraroute_db
|
||||
from neutron.db import l3_gwmode_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.db import quota_db # noqa
|
||||
from neutron.db import securitygroups_rpc_base as sg_db_rpc
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import multiprovidernet as mpnet
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.extensions import providernet as provider
|
||||
@ -57,7 +59,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
l3_gwmode_db.L3_NAT_db_mixin,
|
||||
sg_db_rpc.SecurityGroupServerRpcMixin,
|
||||
agentschedulers_db.L3AgentSchedulerDbMixin,
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin):
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
addr_pair_db.AllowedAddressPairsMixin):
|
||||
"""Implement the Neutron L2 abstractions using modules.
|
||||
|
||||
Ml2Plugin is a Neutron plugin based on separately extensible sets
|
||||
@ -79,7 +82,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
"binding", "quotas", "security-group",
|
||||
"agent", "l3_agent_scheduler",
|
||||
"dhcp_agent_scheduler", "ext-gw-mode",
|
||||
"multi-provider"]
|
||||
"multi-provider", "allowed-address-pairs"]
|
||||
|
||||
@property
|
||||
def supported_extension_aliases(self):
|
||||
@ -452,6 +455,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
mech_context = driver_context.PortContext(self, context, result,
|
||||
network)
|
||||
self._process_port_binding(mech_context, attrs)
|
||||
result[addr_pair.ADDRESS_PAIRS] = (
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, result,
|
||||
attrs.get(addr_pair.ADDRESS_PAIRS)))
|
||||
self.mechanism_manager.create_port_precommit(mech_context)
|
||||
|
||||
try:
|
||||
@ -473,7 +480,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
original_port = super(Ml2Plugin, self).get_port(context, id)
|
||||
updated_port = super(Ml2Plugin, self).update_port(context, id,
|
||||
port)
|
||||
need_port_update_notify = self.update_security_group_on_port(
|
||||
if addr_pair.ADDRESS_PAIRS in port['port']:
|
||||
self._delete_allowed_address_pairs(context, id)
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, updated_port,
|
||||
port['port'][addr_pair.ADDRESS_PAIRS])
|
||||
need_port_update_notify = True
|
||||
need_port_update_notify |= self.update_security_group_on_port(
|
||||
context, id, port, original_port, updated_port)
|
||||
network = self.get_network(context, original_port['network_id'])
|
||||
mech_context = driver_context.PortContext(
|
||||
|
@ -34,6 +34,7 @@ from neutron.common import exceptions as q_exc
|
||||
from neutron.common import utils
|
||||
from neutron import context as q_context
|
||||
from neutron.db import agentschedulers_db
|
||||
from neutron.db import allowedaddresspairs_db as addr_pair_db
|
||||
from neutron.db import api as db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import extraroute_db
|
||||
@ -44,6 +45,7 @@ from neutron.db import portbindings_db
|
||||
from neutron.db import portsecurity_db
|
||||
from neutron.db import quota_db # noqa
|
||||
from neutron.db import securitygroups_db
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import extraroute
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import multiprovidernet as mpnet
|
||||
@ -110,7 +112,8 @@ def create_nvp_cluster(cluster_opts, concurrent_connections,
|
||||
return cluster
|
||||
|
||||
|
||||
class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
db_base_plugin_v2.NeutronDbPluginV2,
|
||||
dhcpmeta_modes.DhcpMetadataAccess,
|
||||
dist_rtr.DistributedRouter_mixin,
|
||||
@ -130,6 +133,7 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
"""
|
||||
|
||||
supported_extension_aliases = ["agent",
|
||||
"allowed-address-pairs",
|
||||
"binding",
|
||||
"dhcp_agent_scheduler",
|
||||
"dist-router",
|
||||
@ -421,7 +425,8 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
port_data[psec.PORTSECURITY],
|
||||
port_data[ext_sg.SECURITYGROUPS],
|
||||
port_data[ext_qos.QUEUE],
|
||||
port_data.get(mac_ext.MAC_LEARNING))
|
||||
port_data.get(mac_ext.MAC_LEARNING),
|
||||
port_data.get(addr_pair.ADDRESS_PAIRS))
|
||||
|
||||
def _handle_create_port_exception(self, context, port_id,
|
||||
ls_uuid, lp_uuid):
|
||||
@ -1128,6 +1133,18 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
port_data[psec.PORTSECURITY] = port_security
|
||||
self._process_port_port_security_create(
|
||||
context, port_data, neutron_db)
|
||||
# allowed address pair checks
|
||||
if attr.is_attr_set(port_data.get(addr_pair.ADDRESS_PAIRS)):
|
||||
if not port_security:
|
||||
raise addr_pair.AddressPairAndPortSecurityRequired()
|
||||
else:
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, neutron_db,
|
||||
port_data[addr_pair.ADDRESS_PAIRS])
|
||||
else:
|
||||
# remove ATTR_NOT_SPECIFIED
|
||||
port_data[addr_pair.ADDRESS_PAIRS] = None
|
||||
|
||||
# security group extension checks
|
||||
if port_security and has_ip:
|
||||
self._ensure_default_security_group_on_port(context, port)
|
||||
@ -1185,6 +1202,9 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
delete_security_groups = self._check_update_deletes_security_groups(
|
||||
port)
|
||||
has_security_groups = self._check_update_has_security_groups(port)
|
||||
delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
|
||||
port)
|
||||
has_addr_pairs = self._check_update_has_allowed_address_pairs(port)
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
ret_port = super(NvpPluginV2, self).update_port(
|
||||
@ -1198,7 +1218,28 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
ret_port.update(port['port'])
|
||||
tenant_id = self._get_tenant_id_for_create(context, ret_port)
|
||||
|
||||
# populate port_security setting
|
||||
if psec.PORTSECURITY not in port['port']:
|
||||
ret_port[psec.PORTSECURITY] = self._get_port_security_binding(
|
||||
context, id)
|
||||
has_ip = self._ip_on_port(ret_port)
|
||||
# validate port security and allowed address pairs
|
||||
if not ret_port[psec.PORTSECURITY]:
|
||||
# has address pairs in request
|
||||
if has_addr_pairs:
|
||||
raise addr_pair.AddressPairAndPortSecurityRequired()
|
||||
elif not delete_addr_pairs:
|
||||
# check if address pairs are in db
|
||||
ret_port[addr_pair.ADDRESS_PAIRS] = (
|
||||
self.get_allowed_address_pairs(context, id))
|
||||
if ret_port[addr_pair.ADDRESS_PAIRS]:
|
||||
raise addr_pair.AddressPairAndPortSecurityRequired()
|
||||
|
||||
if (delete_addr_pairs or has_addr_pairs):
|
||||
# delete address pairs and read them in
|
||||
self._delete_allowed_address_pairs(context, id)
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, ret_port, ret_port[addr_pair.ADDRESS_PAIRS])
|
||||
# checks if security groups were updated adding/modifying
|
||||
# security groups, port security is set and port has ip
|
||||
if not (has_ip and ret_port[psec.PORTSECURITY]):
|
||||
@ -1251,7 +1292,8 @@ class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
ret_port[psec.PORTSECURITY],
|
||||
ret_port[ext_sg.SECURITYGROUPS],
|
||||
ret_port[ext_qos.QUEUE],
|
||||
ret_port.get(mac_ext.MAC_LEARNING))
|
||||
ret_port.get(mac_ext.MAC_LEARNING),
|
||||
ret_port.get(addr_pair.ADDRESS_PAIRS))
|
||||
|
||||
# Update the port status from nvp. If we fail here hide it
|
||||
# since the port was successfully updated but we were not
|
||||
|
@ -696,7 +696,8 @@ def get_port(cluster, network, port, relations=None):
|
||||
|
||||
def _configure_extensions(lport_obj, mac_address, fixed_ips,
|
||||
port_security_enabled, security_profiles,
|
||||
queue_id, mac_learning_enabled):
|
||||
queue_id, mac_learning_enabled,
|
||||
allowed_address_pairs):
|
||||
lport_obj['allowed_address_pairs'] = []
|
||||
if port_security_enabled:
|
||||
for fixed_ip in fixed_ips:
|
||||
@ -714,13 +715,17 @@ def _configure_extensions(lport_obj, mac_address, fixed_ips,
|
||||
if mac_learning_enabled is not None:
|
||||
lport_obj["mac_learning"] = mac_learning_enabled
|
||||
lport_obj["type"] = "LogicalSwitchPortConfig"
|
||||
for address_pair in list(allowed_address_pairs or []):
|
||||
lport_obj['allowed_address_pairs'].append(
|
||||
{'mac_address': address_pair['mac_address'],
|
||||
'ip_address': address_pair['ip_address']})
|
||||
|
||||
|
||||
def update_port(cluster, lswitch_uuid, lport_uuid, neutron_port_id, tenant_id,
|
||||
display_name, device_id, admin_status_enabled,
|
||||
mac_address=None, fixed_ips=None, port_security_enabled=None,
|
||||
security_profiles=None, queue_id=None,
|
||||
mac_learning_enabled=None):
|
||||
mac_learning_enabled=None, allowed_address_pairs=None):
|
||||
# device_id can be longer than 40 so we rehash it
|
||||
hashed_device_id = hashlib.sha1(device_id).hexdigest()
|
||||
lport_obj = dict(
|
||||
@ -733,7 +738,8 @@ def update_port(cluster, lswitch_uuid, lport_uuid, neutron_port_id, tenant_id,
|
||||
|
||||
_configure_extensions(lport_obj, mac_address, fixed_ips,
|
||||
port_security_enabled, security_profiles,
|
||||
queue_id, mac_learning_enabled)
|
||||
queue_id, mac_learning_enabled,
|
||||
allowed_address_pairs)
|
||||
|
||||
path = "/ws.v1/lswitch/" + lswitch_uuid + "/lport/" + lport_uuid
|
||||
try:
|
||||
@ -753,7 +759,7 @@ def create_lport(cluster, lswitch_uuid, tenant_id, neutron_port_id,
|
||||
display_name, device_id, admin_status_enabled,
|
||||
mac_address=None, fixed_ips=None, port_security_enabled=None,
|
||||
security_profiles=None, queue_id=None,
|
||||
mac_learning_enabled=None):
|
||||
mac_learning_enabled=None, allowed_address_pairs=None):
|
||||
"""Creates a logical port on the assigned logical switch."""
|
||||
# device_id can be longer than 40 so we rehash it
|
||||
hashed_device_id = hashlib.sha1(device_id).hexdigest()
|
||||
@ -769,7 +775,8 @@ def create_lport(cluster, lswitch_uuid, tenant_id, neutron_port_id,
|
||||
|
||||
_configure_extensions(lport_obj, mac_address, fixed_ips,
|
||||
port_security_enabled, security_profiles,
|
||||
queue_id, mac_learning_enabled)
|
||||
queue_id, mac_learning_enabled,
|
||||
allowed_address_pairs)
|
||||
|
||||
path = _build_uri_path(LSWITCHPORT_RESOURCE,
|
||||
parent_resource_id=lswitch_uuid)
|
||||
|
@ -36,6 +36,7 @@ from neutron.common import topics
|
||||
from neutron.common import utils
|
||||
from neutron.db import agents_db
|
||||
from neutron.db import agentschedulers_db
|
||||
from neutron.db import allowedaddresspairs_db as addr_pair_db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import dhcp_rpc_base
|
||||
from neutron.db import extradhcpopt_db
|
||||
@ -45,6 +46,7 @@ from neutron.db import l3_rpc_base
|
||||
from neutron.db import portbindings_db
|
||||
from neutron.db import quota_db # noqa
|
||||
from neutron.db import securitygroups_rpc_base as sg_db_rpc
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import extra_dhcp_opt as edo_ext
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.extensions import providernet as provider
|
||||
@ -225,7 +227,8 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
agentschedulers_db.L3AgentSchedulerDbMixin,
|
||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||
portbindings_db.PortBindingMixin,
|
||||
extradhcpopt_db.ExtraDhcpOptMixin):
|
||||
extradhcpopt_db.ExtraDhcpOptMixin,
|
||||
addr_pair_db.AllowedAddressPairsMixin):
|
||||
|
||||
"""Implement the Neutron abstractions using Open vSwitch.
|
||||
|
||||
@ -256,7 +259,8 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
"agent", "extraroute",
|
||||
"l3_agent_scheduler",
|
||||
"dhcp_agent_scheduler",
|
||||
"extra_dhcp_opt"]
|
||||
"extra_dhcp_opt",
|
||||
"allowed-address-pairs"]
|
||||
|
||||
@property
|
||||
def supported_extension_aliases(self):
|
||||
@ -547,6 +551,10 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
self._process_port_create_security_group(context, port, sgids)
|
||||
self._process_port_create_extra_dhcp_opts(context, port,
|
||||
dhcp_opts)
|
||||
port[addr_pair.ADDRESS_PAIRS] = (
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, port,
|
||||
port_data.get(addr_pair.ADDRESS_PAIRS)))
|
||||
self.notify_security_groups_member_updated(context, port)
|
||||
return port
|
||||
|
||||
@ -558,7 +566,14 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
context, id)
|
||||
updated_port = super(OVSNeutronPluginV2, self).update_port(
|
||||
context, id, port)
|
||||
need_port_update_notify = self.update_security_group_on_port(
|
||||
if addr_pair.ADDRESS_PAIRS in port['port']:
|
||||
self._delete_allowed_address_pairs(context, id)
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, updated_port,
|
||||
port['port'][addr_pair.ADDRESS_PAIRS])
|
||||
need_port_update_notify = True
|
||||
|
||||
need_port_update_notify |= self.update_security_group_on_port(
|
||||
context, id, port, original_port, updated_port)
|
||||
self._process_portbindings_create_and_update(context,
|
||||
port['port'],
|
||||
|
@ -87,3 +87,15 @@ class TestMl2SecurityGroups(Ml2SecurityGroupsTestCase,
|
||||
|
||||
class TestMl2SecurityGroupsXML(TestMl2SecurityGroups):
|
||||
fmt = 'xml'
|
||||
|
||||
|
||||
class TestMl2SGServerRpcCallBack(
|
||||
Ml2SecurityGroupsTestCase,
|
||||
test_sg_rpc.SGServerRpcCallBackMixinTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestMl2SGServerRpcCallBackXML(
|
||||
Ml2SecurityGroupsTestCase,
|
||||
test_sg_rpc.SGServerRpcCallBackMixinTestCaseXML):
|
||||
pass
|
||||
|
@ -53,6 +53,7 @@ from neutron.tests.unit.nicira import PLUGIN_NAME
|
||||
from neutron.tests.unit.nicira import STUBS_PATH
|
||||
import neutron.tests.unit.nicira.test_networkgw as test_l2_gw
|
||||
import neutron.tests.unit.test_db_plugin as test_plugin
|
||||
import neutron.tests.unit.test_extension_allowedaddresspairs as test_addr_pair
|
||||
import neutron.tests.unit.test_extension_ext_gw_mode as test_ext_gw_mode
|
||||
import neutron.tests.unit.test_extension_portsecurity as psec
|
||||
import neutron.tests.unit.test_extension_security_group as ext_sg
|
||||
@ -352,6 +353,11 @@ class TestNiciraPortSecurity(NiciraPortSecurityTestCase,
|
||||
pass
|
||||
|
||||
|
||||
class TestNiciraAllowedAddressPairs(test_addr_pair.TestAllowedAddressPairs,
|
||||
NiciraPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class NiciraSecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -53,6 +53,18 @@ class OpenvswitchSecurityGroupsTestCase(test_sg.SecurityGroupDBTestCase):
|
||||
attributes.RESOURCE_ATTRIBUTE_MAP = self._attribute_map_bk_
|
||||
|
||||
|
||||
class TestOpenvswitchSGServerRpcCallBack(
|
||||
OpenvswitchSecurityGroupsTestCase,
|
||||
test_sg_rpc.SGServerRpcCallBackMixinTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestOpenvswitchSGServerRpcCallBackXML(
|
||||
OpenvswitchSecurityGroupsTestCase,
|
||||
test_sg_rpc.SGServerRpcCallBackMixinTestCaseXML):
|
||||
pass
|
||||
|
||||
|
||||
class TestOpenvswitchSecurityGroups(OpenvswitchSecurityGroupsTestCase,
|
||||
test_sg.TestSecurityGroups,
|
||||
test_sg_rpc.SGNotificationTestMixin):
|
||||
|
268
neutron/tests/unit/test_extension_allowedaddresspairs.py
Normal file
268
neutron/tests/unit/test_extension_allowedaddresspairs.py
Normal file
@ -0,0 +1,268 @@
|
||||
# Copyright (c) 2013 OpenStack Foundation.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.common.test_lib import test_config
|
||||
from neutron.db import allowedaddresspairs_db as addr_pair_db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import portsecurity_db
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import portsecurity as psec
|
||||
from neutron.manager import NeutronManager
|
||||
from neutron.tests.unit import test_db_plugin
|
||||
|
||||
DB_PLUGIN_KLASS = ('neutron.tests.unit.test_extension_allowedaddresspairs.'
|
||||
'AllowedAddressPairTestPlugin')
|
||||
|
||||
|
||||
class AllowedAddressPairTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
||||
def setUp(self, plugin=None):
|
||||
super(AllowedAddressPairTestCase, self).setUp()
|
||||
|
||||
# Check if a plugin supports security groups
|
||||
plugin_obj = NeutronManager.get_plugin()
|
||||
self._skip_port_security = ('port-security' not in
|
||||
plugin_obj.supported_extension_aliases)
|
||||
|
||||
|
||||
class AllowedAddressPairTestPlugin(portsecurity_db.PortSecurityDbMixin,
|
||||
db_base_plugin_v2.NeutronDbPluginV2,
|
||||
addr_pair_db.AllowedAddressPairsMixin):
|
||||
|
||||
"""Test plugin that implements necessary calls on create/delete port for
|
||||
associating ports with port security and allowed address pairs.
|
||||
"""
|
||||
|
||||
supported_extension_aliases = ["allowed-address-pairs"]
|
||||
|
||||
def create_port(self, context, port):
|
||||
p = port['port']
|
||||
with context.session.begin(subtransactions=True):
|
||||
neutron_db = super(AllowedAddressPairTestPlugin, self).create_port(
|
||||
context, port)
|
||||
p.update(neutron_db)
|
||||
if attr.is_attr_set(p.get(addr_pair.ADDRESS_PAIRS)):
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, p,
|
||||
p[addr_pair.ADDRESS_PAIRS])
|
||||
else:
|
||||
p[addr_pair.ADDRESS_PAIRS] = None
|
||||
|
||||
return port['port']
|
||||
|
||||
def update_port(self, context, id, port):
|
||||
delete_addr_pairs = self._check_update_deletes_allowed_address_pairs(
|
||||
port)
|
||||
has_addr_pairs = self._check_update_has_allowed_address_pairs(port)
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
ret_port = super(AllowedAddressPairTestPlugin, self).update_port(
|
||||
context, id, port)
|
||||
# copy values over - but not fixed_ips
|
||||
port['port'].pop('fixed_ips', None)
|
||||
ret_port.update(port['port'])
|
||||
|
||||
if (delete_addr_pairs or has_addr_pairs):
|
||||
# delete address pairds and readd them
|
||||
self._delete_allowed_address_pairs(context, id)
|
||||
self._process_create_allowed_address_pairs(
|
||||
context, ret_port,
|
||||
ret_port[addr_pair.ADDRESS_PAIRS])
|
||||
|
||||
return ret_port
|
||||
|
||||
|
||||
class AllowedAddressPairDBTestCase(AllowedAddressPairTestCase):
|
||||
def setUp(self, plugin=None):
|
||||
test_config['plugin_name_v2'] = DB_PLUGIN_KLASS
|
||||
super(AllowedAddressPairDBTestCase, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
del test_config['plugin_name_v2']
|
||||
super(AllowedAddressPairDBTestCase, self).tearDown()
|
||||
|
||||
|
||||
class AllowedAddressPairDBTestCaseXML(AllowedAddressPairDBTestCase):
|
||||
fmt = 'xml'
|
||||
|
||||
|
||||
class TestAllowedAddressPairs(AllowedAddressPairDBTestCase):
|
||||
|
||||
def test_create_port_allowed_address_pairs(self):
|
||||
with self.network() as net:
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=(addr_pair.ADDRESS_PAIRS,),
|
||||
allowed_address_pairs=address_pairs)
|
||||
port = self.deserialize(self.fmt, res)
|
||||
self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS],
|
||||
address_pairs)
|
||||
self._delete('ports', port['port']['id'])
|
||||
|
||||
def test_create_port_security_true_allowed_address_pairs(self):
|
||||
if self._skip_port_security:
|
||||
self.skipTest("Plugin does not implement port-security extension")
|
||||
|
||||
with self.network() as net:
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=('port_security_enabled',
|
||||
addr_pair.ADDRESS_PAIRS,),
|
||||
port_security_enabled=True,
|
||||
allowed_address_pairs=address_pairs)
|
||||
port = self.deserialize(self.fmt, res)
|
||||
self.assertEqual(port['port'][psec.PORTSECURITY], True)
|
||||
self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS],
|
||||
address_pairs)
|
||||
self._delete('ports', port['port']['id'])
|
||||
|
||||
def test_create_port_security_false_allowed_address_pairs(self):
|
||||
if self._skip_port_security:
|
||||
self.skipTest("Plugin does not implement port-security extension")
|
||||
|
||||
with self.network() as net:
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=('port_security_enabled',
|
||||
addr_pair.ADDRESS_PAIRS,),
|
||||
port_security_enabled=False,
|
||||
allowed_address_pairs=address_pairs)
|
||||
self.deserialize(self.fmt, res)
|
||||
self.assertEqual(res.status_int, 409)
|
||||
|
||||
def test_create_port_bad_mac(self):
|
||||
address_pairs = [{'mac_address': 'invalid_mac',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
self._create_port_with_address_pairs(address_pairs, 400)
|
||||
|
||||
def test_create_port_bad_ip(self):
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1222'}]
|
||||
self._create_port_with_address_pairs(address_pairs, 400)
|
||||
|
||||
def test_create_missing_ip_field(self):
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01'}]
|
||||
self._create_port_with_address_pairs(address_pairs, 400)
|
||||
|
||||
def test_create_duplicate_mac_ip(self):
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'},
|
||||
{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
self._create_port_with_address_pairs(address_pairs, 400)
|
||||
|
||||
def test_create_port_extra_args(self):
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1',
|
||||
'icbb': 'agreed'}]
|
||||
self._create_port_with_address_pairs(address_pairs, 400)
|
||||
|
||||
def _create_port_with_address_pairs(self, address_pairs, ret_code):
|
||||
with self.network() as net:
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=(addr_pair.ADDRESS_PAIRS,),
|
||||
allowed_address_pairs=address_pairs)
|
||||
self.deserialize(self.fmt, res)
|
||||
self.assertEqual(res.status_int, ret_code)
|
||||
|
||||
def test_update_add_address_pairs(self):
|
||||
with self.network() as net:
|
||||
res = self._create_port(self.fmt, net['network']['id'])
|
||||
port = self.deserialize(self.fmt, res)
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
update_port = {'port': {addr_pair.ADDRESS_PAIRS:
|
||||
address_pairs}}
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize(self.fmt, req.get_response(self.api))
|
||||
self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS],
|
||||
address_pairs)
|
||||
self._delete('ports', port['port']['id'])
|
||||
|
||||
def test_create_address_gets_port_mac(self):
|
||||
with self.network() as net:
|
||||
address_pairs = [{'ip_address': '23.23.23.23'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=('port_security_enabled',
|
||||
addr_pair.ADDRESS_PAIRS,),
|
||||
allowed_address_pairs=address_pairs)
|
||||
port = self.deserialize(self.fmt, res)['port']
|
||||
port_addr_mac = port[addr_pair.ADDRESS_PAIRS][0]['mac_address']
|
||||
self.assertEqual(port_addr_mac,
|
||||
port['mac_address'])
|
||||
self._delete('ports', port['id'])
|
||||
|
||||
def test_update_address_pair_to_match_fixed_ip_and_mac(self):
|
||||
with self.network() as net:
|
||||
with self.subnet(network=net):
|
||||
res = self._create_port(self.fmt, net['network']['id'])
|
||||
port = self.deserialize(self.fmt, res)['port']
|
||||
address_pairs = [{'mac_address': port['mac_address'],
|
||||
'ip_address':
|
||||
port['fixed_ips'][0]['ip_address']}]
|
||||
|
||||
update_port = {'port': {addr_pair.ADDRESS_PAIRS:
|
||||
address_pairs}}
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['id'])
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
self._delete('ports', port['id'])
|
||||
|
||||
def test_update_port_security_off_address_pairs(self):
|
||||
if self._skip_port_security:
|
||||
self.skipTest("Plugin does not implement port-security extension")
|
||||
with self.network() as net:
|
||||
with self.subnet(network=net):
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=('port_security_enabled',
|
||||
addr_pair.ADDRESS_PAIRS,),
|
||||
port_security_enabled=True,
|
||||
allowed_address_pairs=address_pairs)
|
||||
port = self.deserialize(self.fmt, res)
|
||||
print port
|
||||
update_port = {'port': {psec.PORTSECURITY: False}}
|
||||
# If plugin implements security groups we also need to remove
|
||||
# the security group on port.
|
||||
plugin_obj = NeutronManager.get_plugin()
|
||||
if 'security-groups' in plugin_obj.supported_extension_aliases:
|
||||
update_port['port']['security_groups'] = []
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 409)
|
||||
self._delete('ports', port['port']['id'])
|
||||
|
||||
def test_create_port_remove_allowed_address_pairs(self):
|
||||
with self.network() as net:
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.1'}]
|
||||
res = self._create_port(self.fmt, net['network']['id'],
|
||||
arg_list=(addr_pair.ADDRESS_PAIRS,),
|
||||
allowed_address_pairs=address_pairs)
|
||||
port = self.deserialize(self.fmt, res)
|
||||
update_port = {'port': {addr_pair.ADDRESS_PAIRS: []}}
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize(self.fmt, req.get_response(self.api))
|
||||
self.assertEqual(port['port'][addr_pair.ADDRESS_PAIRS], [])
|
||||
self._delete('ports', port['port']['id'])
|
@ -97,12 +97,15 @@ class IptablesFirewallTestCase(base.BaseTestCase):
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
|
||||
'sfake_dev', '-m mac --mac-source ff:ff:ff:ff '
|
||||
'-s 10.0.0.1 -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 68 --dport 67 -j RETURN'),
|
||||
call.add_rule('ofake_dev', '! -s 10.0.0.1 -j DROP'),
|
||||
call.add_rule('ofake_dev', '-j $sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 67 --dport 68 -j DROP'),
|
||||
@ -767,12 +770,14 @@ class IptablesFirewallTestCase(base.BaseTestCase):
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -s %s -j RETURN'
|
||||
% prefix),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
dhcp_rule,
|
||||
call.add_rule('ofake_dev', '! -s %s -j DROP' % prefix)]
|
||||
|
||||
call.add_rule('ofake_dev', '-j $sfake_dev')]
|
||||
if ethertype == 'IPv4':
|
||||
calls.append(call.add_rule(
|
||||
'ofake_dev',
|
||||
@ -836,15 +841,16 @@ class IptablesFirewallTestCase(base.BaseTestCase):
|
||||
'INPUT',
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged -j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 '
|
||||
'-j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 68 --dport 67 -j RETURN'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'! -s 10.0.0.1 -j DROP'),
|
||||
call.add_rule('ofake_dev', '-j $sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 67 --dport 68 -j DROP'),
|
||||
@ -889,14 +895,15 @@ class IptablesFirewallTestCase(base.BaseTestCase):
|
||||
'INPUT',
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged -j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 68 --dport 67 -j RETURN'),
|
||||
call.add_rule(
|
||||
'ofake_dev', '! -s 10.0.0.1 -j DROP'),
|
||||
call.add_rule('ofake_dev', '-j $sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 67 --dport 68 -j DROP'),
|
||||
@ -1039,11 +1046,71 @@ class IptablesFirewallTestCase(base.BaseTestCase):
|
||||
'--physdev-is-bridged '
|
||||
'-j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule('sfake_dev', '-s 10.0.0.1 -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-s 10.0.0.2 -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'),
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -s 10.0.0.1 -j RETURN'),
|
||||
call.add_rule(
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -s 10.0.0.2 -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 68 --dport 67 -j RETURN'),
|
||||
call.add_rule('ofake_dev', '-j $sfake_dev'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m 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 RELATED,ESTABLISHED -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_ip_spoofing_no_fixed_ips(self):
|
||||
port = {'device': 'tapfake_dev',
|
||||
'mac_address': 'ff:ff:ff:ff',
|
||||
'fixed_ips': []}
|
||||
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-out tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $sg-chain'),
|
||||
call.add_rule('sg-chain',
|
||||
'-m physdev --physdev-out tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $ifake_dev'),
|
||||
call.add_rule(
|
||||
'ifake_dev', '-m state --state INVALID -j DROP'),
|
||||
call.add_rule(
|
||||
'ifake_dev',
|
||||
'-m state --state RELATED,ESTABLISHED -j RETURN'),
|
||||
call.add_rule('ifake_dev', '-j $sg-fallback'),
|
||||
call.add_chain('ofake_dev'),
|
||||
call.add_rule('FORWARD',
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $sg-chain'),
|
||||
call.add_rule('sg-chain',
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $ofake_dev'),
|
||||
call.add_rule('INPUT',
|
||||
'-m physdev --physdev-in tapfake_dev '
|
||||
'--physdev-is-bridged '
|
||||
'-j $ofake_dev'),
|
||||
call.add_chain('sfake_dev'),
|
||||
call.add_rule(
|
||||
'sfake_dev',
|
||||
'-m mac --mac-source ff:ff:ff:ff -j RETURN'),
|
||||
call.add_rule('sfake_dev', '-j DROP'),
|
||||
call.add_rule(
|
||||
'ofake_dev',
|
||||
'-p udp -m udp --sport 68 --dport 67 -j RETURN'),
|
||||
|
@ -28,7 +28,9 @@ from neutron.agent import rpc as agent_rpc
|
||||
from neutron.agent import securitygroups_rpc as sg_rpc
|
||||
from neutron import context
|
||||
from neutron.db import securitygroups_rpc_base as sg_db_rpc
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import securitygroup as ext_sg
|
||||
from neutron.manager import NeutronManager
|
||||
from neutron.openstack.common.rpc import proxy
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit import test_extension_security_group as test_sg
|
||||
@ -47,7 +49,7 @@ class FakeSGCallback(sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
|
||||
|
||||
|
||||
class SGServerRpcCallBackMixinTestCase(test_sg.SecurityGroupDBTestCase):
|
||||
def setUp(self):
|
||||
def setUp(self, plugin=None):
|
||||
super(SGServerRpcCallBackMixinTestCase, self).setUp()
|
||||
self.rpc = FakeSGCallback()
|
||||
|
||||
@ -103,6 +105,69 @@ class SGServerRpcCallBackMixinTestCase(test_sg.SecurityGroupDBTestCase):
|
||||
expected)
|
||||
self._delete('ports', port_id1)
|
||||
|
||||
def test_security_group_rules_for_devices_ipv4_ingress_addr_pair(self):
|
||||
plugin_obj = NeutronManager.get_plugin()
|
||||
if ('allowed-address-pairs'
|
||||
not in plugin_obj.supported_extension_aliases):
|
||||
self.skipTest("Test depeneds on allowed-address-pairs extension")
|
||||
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(self.fmt, rules)
|
||||
self.deserialize(self.fmt, res)
|
||||
self.assertEqual(res.status_int, 201)
|
||||
address_pairs = [{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '10.0.0.0/24'},
|
||||
{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '11.0.0.1'}]
|
||||
res1 = self._create_port(
|
||||
self.fmt, n['network']['id'],
|
||||
security_groups=[sg1_id],
|
||||
arg_list=(addr_pair.ADDRESS_PAIRS,),
|
||||
allowed_address_pairs=address_pairs)
|
||||
ports_rest1 = self.deserialize(self.fmt, 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', 'ethertype': 'IPv4',
|
||||
'security_group_id': sg1_id},
|
||||
{'direction': 'egress', 'ethertype': 'IPv6',
|
||||
'security_group_id': sg1_id},
|
||||
{'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},
|
||||
]
|
||||
self.assertEqual(port_rpc['security_group_rules'],
|
||||
expected)
|
||||
self.assertEqual(port_rpc['allowed_address_pairs'],
|
||||
address_pairs)
|
||||
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:
|
||||
@ -616,8 +681,8 @@ COMMIT
|
||||
""" % 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'
|
||||
CHAINS_1 = CHAINS_EMPTY + '|i_port1|o_port1|s_port1'
|
||||
CHAINS_2 = CHAINS_1 + '|i_port2|o_port2|s_port2'
|
||||
|
||||
IPTABLES_ARG['chains'] = CHAINS_1
|
||||
|
||||
@ -632,6 +697,7 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
[0:0] -A FORWARD -j neutron-filter-top
|
||||
[0:0] -A OUTPUT -j neutron-filter-top
|
||||
[0:0] -A neutron-filter-top -j %(bn)s-local
|
||||
@ -645,8 +711,8 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port1
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback
|
||||
[0:0] -A %(bn)s-FORWARD %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
@ -655,9 +721,11 @@ IPTABLES_FILTER_1 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port1 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -668,6 +736,7 @@ COMMIT
|
||||
# Completed by iptables_manager
|
||||
""" % IPTABLES_ARG
|
||||
|
||||
|
||||
IPTABLES_FILTER_1_2 = """# Generated by iptables_manager
|
||||
*filter
|
||||
:neutron-filter-top - [0:0]
|
||||
@ -679,6 +748,7 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
:%(bn)s-(%(chains)s) - [0:0]
|
||||
[0:0] -A FORWARD -j neutron-filter-top
|
||||
[0:0] -A OUTPUT -j neutron-filter-top
|
||||
[0:0] -A neutron-filter-top -j %(bn)s-local
|
||||
@ -692,8 +762,8 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port1
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback
|
||||
@ -703,9 +773,11 @@ IPTABLES_FILTER_1_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port1 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -731,6 +803,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager
|
||||
:%(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]
|
||||
[0:0] -A FORWARD -j neutron-filter-top
|
||||
[0:0] -A OUTPUT -j neutron-filter-top
|
||||
[0:0] -A neutron-filter-top -j %(bn)s-local
|
||||
@ -744,8 +818,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port1
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback
|
||||
@ -755,9 +829,11 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-s_port1 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -769,8 +845,8 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port2
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -j %(bn)s-sg-fallback
|
||||
@ -780,9 +856,11 @@ IPTABLES_FILTER_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
|
||||
[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-s_port2 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -806,6 +884,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager
|
||||
:%(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]
|
||||
[0:0] -A FORWARD -j neutron-filter-top
|
||||
[0:0] -A OUTPUT -j neutron-filter-top
|
||||
[0:0] -A neutron-filter-top -j %(bn)s-local
|
||||
@ -819,8 +899,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port1
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -j %(bn)s-sg-fallback
|
||||
[0:0] -A %(bn)s-FORWARD %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
@ -829,9 +909,11 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port1 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -843,8 +925,8 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port2
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -j %(bn)s-sg-fallback
|
||||
@ -854,9 +936,11 @@ IPTABLES_FILTER_2_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
|
||||
[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port2 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -880,6 +964,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager
|
||||
:%(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]
|
||||
[0:0] -A FORWARD -j neutron-filter-top
|
||||
[0:0] -A OUTPUT -j neutron-filter-top
|
||||
[0:0] -A neutron-filter-top -j %(bn)s-local
|
||||
@ -893,8 +979,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port1
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -s 10.0.0.4 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port1 -p icmp -j RETURN
|
||||
@ -905,9 +991,11 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-s_port1 -m mac --mac-source 12:34:56:78:9a:bc -s 10.0.0.3 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port1 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 ! -s 10.0.0.3 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -j %(bn)s-s_port1
|
||||
[0:0] -A %(bn)s-o_port1 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -919,8 +1007,8 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-i_port2
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-i_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 \
|
||||
-j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.2 -p udp -m udp --sport 67 --dport 68 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -p tcp -m tcp --dport 22 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -s 10.0.0.3 -j RETURN
|
||||
[0:0] -A %(bn)s-i_port2 -p icmp -j RETURN
|
||||
@ -931,9 +1019,11 @@ IPTABLES_FILTER_2_3 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
|
||||
[0:0] -A %(bn)s-s_port2 -m mac --mac-source 12:34:56:78:9a:bd -s 10.0.0.4 -j \
|
||||
RETURN
|
||||
[0:0] -A %(bn)s-s_port2 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 68 --dport 67 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port2 ! -s 10.0.0.4 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -j %(bn)s-s_port2
|
||||
[0:0] -A %(bn)s-o_port2 -p udp -m udp --sport 67 --dport 68 -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -944,6 +1034,7 @@ COMMIT
|
||||
# Completed by iptables_manager
|
||||
""" % IPTABLES_ARG
|
||||
|
||||
|
||||
IPTABLES_ARG['chains'] = CHAINS_EMPTY
|
||||
IPTABLES_FILTER_EMPTY = """# Generated by iptables_manager
|
||||
*filter
|
||||
@ -997,7 +1088,6 @@ IPTABLES_FILTER_V6_1 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p icmpv6 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -1007,7 +1097,9 @@ COMMIT
|
||||
# Completed by iptables_manager
|
||||
""" % IPTABLES_ARG
|
||||
|
||||
|
||||
IPTABLES_ARG['chains'] = CHAINS_2
|
||||
|
||||
IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager
|
||||
*filter
|
||||
:neutron-filter-top - [0:0]
|
||||
@ -1041,7 +1133,6 @@ IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port1 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port1
|
||||
[0:0] -A %(bn)s-o_port1 -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -p icmpv6 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port1 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -1059,7 +1150,6 @@ IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-INPUT %(physdev_mod)s --physdev-EGRESS tap_port2 \
|
||||
%(physdev_is_bridged)s -j %(bn)s-o_port2
|
||||
[0:0] -A %(bn)s-o_port2 -m mac ! --mac-source 12:34:56:78:9a:bd -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -p icmpv6 -j RETURN
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state INVALID -j DROP
|
||||
[0:0] -A %(bn)s-o_port2 -m state --state RELATED,ESTABLISHED -j RETURN
|
||||
@ -1246,7 +1336,6 @@ class TestSecurityGroupAgentWithIptables(base.BaseTestCase):
|
||||
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_updated(self):
|
||||
@ -1325,6 +1414,7 @@ class TestSecurityGroupAgentWithOVSIptables(
|
||||
value = value.replace('tap_port', 'taptap_port')
|
||||
value = value.replace('o_port', 'otap_port')
|
||||
value = value.replace('i_port', 'itap_port')
|
||||
value = value.replace('s_port', 'stap_port')
|
||||
return super(
|
||||
TestSecurityGroupAgentWithOVSIptables,
|
||||
self)._regex(value)
|
||||
|
Loading…
Reference in New Issue
Block a user