OVS based l2 Firewall driver for FWaaS v2
This driver is based on neutron ovsfw driver. Current implementation forks neutron code from commit 917063a0ce3638dafb80f29da2b0c1f0c4165306 Co-Authored-By: Yushiro FURUKAWA <y.furukawa_2@jp.fujitsu.com> Co-Authored-By: Inessa Vasilevskaya <ivasilevskaya@mirantis.com> Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com> Partial-Implements: blueprint fwaas-api-2.0 Change-Id: If89e29bac3bc4167c7caf602fb5e3133cc93255f
This commit is contained in:
parent
dbac4b8922
commit
3b7093bfe8
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2015
|
||||
# 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 neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import firewall
|
||||
|
||||
OVSFirewallDriver = firewall.OVSFirewallDriver
|
|
@ -0,0 +1,64 @@
|
|||
# Copyright 2015
|
||||
# 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 neutron_lib import constants
|
||||
|
||||
from neutron.common import constants as n_const
|
||||
|
||||
OF_STATE_NOT_TRACKED = "-trk"
|
||||
OF_STATE_TRACKED = "+trk"
|
||||
OF_STATE_NEW_NOT_ESTABLISHED = "+new-est"
|
||||
OF_STATE_NOT_ESTABLISHED = "-est"
|
||||
OF_STATE_ESTABLISHED = "+est"
|
||||
OF_STATE_ESTABLISHED_NOT_REPLY = "+est-rel-rpl"
|
||||
OF_STATE_ESTABLISHED_REPLY = "+est-rel+rpl"
|
||||
OF_STATE_RELATED = "-new-est+rel-inv"
|
||||
OF_STATE_INVALID = "+trk+inv"
|
||||
OF_STATE_NEW = "+new"
|
||||
OF_STATE_NOT_REPLY_NOT_NEW = "-new-rpl"
|
||||
|
||||
CT_MARK_NORMAL = '0x0'
|
||||
CT_MARK_INVALID = '0x1'
|
||||
|
||||
REG_PORT = 5
|
||||
REG_NET = 6
|
||||
|
||||
FW_BASE_EGRESS_TABLE = 64
|
||||
FW_RULES_EGRESS_TABLE = 65
|
||||
FW_ACCEPT_OR_INGRESS_TABLE = 66
|
||||
FW_BASE_INGRESS_TABLE = 68
|
||||
FW_RULES_INGRESS_TABLE = 69
|
||||
|
||||
OVS_FIREWALL_TABLES = (
|
||||
FW_BASE_EGRESS_TABLE,
|
||||
FW_RULES_EGRESS_TABLE,
|
||||
FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
FW_BASE_INGRESS_TABLE,
|
||||
FW_RULES_INGRESS_TABLE,
|
||||
)
|
||||
|
||||
PROTOCOLS_WITH_PORTS = (constants.PROTO_NAME_SCTP,
|
||||
constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP)
|
||||
|
||||
# Only map protocols that need special handling
|
||||
REVERSE_IP_PROTOCOL_MAP_WITH_PORTS = {
|
||||
constants.IP_PROTOCOL_MAP[proto]: proto for proto in
|
||||
PROTOCOLS_WITH_PORTS}
|
||||
|
||||
ethertype_to_dl_type_map = {
|
||||
constants.IPv4: n_const.ETHERTYPE_IP,
|
||||
constants.IPv6: n_const.ETHERTYPE_IPV6,
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2016, Red Hat, 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 neutron_lib import exceptions
|
||||
|
||||
from neutron_fwaas._i18n import _
|
||||
|
||||
|
||||
class OVSFWaaSPortNotFound(exceptions.NeutronException):
|
||||
message = _("Port %(port_id)s is not managed by this agent.")
|
||||
|
||||
|
||||
class OVSFWaaSTagNotFound(exceptions.NeutronException):
|
||||
message = _("Cannot get vlan tag for port %(port_id)s.")
|
|
@ -0,0 +1,967 @@
|
|||
# Copyright 2015
|
||||
# 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 neutron_lib import constants as lib_const
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
|
||||
from neutron.agent import firewall
|
||||
from neutron.common import constants
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
|
||||
as ovs_consts
|
||||
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2 import driver_base
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import constants as fwaas_ovs_consts
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import exceptions
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import rules
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
ACTION_ALLOW = 'allow'
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver.
|
||||
def _replace_register(flow_params, register_number, register_value):
|
||||
"""Replace value from flows to given register number
|
||||
|
||||
'register_value' key in dictionary will be replaced by register number
|
||||
given by 'register_number'
|
||||
|
||||
:param flow_params: Dictionary containing defined flows
|
||||
:param register_number: The number of register where value will be stored
|
||||
:param register_value: Key to be replaced by register number
|
||||
|
||||
"""
|
||||
try:
|
||||
reg_port = flow_params[register_value]
|
||||
del flow_params[register_value]
|
||||
flow_params['reg{:d}'.format(register_number)] = reg_port
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver that
|
||||
# differs only in constants REG_PORT/REG_NET.
|
||||
def create_reg_numbers(flow_params):
|
||||
"""Replace reg_(port|net) values with defined register numbers"""
|
||||
_replace_register(flow_params, fwaas_ovs_consts.REG_PORT, 'reg_port')
|
||||
_replace_register(flow_params, fwaas_ovs_consts.REG_NET, 'reg_net')
|
||||
|
||||
|
||||
class FirewallGroup(object):
|
||||
def __init__(self, id_):
|
||||
self.id = id_
|
||||
self.ingress_rules = []
|
||||
self.egress_rules = []
|
||||
self.members = {}
|
||||
self.ports = set()
|
||||
|
||||
def update_rules(self, ingress_rules, egress_rules):
|
||||
"""Update firewall group with ingress/egress rules.
|
||||
|
||||
If a rule has a protocol field, it is normalized to a number
|
||||
here in order to ease later processing.
|
||||
"""
|
||||
def _translate_protocol_to_number(rule):
|
||||
protocol = rule.get('protocol')
|
||||
if protocol is not None:
|
||||
if protocol.isdigit():
|
||||
rule['protocol'] = int(protocol)
|
||||
elif (rule.get('ethertype') == lib_const.IPv6 and
|
||||
protocol == lib_const.PROTO_NAME_ICMP):
|
||||
rule['protocol'] = lib_const.PROTO_NUM_IPV6_ICMP
|
||||
else:
|
||||
rule['protocol'] = lib_const.IP_PROTOCOL_MAP.get(
|
||||
protocol, protocol)
|
||||
return rule
|
||||
|
||||
self.ingress_rules = [_translate_protocol_to_number(ir)
|
||||
for ir in ingress_rules]
|
||||
self.egress_rules = [_translate_protocol_to_number(er)
|
||||
for er in egress_rules]
|
||||
|
||||
def get_ethertype_filtered_addresses(self, ethertype,
|
||||
exclude_addresses=None):
|
||||
exclude_addresses = set(exclude_addresses if exclude_addresses else [])
|
||||
group_addresses = set(self.members.get(ethertype, []))
|
||||
return list(group_addresses - exclude_addresses)
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver that
|
||||
# differs only in firewall groups list field name
|
||||
class OFPort(object):
|
||||
def __init__(self, port_dict, ovs_port, vlan_tag):
|
||||
self.id = port_dict['device']
|
||||
self.vlan_tag = vlan_tag
|
||||
self.mac = ovs_port.vif_mac
|
||||
self.lla_address = str(netutils.get_ipv6_addr_by_EUI64(
|
||||
lib_const.IPv6_LLA_PREFIX, self.mac))
|
||||
self.ofport = ovs_port.ofport
|
||||
self.fw_group = None
|
||||
self.fixed_ips = port_dict.get('fixed_ips', [])
|
||||
self.neutron_port_dict = port_dict.copy()
|
||||
self.allowed_pairs_v4 = self._get_allowed_pairs(port_dict, version=4)
|
||||
self.allowed_pairs_v6 = self._get_allowed_pairs(port_dict, version=6)
|
||||
|
||||
@staticmethod
|
||||
def _get_allowed_pairs(port_dict, version):
|
||||
aap_dict = port_dict.get('allowed_address_pairs', set())
|
||||
return {(aap['mac_address'], aap['ip_address']) for aap in aap_dict
|
||||
if netaddr.IPNetwork(aap['ip_address']).version == version}
|
||||
|
||||
@property
|
||||
def all_allowed_macs(self):
|
||||
macs = {item[0] for item in self.allowed_pairs_v4.union(
|
||||
self.allowed_pairs_v6)}
|
||||
macs.add(self.mac)
|
||||
return macs
|
||||
|
||||
@property
|
||||
def ipv4_addresses(self):
|
||||
return [ip_addr for ip_addr in
|
||||
[fixed_ip['ip_address'] for fixed_ip in self.fixed_ips]
|
||||
if netaddr.IPAddress(ip_addr).version == 4]
|
||||
|
||||
@property
|
||||
def ipv6_addresses(self):
|
||||
return [ip_addr for ip_addr in
|
||||
[fixed_ip['ip_address'] for fixed_ip in self.fixed_ips]
|
||||
if netaddr.IPAddress(ip_addr).version == 6]
|
||||
|
||||
def update(self, port_dict):
|
||||
self.allowed_pairs_v4 = self._get_allowed_pairs(port_dict,
|
||||
version=4)
|
||||
self.allowed_pairs_v6 = self._get_allowed_pairs(port_dict,
|
||||
version=6)
|
||||
# Neighbour discovery uses LLA
|
||||
self.allowed_pairs_v6.add((self.mac, self.lla_address))
|
||||
self.fixed_ips = port_dict.get('fixed_ips', [])
|
||||
self.neutron_port_dict = port_dict.copy()
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver that
|
||||
# differs in methods name [s/sg/fwg] and update_rules method.
|
||||
class FWGPortMap(object):
|
||||
def __init__(self):
|
||||
self.ports = {}
|
||||
self.fw_groups = {}
|
||||
# Maps port_id to ofport number
|
||||
self.unfiltered = {}
|
||||
|
||||
def get_fwg(self, fwg_id):
|
||||
return self.fw_groups.get(fwg_id, None)
|
||||
|
||||
def get_or_create_fwg(self, fwg_id):
|
||||
fw_group = self.get_fwg(fwg_id)
|
||||
if not fw_group:
|
||||
fw_group = FirewallGroup(fwg_id)
|
||||
self.fw_groups[fwg_id] = fw_group
|
||||
return fw_group
|
||||
|
||||
def delete_fwg(self, fwg_id):
|
||||
del self.fw_groups[fwg_id]
|
||||
|
||||
# XXX NOTE(ivasilevskaya) couldn't find any logical definition why
|
||||
# firewall_group should come as 3rd argument instead of adding fwg_id
|
||||
# to port_dict. Removed in favor of SG api
|
||||
def create_port(self, port, port_dict):
|
||||
self.ports[port.id] = port
|
||||
self.update_port(port, port_dict)
|
||||
|
||||
# XXX NOTE(ivasilevskaya) couldn't find any logical definition why
|
||||
# firewall_group should come as 3rd argument instead of adding fwg_id
|
||||
# to port_dict. Removed in favor of SG api
|
||||
def update_port(self, port, port_dict):
|
||||
for fw_group in self.fw_groups.values():
|
||||
fw_group.ports.discard(port)
|
||||
|
||||
fw_group = self.get_or_create_fwg(port_dict['firewall_group'])
|
||||
port.fw_group = fw_group
|
||||
fw_group.ports.add(port)
|
||||
port.update(port_dict)
|
||||
|
||||
def remove_port(self, port):
|
||||
if port.fw_group:
|
||||
port.fw_group.ports.discard(port)
|
||||
del self.ports[port.id]
|
||||
|
||||
def update_rules(self, fwg_id, ingress_rules, egress_rules):
|
||||
fw_group = self.get_or_create_fwg(fwg_id)
|
||||
fw_group.update_rules(ingress_rules, egress_rules)
|
||||
|
||||
def update_members(self, fwg_id, members):
|
||||
fw_group = self.get_or_create_fwg(fwg_id)
|
||||
fw_group.members = members
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver that
|
||||
# doesn't have a conjunction manager because no remote_group_id concept is
|
||||
# applicable to firewall groups
|
||||
class OVSFirewallDriver(driver_base.FirewallL2DriverBase):
|
||||
REQUIRED_PROTOCOLS = [
|
||||
ovs_consts.OPENFLOW10,
|
||||
ovs_consts.OPENFLOW11,
|
||||
ovs_consts.OPENFLOW12,
|
||||
ovs_consts.OPENFLOW13,
|
||||
ovs_consts.OPENFLOW14,
|
||||
]
|
||||
|
||||
provides_arp_spoofing_protection = True
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver.
|
||||
# This driver won't have any conj_manager logic because there is no concept
|
||||
# of remote_group_id for firewall groups (that I know of at least)
|
||||
def __init__(self, integration_bridge, sg_enabled=False):
|
||||
"""Initialize object
|
||||
|
||||
:param integration_bridge: Bridge on which openflow rules will be
|
||||
applied
|
||||
|
||||
"""
|
||||
self.int_br = self.initialize_bridge(integration_bridge)
|
||||
self.fwg_port_map = FWGPortMap()
|
||||
self.fwg_to_delete = set()
|
||||
self._deferred = False
|
||||
self.sg_enabled = sg_enabled
|
||||
self._drop_all_unmatched_flows()
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def _accept_flow(self, **flow):
|
||||
for f in rules.create_accept_flows(flow):
|
||||
self._add_flow(**f)
|
||||
|
||||
def _drop_flow(self, **flow):
|
||||
for f in rules.create_drop_flows(flow):
|
||||
self._add_flow(**f)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def _add_flow(self, **kwargs):
|
||||
dl_type = kwargs.get('dl_type')
|
||||
create_reg_numbers(kwargs)
|
||||
if isinstance(dl_type, int):
|
||||
kwargs['dl_type'] = "0x{:04x}".format(dl_type)
|
||||
if self._deferred:
|
||||
self.int_br.add_flow(**kwargs)
|
||||
else:
|
||||
self.int_br.br.add_flow(**kwargs)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def _delete_flows(self, **kwargs):
|
||||
create_reg_numbers(kwargs)
|
||||
if self._deferred:
|
||||
self.int_br.delete_flows(**kwargs)
|
||||
else:
|
||||
self.int_br.br.delete_flows(**kwargs)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def _strict_delete_flow(self, **kwargs):
|
||||
"""Delete given flow right away even if bridge is deferred.
|
||||
|
||||
Delete command will use strict delete.
|
||||
"""
|
||||
create_reg_numbers(kwargs)
|
||||
self.int_br.br.delete_flows(strict=True, **kwargs)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
@staticmethod
|
||||
def initialize_bridge(int_br):
|
||||
int_br.add_protocols(*OVSFirewallDriver.REQUIRED_PROTOCOLS)
|
||||
return int_br.deferred(full_ordered=True)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver,
|
||||
# differs in constants
|
||||
def _drop_all_unmatched_flows(self):
|
||||
for table in fwaas_ovs_consts.OVS_FIREWALL_TABLES:
|
||||
self.int_br.br.add_flow(table=table, priority=0, actions='drop')
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def get_ovs_port(self, port_id):
|
||||
ovs_port = self.int_br.br.get_vif_port_by_id(port_id)
|
||||
if not ovs_port:
|
||||
raise exceptions.OVSFWaaSPortNotFound(port_id=port_id)
|
||||
return ovs_port
|
||||
|
||||
def _get_port_vlan_tag(self, port):
|
||||
vlan_tag = port.get('lvlan', None)
|
||||
if not vlan_tag:
|
||||
raise exceptions.OVSFWaaSTagNotFound(port_id=port['device'])
|
||||
return vlan_tag
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def get_ofport(self, port):
|
||||
port_id = port['device']
|
||||
return self.fwg_port_map.ports.get(port_id)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver,
|
||||
# self.sg_port_map -> self.fwg_port_map
|
||||
def get_or_create_ofport(self, port):
|
||||
"""Get ofport specified by port['device'], checking and reflecting
|
||||
ofport changes.
|
||||
If ofport is nonexistent, create and return one.
|
||||
"""
|
||||
port_id = port['device']
|
||||
ovs_port = self.get_ovs_port(port_id)
|
||||
try:
|
||||
of_port = self.fwg_port_map.ports[port_id]
|
||||
except KeyError:
|
||||
port_vlan_id = self._get_port_vlan_tag(port)
|
||||
of_port = OFPort(port, ovs_port, port_vlan_id)
|
||||
self.fwg_port_map.create_port(of_port, port)
|
||||
else:
|
||||
if of_port.ofport != ovs_port.ofport:
|
||||
self.fwg_port_map.remove_port(of_port)
|
||||
of_port = OFPort(port, ovs_port, of_port.vlan_tag)
|
||||
self.fwg_port_map.update_port(of_port, port)
|
||||
|
||||
return of_port
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def is_port_managed(self, port):
|
||||
return port['device'] in self.fwg_port_map.ports
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def prepare_port_filter(self, port):
|
||||
# NOTE(annp): port no security should be handled by security group in
|
||||
# co-existence mode, otherwise(standalone mode) fwg will handle it.
|
||||
if not firewall.port_sec_enabled(port) and not self.sg_enabled:
|
||||
self._initialize_egress_no_port_security(port)
|
||||
return
|
||||
old_of_port = self.get_ofport(port)
|
||||
# Make sure delete old allow_address_pair MACs because
|
||||
# allow_address_pair MACs will be updated in
|
||||
# self.get_or_create_ofport(port)
|
||||
if old_of_port:
|
||||
LOG.error("Initializing port %s that was already "
|
||||
"initialized.",
|
||||
port['device'])
|
||||
self.delete_all_port_flows(old_of_port)
|
||||
of_port = self.get_or_create_ofport(port)
|
||||
self.initialize_port_flows(of_port)
|
||||
self.add_flows_from_rules(of_port)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def update_port_filter(self, port):
|
||||
"""Update rules for given port
|
||||
|
||||
Current existing filtering rules are removed and new ones are generated
|
||||
based on current loaded firewall group rules and members.
|
||||
|
||||
Note: port no security should be handled by security group in
|
||||
co-existence mode, otherwise fwg will handle it.
|
||||
|
||||
"""
|
||||
if not firewall.port_sec_enabled(port) and not self.sg_enabled:
|
||||
self.remove_port_filter(port)
|
||||
self._initialize_egress_no_port_security(port)
|
||||
return
|
||||
elif not self.is_port_managed(port):
|
||||
if not self.sg_enabled:
|
||||
self._remove_egress_no_port_security(port['device'])
|
||||
self.prepare_port_filter(port)
|
||||
return
|
||||
|
||||
old_of_port = self.get_ofport(port)
|
||||
of_port = self.get_or_create_ofport(port)
|
||||
# TODO(jlibosva): Handle firewall blink
|
||||
self.delete_all_port_flows(old_of_port)
|
||||
self.initialize_port_flows(of_port)
|
||||
self.add_flows_from_rules(of_port)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver,
|
||||
# sg_port_map -> fwg_port_map
|
||||
def remove_port_filter(self, port):
|
||||
"""Remove port from firewall
|
||||
|
||||
All flows related to this port are removed from ovs. Port is also
|
||||
removed from ports managed by this firewall.
|
||||
|
||||
"""
|
||||
if self.is_port_managed(port):
|
||||
of_port = self.get_ofport(port)
|
||||
self.delete_all_port_flows(of_port)
|
||||
self.fwg_port_map.remove_port(of_port)
|
||||
self._schedule_fwg_deletion_maybe(of_port.fw_group.id)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# with ingress\egress rules arguments instead of single rules
|
||||
def update_firewall_group_rules(self, fwg_id, ingress_rules, egress_rules):
|
||||
self.fwg_port_map.update_rules(fwg_id, ingress_rules, egress_rules)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# with sg_port_map -> fwg_port_map
|
||||
def _schedule_fwg_deletion_maybe(self, fwg_id):
|
||||
"""Schedule possible deletion of the given firewall group.
|
||||
|
||||
This function must be called when the number of ports
|
||||
associated to fwg_id drops to zero, as it isn't possible
|
||||
to know FWG deletions from agents due to RPC API design.
|
||||
"""
|
||||
fwg_group = self.fwg_port_map.get_or_create_fwg(fwg_id)
|
||||
if not fwg_group.members or not fwg_group.ports:
|
||||
self.fwg_to_delete.add(fwg_id)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# with sg_port_map -> fwg_port_map
|
||||
def _cleanup_stale_fwg(self):
|
||||
fwg_to_delete = self.fwg_to_delete
|
||||
self.fwg_to_delete = set()
|
||||
|
||||
for fwg_id in fwg_to_delete:
|
||||
fw_group = self.fwg_port_map.get_fwg(fwg_id)
|
||||
if fw_group.members and fw_group.ports:
|
||||
# firewall group is still in use
|
||||
continue
|
||||
|
||||
self.fwg_port_map.delete_fwg(fwg_id)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def filter_defer_apply_on(self):
|
||||
self._deferred = True
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
def filter_defer_apply_off(self):
|
||||
if self._deferred:
|
||||
self._cleanup_stale_fwg()
|
||||
self.int_br.apply_flows()
|
||||
self._deferred = False
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# with sg_port_map -> fwg_port_map
|
||||
@property
|
||||
def ports(self):
|
||||
return {id_: port.neutron_port_dict
|
||||
for id_, port in self.fwg_port_map.ports.items()}
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def initialize_port_flows(self, port):
|
||||
"""Set base flows for port
|
||||
|
||||
:param port: OFPort instance
|
||||
|
||||
"""
|
||||
# Identify egress flow
|
||||
self._add_flow(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
priority=105,
|
||||
in_port=port.ofport,
|
||||
actions='set_field:{:d}->reg{:d},'
|
||||
'set_field:{:d}->reg{:d},'
|
||||
'resubmit(,{:d})'.format(
|
||||
port.ofport,
|
||||
fwaas_ovs_consts.REG_PORT,
|
||||
port.vlan_tag,
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.FW_BASE_EGRESS_TABLE)
|
||||
)
|
||||
|
||||
# Identify ingress flows after egress filtering
|
||||
for mac_addr in port.all_allowed_macs:
|
||||
self._add_flow(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
priority=95,
|
||||
dl_dst=mac_addr,
|
||||
dl_vlan='0x%x' % port.vlan_tag,
|
||||
actions='set_field:{:d}->reg{:d},'
|
||||
'set_field:{:d}->reg{:d},'
|
||||
'strip_vlan,resubmit(,{:d})'.format(
|
||||
port.ofport,
|
||||
fwaas_ovs_consts.REG_PORT,
|
||||
port.vlan_tag,
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
|
||||
)
|
||||
|
||||
self._initialize_egress(port)
|
||||
self._initialize_ingress(port)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_egress_ipv6_icmp(self, port):
|
||||
for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=95,
|
||||
in_port=port.ofport,
|
||||
reg_port=port.ofport,
|
||||
dl_type=constants.ETHERTYPE_IPV6,
|
||||
nw_proto=lib_const.PROTO_NUM_IPV6_ICMP,
|
||||
icmp_type=icmp_type,
|
||||
actions='normal'
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers) and exception classes
|
||||
def _initialize_egress_no_port_security(self, port):
|
||||
port_id = port['device']
|
||||
try:
|
||||
ovs_port = self.get_ovs_port(port_id)
|
||||
vlan_tag = self._get_port_vlan_tag(port)
|
||||
except exceptions.OVSFWaaSTagNotFound:
|
||||
# It's a patch port, don't set anything
|
||||
return
|
||||
except exceptions.OVSFWaaSPortNotFound as not_found_e:
|
||||
LOG.error("Initializing unfiltered port %(port_id)s that does not "
|
||||
"exist in ovsdb: %(err)s.",
|
||||
{'port_id': port_id,
|
||||
'err': not_found_e})
|
||||
return
|
||||
self.fwg_port_map.unfiltered[port_id] = ovs_port.ofport
|
||||
self._add_flow(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
priority=100,
|
||||
in_port=ovs_port.ofport,
|
||||
actions='set_field:%d->reg%d,'
|
||||
'set_field:%d->reg%d,'
|
||||
'resubmit(,%d)' % (
|
||||
ovs_port.ofport,
|
||||
fwaas_ovs_consts.REG_PORT,
|
||||
vlan_tag,
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE)
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
priority=80,
|
||||
reg_port=ovs_port.ofport,
|
||||
actions='normal'
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _remove_egress_no_port_security(self, port_id):
|
||||
try:
|
||||
ofport = self.fwg_port_map.unfiltered[port_id]
|
||||
except KeyError:
|
||||
LOG.debug("Port %s is not handled by the firewall.", port_id)
|
||||
return
|
||||
self._delete_flows(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
in_port=ofport
|
||||
)
|
||||
self._delete_flows(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
reg_port=ofport
|
||||
)
|
||||
del self.fwg_port_map.unfiltered[port_id]
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_egress(self, port):
|
||||
"""Identify egress traffic and send it to egress base"""
|
||||
self._initialize_egress_ipv6_icmp(port)
|
||||
|
||||
# Apply mac/ip pairs for IPv4
|
||||
allowed_pairs = port.allowed_pairs_v4.union(
|
||||
{(port.mac, ip_addr) for ip_addr in port.ipv4_addresses})
|
||||
for mac_addr, ip_addr in allowed_pairs:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=95,
|
||||
in_port=port.ofport,
|
||||
reg_port=port.ofport,
|
||||
dl_src=mac_addr,
|
||||
dl_type=constants.ETHERTYPE_ARP,
|
||||
arp_spa=ip_addr,
|
||||
actions='normal'
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=65,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_TRACKED,
|
||||
dl_type=constants.ETHERTYPE_IP,
|
||||
in_port=port.ofport,
|
||||
dl_src=mac_addr,
|
||||
nw_src=ip_addr,
|
||||
actions='ct(table={:d},zone=NXM_NX_REG{:d}[0..15])'.format(
|
||||
fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
fwaas_ovs_consts.REG_NET)
|
||||
)
|
||||
|
||||
# Apply mac/ip pairs for IPv6
|
||||
allowed_pairs = port.allowed_pairs_v6.union(
|
||||
{(port.mac, ip_addr) for ip_addr in port.ipv6_addresses})
|
||||
for mac_addr, ip_addr in allowed_pairs:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=65,
|
||||
reg_port=port.ofport,
|
||||
in_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_TRACKED,
|
||||
dl_type=constants.ETHERTYPE_IPV6,
|
||||
dl_src=mac_addr,
|
||||
ipv6_src=ip_addr,
|
||||
actions='ct(table={:d},zone=NXM_NX_REG{:d}[0..15])'.format(
|
||||
fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
fwaas_ovs_consts.REG_NET)
|
||||
)
|
||||
|
||||
# DHCP discovery
|
||||
for dl_type, src_port, dst_port in (
|
||||
(constants.ETHERTYPE_IP, 68, 67),
|
||||
(constants.ETHERTYPE_IPV6, 546, 547)):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=80,
|
||||
reg_port=port.ofport,
|
||||
in_port=port.ofport,
|
||||
dl_type=dl_type,
|
||||
nw_proto=lib_const.PROTO_NUM_UDP,
|
||||
tp_src=src_port,
|
||||
tp_dst=dst_port,
|
||||
actions='resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE)
|
||||
)
|
||||
# Ban dhcp service running on an instance
|
||||
for dl_type, src_port, dst_port in (
|
||||
(constants.ETHERTYPE_IP, 67, 68),
|
||||
(constants.ETHERTYPE_IPV6, 547, 546)):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=70,
|
||||
in_port=port.ofport,
|
||||
reg_port=port.ofport,
|
||||
dl_type=dl_type,
|
||||
nw_proto=lib_const.PROTO_NUM_UDP,
|
||||
tp_src=src_port,
|
||||
tp_dst=dst_port,
|
||||
actions='drop'
|
||||
)
|
||||
|
||||
# Drop Router Advertisements from instances
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=70,
|
||||
in_port=port.ofport,
|
||||
reg_port=port.ofport,
|
||||
dl_type=constants.ETHERTYPE_IPV6,
|
||||
nw_proto=lib_const.PROTO_NUM_IPV6_ICMP,
|
||||
icmp_type=lib_const.ICMPV6_TYPE_RA,
|
||||
actions='drop'
|
||||
)
|
||||
|
||||
# Drop all remaining not tracked egress connections
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE,
|
||||
priority=10,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_TRACKED,
|
||||
in_port=port.ofport,
|
||||
reg_port=port.ofport,
|
||||
actions='drop'
|
||||
)
|
||||
|
||||
# Fill in accept_or_ingress table by checking that traffic is ingress
|
||||
# and if not, accept it
|
||||
for mac_addr in port.all_allowed_macs:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
priority=100,
|
||||
dl_dst=mac_addr,
|
||||
reg_net=port.vlan_tag,
|
||||
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
|
||||
port.ofport,
|
||||
fwaas_ovs_consts.REG_PORT,
|
||||
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
|
||||
)
|
||||
for ethertype in [constants.ETHERTYPE_IP, constants.ETHERTYPE_IPV6]:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
priority=90,
|
||||
dl_type=ethertype,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
|
||||
actions='ct(commit,zone=NXM_NX_REG{:d}[0..15]),normal'.format(
|
||||
fwaas_ovs_consts.REG_NET)
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
priority=80,
|
||||
reg_port=port.ofport,
|
||||
actions='normal'
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_tracked_egress(self, port):
|
||||
# Drop invalid packets
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
priority=50,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_INVALID,
|
||||
actions='drop'
|
||||
)
|
||||
# Drop traffic for removed fwg rules
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
priority=50,
|
||||
reg_port=port.ofport,
|
||||
ct_mark=fwaas_ovs_consts.CT_MARK_INVALID,
|
||||
actions='drop'
|
||||
)
|
||||
|
||||
for state in (
|
||||
fwaas_ovs_consts.OF_STATE_ESTABLISHED_REPLY,
|
||||
fwaas_ovs_consts.OF_STATE_RELATED,
|
||||
):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
priority=50,
|
||||
ct_state=state,
|
||||
ct_mark=fwaas_ovs_consts.CT_MARK_NORMAL,
|
||||
reg_port=port.ofport,
|
||||
ct_zone=port.vlan_tag,
|
||||
actions='normal'
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
priority=40,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_ESTABLISHED,
|
||||
actions='drop'
|
||||
)
|
||||
for ethertype in [constants.ETHERTYPE_IP, constants.ETHERTYPE_IPV6]:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
priority=40,
|
||||
dl_type=ethertype,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_ESTABLISHED,
|
||||
actions="ct(commit,zone=NXM_NX_REG{:d}[0..15],"
|
||||
"exec(set_field:{:s}->ct_mark))".format(
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.CT_MARK_INVALID)
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_ingress_ipv6_icmp(self, port):
|
||||
for icmp_type in firewall.ICMPV6_ALLOWED_INGRESS_TYPES:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE,
|
||||
priority=100,
|
||||
reg_port=port.ofport,
|
||||
dl_dst=port.mac,
|
||||
dl_type=constants.ETHERTYPE_IPV6,
|
||||
nw_proto=lib_const.PROTO_NUM_IPV6_ICMP,
|
||||
icmp_type=icmp_type,
|
||||
actions='output:{:d}'.format(port.ofport),
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_ingress(self, port):
|
||||
# Allow incoming ARPs
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE,
|
||||
priority=100,
|
||||
dl_type=constants.ETHERTYPE_ARP,
|
||||
reg_port=port.ofport,
|
||||
actions='output:{:d}'.format(port.ofport),
|
||||
)
|
||||
self._initialize_ingress_ipv6_icmp(port)
|
||||
|
||||
# DHCP offers
|
||||
for dl_type, src_port, dst_port in (
|
||||
(constants.ETHERTYPE_IP, 67, 68),
|
||||
(constants.ETHERTYPE_IPV6, 547, 546)):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE,
|
||||
priority=95,
|
||||
reg_port=port.ofport,
|
||||
dl_type=dl_type,
|
||||
nw_proto=lib_const.PROTO_NUM_UDP,
|
||||
tp_src=src_port,
|
||||
tp_dst=dst_port,
|
||||
actions='output:{:d}'.format(port.ofport),
|
||||
)
|
||||
|
||||
# Track untracked
|
||||
for dl_type in (constants.ETHERTYPE_IP, constants.ETHERTYPE_IPV6):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE,
|
||||
priority=90,
|
||||
reg_port=port.ofport,
|
||||
dl_type=dl_type,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_TRACKED,
|
||||
actions='ct(table={:d},zone=NXM_NX_REG{:d}[0..15])'.format(
|
||||
fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
fwaas_ovs_consts.REG_NET)
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_TRACKED,
|
||||
priority=80,
|
||||
reg_port=port.ofport,
|
||||
actions='resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_RULES_INGRESS_TABLE)
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def _initialize_tracked_ingress(self, port):
|
||||
# Drop invalid packets
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
priority=50,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_INVALID,
|
||||
actions='drop'
|
||||
)
|
||||
# Drop traffic for removed fwg rules
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
priority=50,
|
||||
reg_port=port.ofport,
|
||||
ct_mark=fwaas_ovs_consts.CT_MARK_INVALID,
|
||||
actions='drop'
|
||||
)
|
||||
|
||||
# Allow established and related connections
|
||||
for state in (fwaas_ovs_consts.OF_STATE_ESTABLISHED_REPLY,
|
||||
fwaas_ovs_consts.OF_STATE_RELATED):
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
priority=50,
|
||||
reg_port=port.ofport,
|
||||
ct_state=state,
|
||||
ct_mark=fwaas_ovs_consts.CT_MARK_NORMAL,
|
||||
ct_zone=port.vlan_tag,
|
||||
actions='output:{:d}'.format(port.ofport)
|
||||
)
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
priority=40,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NOT_ESTABLISHED,
|
||||
actions='drop'
|
||||
)
|
||||
for ethertype in [constants.ETHERTYPE_IP, constants.ETHERTYPE_IPV6]:
|
||||
self._add_flow(
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
priority=40,
|
||||
dl_type=ethertype,
|
||||
reg_port=port.ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_ESTABLISHED,
|
||||
actions="ct(commit,zone=NXM_NX_REG{:d}[0..15],"
|
||||
"exec(set_field:{:s}->ct_mark))".format(
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.CT_MARK_INVALID)
|
||||
)
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers) and rules_generator method
|
||||
def add_flows_from_rules(self, port):
|
||||
self._initialize_tracked_ingress(port)
|
||||
self._initialize_tracked_egress(port)
|
||||
LOG.debug('Creating flow rules for port %s that is port %d in OVS',
|
||||
port.id, port.ofport)
|
||||
for rule in self.create_rules_generator_for_port(port):
|
||||
flows = rules.create_flows_from_rule_and_port(rule, port)
|
||||
LOG.debug("RULGEN: Rules generated for flow %s are %s",
|
||||
rule, flows)
|
||||
for flow in flows:
|
||||
if rule.get('action') == ACTION_ALLOW:
|
||||
self._accept_flow(**flow)
|
||||
else:
|
||||
self._drop_flow(**flow)
|
||||
|
||||
def create_rules_generator_for_port(self, port):
|
||||
"""Returns a generator emitting rules valid for further processing
|
||||
|
||||
Injects necessary fields to feed one-by-one to rules module to
|
||||
transform into valid openflow rules.
|
||||
"""
|
||||
|
||||
def inject_fields(rule, direction, offset=0):
|
||||
"""Add fields to rule dict to be able to utilize rules module
|
||||
|
||||
Currently such fields are added:
|
||||
'offset', 'direction', 'ethertype', 'source_port_range_min',
|
||||
'source_port_range_max', 'port_range_min', 'port_range_max'
|
||||
"""
|
||||
# XXX NOTE(ivasilevskaya) maybe there's a clever way to do that
|
||||
version_ethertype_map = {lib_const.IP_VERSION_4: lib_const.IPv4,
|
||||
lib_const.IP_VERSION_6: lib_const.IPv6}
|
||||
|
||||
rule['direction'] = direction
|
||||
rule['ethertype'] = version_ethertype_map[rule['ip_version']]
|
||||
rule['offset'] = offset
|
||||
|
||||
# transfer destination_port into port_range_min/port_range_max
|
||||
def add_range(range_key, key_min, key_max):
|
||||
range_str = rule.get(range_key)
|
||||
if not range_str:
|
||||
return
|
||||
ports = range_str.split(':', 1)
|
||||
rule[key_min] = int(ports[0])
|
||||
rule['port_range_max'] = (
|
||||
int(ports[1]) if len(ports) == 2 else int(ports[0]))
|
||||
|
||||
add_range('destination_port', 'port_range_min', 'port_range_max')
|
||||
add_range('source_port', 'source_port_range_min',
|
||||
'source_port_range_max')
|
||||
|
||||
# add direction field
|
||||
offset = len(port.fw_group.ingress_rules) - 1
|
||||
for rule in port.fw_group.ingress_rules:
|
||||
inject_fields(rule, firewall.INGRESS_DIRECTION, offset)
|
||||
offset -= 1
|
||||
yield rule
|
||||
|
||||
offset = len(port.fw_group.egress_rules) - 1
|
||||
for rule in port.fw_group.egress_rules:
|
||||
inject_fields(rule, firewall.EGRESS_DIRECTION, offset)
|
||||
offset -= 1
|
||||
yield rule
|
||||
|
||||
# NOTE(ivasilevskaya) That's a copy-paste from neutron ovsfw driver
|
||||
# which differs in constants (table numbers)
|
||||
def delete_all_port_flows(self, port):
|
||||
"""Delete all flows for given port"""
|
||||
for mac_addr in port.all_allowed_macs:
|
||||
self._strict_delete_flow(priority=95,
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
dl_dst=mac_addr,
|
||||
dl_vlan=port.vlan_tag)
|
||||
self._delete_flows(
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE,
|
||||
dl_dst=mac_addr, reg_net=port.vlan_tag)
|
||||
self._strict_delete_flow(priority=105,
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
in_port=port.ofport)
|
||||
self._delete_flows(reg_port=port.ofport)
|
||||
|
||||
def create_firewall_group(self, ports_for_fwg, firewall_group):
|
||||
ingress_rules = firewall_group['egress_rule_list']
|
||||
egress_rules = firewall_group['ingress_rule_list']
|
||||
fwg_id = firewall_group['id']
|
||||
|
||||
self.update_firewall_group_rules(fwg_id, ingress_rules, egress_rules)
|
||||
for port in ports_for_fwg:
|
||||
port['firewall_group'] = fwg_id
|
||||
self.update_port_filter(port)
|
||||
|
||||
def update_firewall_group(self, ports_for_fwg, firewall_group):
|
||||
self.create_firewall_group(ports_for_fwg, firewall_group)
|
||||
|
||||
def delete_firewall_group(self, ports_for_fwg, firewall_group):
|
||||
for port in ports_for_fwg:
|
||||
port['firewall_group'] = firewall_group['id']
|
||||
self.remove_port_filter(port)
|
|
@ -0,0 +1,186 @@
|
|||
# Copyright 2015 Red Hat, 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 neutron_lib import constants as n_consts
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.agent import firewall
|
||||
from neutron.common import utils
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import constants as fwaas_ovs_consts
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver, differs in
|
||||
# constants
|
||||
CT_STATES = [
|
||||
fwaas_ovs_consts.OF_STATE_ESTABLISHED_NOT_REPLY,
|
||||
fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED]
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
FLOW_FIELD_FOR_IPVER_AND_DIRECTION = {
|
||||
(n_consts.IP_VERSION_4, firewall.EGRESS_DIRECTION): 'nw_dst',
|
||||
(n_consts.IP_VERSION_6, firewall.EGRESS_DIRECTION): 'ipv6_dst',
|
||||
(n_consts.IP_VERSION_4, firewall.INGRESS_DIRECTION): 'nw_src',
|
||||
(n_consts.IP_VERSION_6, firewall.INGRESS_DIRECTION): 'ipv6_src',
|
||||
}
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
FORBIDDEN_PREFIXES = (n_consts.IPv4_ANY, n_consts.IPv6_ANY)
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
def is_valid_prefix(ip_prefix):
|
||||
# IPv6 have multiple ways how to describe ::/0 network, converting to
|
||||
# IPNetwork and back to string unifies it
|
||||
return (ip_prefix and
|
||||
str(netaddr.IPNetwork(ip_prefix)) not in FORBIDDEN_PREFIXES)
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
def create_flows_from_rule_and_port(rule, port):
|
||||
ethertype = rule['ethertype']
|
||||
direction = rule['direction']
|
||||
dst_ip_prefix = rule.get('dest_ip_prefix')
|
||||
src_ip_prefix = rule.get('source_ip_prefix')
|
||||
offset = int(rule.get('offset', 0))
|
||||
|
||||
flow_template = {
|
||||
'priority': 70 + offset,
|
||||
'dl_type': fwaas_ovs_consts.ethertype_to_dl_type_map[ethertype],
|
||||
'reg_port': port.ofport,
|
||||
}
|
||||
|
||||
if is_valid_prefix(dst_ip_prefix):
|
||||
flow_template[FLOW_FIELD_FOR_IPVER_AND_DIRECTION[(
|
||||
utils.get_ip_version(dst_ip_prefix), firewall.EGRESS_DIRECTION)]
|
||||
] = dst_ip_prefix
|
||||
|
||||
if is_valid_prefix(src_ip_prefix):
|
||||
flow_template[FLOW_FIELD_FOR_IPVER_AND_DIRECTION[(
|
||||
utils.get_ip_version(src_ip_prefix), firewall.INGRESS_DIRECTION)]
|
||||
] = src_ip_prefix
|
||||
|
||||
flows = create_protocol_flows(direction, flow_template, port, rule)
|
||||
|
||||
return flows
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver, differs in
|
||||
# constants
|
||||
def populate_flow_common(direction, flow_template, port):
|
||||
"""Initialize common flow fields."""
|
||||
if direction == firewall.INGRESS_DIRECTION:
|
||||
flow_template['table'] = fwaas_ovs_consts.FW_RULES_INGRESS_TABLE
|
||||
flow_template['actions'] = "output:{:d}".format(port.ofport)
|
||||
elif direction == firewall.EGRESS_DIRECTION:
|
||||
flow_template['table'] = fwaas_ovs_consts.FW_RULES_EGRESS_TABLE
|
||||
# Traffic can be both ingress and egress, check that no ingress rules
|
||||
# should be applied
|
||||
flow_template['actions'] = 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE)
|
||||
return flow_template
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
def create_protocol_flows(direction, flow_template, port, rule):
|
||||
flow_template = populate_flow_common(direction,
|
||||
flow_template.copy(),
|
||||
port)
|
||||
protocol = rule.get('protocol')
|
||||
if protocol is not None:
|
||||
flow_template['nw_proto'] = protocol
|
||||
|
||||
if protocol in [n_consts.PROTO_NUM_ICMP, n_consts.PROTO_NUM_IPV6_ICMP]:
|
||||
flows = create_icmp_flows(flow_template, rule)
|
||||
else:
|
||||
flows = create_port_range_flows(flow_template, rule)
|
||||
return flows or [flow_template]
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver, differs only in
|
||||
# constant
|
||||
def create_port_range_flows(flow_template, rule):
|
||||
protocol = fwaas_ovs_consts.REVERSE_IP_PROTOCOL_MAP_WITH_PORTS.get(
|
||||
rule.get('protocol'))
|
||||
if protocol is None:
|
||||
return []
|
||||
flows = []
|
||||
src_port_match = '{:s}_src'.format(protocol)
|
||||
src_port_min = rule.get('source_port_range_min')
|
||||
src_port_max = rule.get('source_port_range_max')
|
||||
dst_port_match = '{:s}_dst'.format(protocol)
|
||||
dst_port_min = rule.get('port_range_min')
|
||||
dst_port_max = rule.get('port_range_max')
|
||||
|
||||
dst_port_range = []
|
||||
if dst_port_min and dst_port_max:
|
||||
dst_port_range = utils.port_rule_masking(dst_port_min, dst_port_max)
|
||||
|
||||
src_port_range = []
|
||||
if src_port_min and src_port_max:
|
||||
src_port_range = utils.port_rule_masking(src_port_min, src_port_max)
|
||||
for port in src_port_range:
|
||||
flow = flow_template.copy()
|
||||
flow[src_port_match] = port
|
||||
if dst_port_range:
|
||||
for port in dst_port_range:
|
||||
dst_flow = flow.copy()
|
||||
dst_flow[dst_port_match] = port
|
||||
flows.append(dst_flow)
|
||||
else:
|
||||
flows.append(flow)
|
||||
else:
|
||||
for port in dst_port_range:
|
||||
flow = flow_template.copy()
|
||||
flow[dst_port_match] = port
|
||||
flows.append(flow)
|
||||
|
||||
return flows
|
||||
|
||||
|
||||
# NOTE(ivasilevskaya) copy-paste from neutron ovsfw driver
|
||||
def create_icmp_flows(flow_template, rule):
|
||||
icmp_type = rule.get('port_range_min')
|
||||
if icmp_type is None:
|
||||
return
|
||||
flow = flow_template.copy()
|
||||
flow['icmp_type'] = icmp_type
|
||||
|
||||
icmp_code = rule.get('port_range_max')
|
||||
if icmp_code is not None:
|
||||
flow['icmp_code'] = icmp_code
|
||||
return [flow]
|
||||
|
||||
|
||||
def create_accept_flows(flow):
|
||||
flow['ct_state'] = CT_STATES[0]
|
||||
result = [flow.copy()]
|
||||
flow['ct_state'] = CT_STATES[1]
|
||||
if flow['table'] == fwaas_ovs_consts.FW_RULES_INGRESS_TABLE:
|
||||
flow['actions'] = (
|
||||
'ct(commit,zone=NXM_NX_REG{:d}[0..15]),{:s}'.format(
|
||||
fwaas_ovs_consts.REG_NET, flow['actions']))
|
||||
result.append(flow)
|
||||
return result
|
||||
|
||||
|
||||
def create_drop_flows(flow):
|
||||
if flow['table'] in [fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
fwaas_ovs_consts.FW_RULES_EGRESS_TABLE]:
|
||||
flow['actions'] = 'drop'
|
||||
return [flow]
|
|
@ -0,0 +1,611 @@
|
|||
# Copyright 2017 Mirantis, Inc.
|
||||
#
|
||||
# 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 neutron_lib import constants
|
||||
import testtools
|
||||
|
||||
from neutron.agent.common import ovs_lib
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
|
||||
as ovs_consts
|
||||
from neutron.tests import base
|
||||
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import constants as fwaas_ovs_consts
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import exceptions
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import firewall as ovsfw
|
||||
|
||||
TESTING_VLAN_TAG = 1
|
||||
|
||||
|
||||
def create_ofport(port_dict):
|
||||
ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00', ofport=1,
|
||||
port_name="port-name")
|
||||
return ovsfw.OFPort(port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG)
|
||||
|
||||
|
||||
class TestCreateRegNumbers(base.BaseTestCase):
|
||||
def test_no_registers_defined(self):
|
||||
flow = {'foo': 'bar'}
|
||||
ovsfw.create_reg_numbers(flow)
|
||||
self.assertEqual({'foo': 'bar'}, flow)
|
||||
|
||||
def test_both_registers_defined(self):
|
||||
flow = {'foo': 'bar', 'reg_port': 1, 'reg_net': 2}
|
||||
expected_flow = {'foo': 'bar',
|
||||
'reg{:d}'.format(fwaas_ovs_consts.REG_PORT): 1,
|
||||
'reg{:d}'.format(fwaas_ovs_consts.REG_NET): 2}
|
||||
ovsfw.create_reg_numbers(flow)
|
||||
self.assertEqual(expected_flow, flow)
|
||||
|
||||
|
||||
class TestFirewallGroup(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFirewallGroup, self).setUp()
|
||||
self.fwg = ovsfw.FirewallGroup('123')
|
||||
self.fwg.members = {'type': [1, 2, 3, 4]}
|
||||
|
||||
def test_update_rules(self):
|
||||
ingress_rules = [{'foo-ingress': 'bar', 'rule': 'all'},
|
||||
{'bar-ingress': 'foo'}]
|
||||
egress_rules = [{'foo-egress': '123456'}, {'bar-egress': 'bar'}]
|
||||
self.fwg.update_rules(ingress_rules, egress_rules)
|
||||
|
||||
self.assertEqual(ingress_rules, self.fwg.ingress_rules)
|
||||
self.assertEqual(egress_rules, self.fwg.egress_rules)
|
||||
|
||||
def test_update_rules_protocols(self):
|
||||
# XXX FIXME(ivasilevskaya) figure out what this test does and fix
|
||||
# appropriately
|
||||
# leaving failing as it may be important
|
||||
rules = [
|
||||
{'foo': 'bar', 'protocol': constants.PROTO_NAME_ICMP,
|
||||
'ethertype': constants.IPv4},
|
||||
{'foo': 'bar', 'protocol': constants.PROTO_NAME_ICMP,
|
||||
'ethertype': constants.IPv6},
|
||||
{'foo': 'bar', 'protocol': constants.PROTO_NAME_IPV6_ICMP_LEGACY,
|
||||
'ethertype': constants.IPv6},
|
||||
{'foo': 'bar', 'protocol': constants.PROTO_NAME_TCP},
|
||||
{'foo': 'bar', 'protocol': '94'},
|
||||
{'foo': 'bar', 'protocol': 'baz'},
|
||||
{'foo': 'no_proto'}]
|
||||
self.fwg.update_rules(rules, [])
|
||||
|
||||
self.assertEqual({'foo': 'no_proto'}, self.fwg.ingress_rules.pop())
|
||||
protos = [rule['protocol'] for rule in self.fwg.ingress_rules]
|
||||
self.assertEqual([constants.PROTO_NUM_ICMP,
|
||||
constants.PROTO_NUM_IPV6_ICMP,
|
||||
constants.PROTO_NUM_IPV6_ICMP,
|
||||
constants.PROTO_NUM_TCP,
|
||||
94,
|
||||
'baz'], protos)
|
||||
|
||||
def test_get_ethertype_filtered_addresses(self):
|
||||
addresses = self.fwg.get_ethertype_filtered_addresses('type')
|
||||
expected_addresses = [1, 2, 3, 4]
|
||||
self.assertEqual(expected_addresses, addresses)
|
||||
|
||||
|
||||
class TestOFPort(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestOFPort, self).setUp()
|
||||
self.ipv4_addresses = ['10.0.0.1', '192.168.0.1']
|
||||
self.ipv6_addresses = ['fe80::f816:3eff:fe2e:1']
|
||||
port_dict = {'device': 1,
|
||||
'fixed_ips': [
|
||||
{'subnet_id': 's_%s' % ip, 'ip_address': ip}
|
||||
for ip in self.ipv4_addresses + self.ipv6_addresses]}
|
||||
self.port = create_ofport(port_dict)
|
||||
|
||||
def test_ipv4_address(self):
|
||||
ipv4_addresses = self.port.ipv4_addresses
|
||||
self.assertEqual(self.ipv4_addresses, ipv4_addresses)
|
||||
|
||||
def test_ipv6_address(self):
|
||||
ipv6_addresses = self.port.ipv6_addresses
|
||||
self.assertEqual(self.ipv6_addresses, ipv6_addresses)
|
||||
|
||||
def test__get_allowed_pairs(self):
|
||||
port = {
|
||||
'allowed_address_pairs': [
|
||||
{'mac_address': 'foo', 'ip_address': '10.0.0.1'},
|
||||
{'mac_address': 'bar', 'ip_address': '192.168.0.1'},
|
||||
{'mac_address': 'qux', 'ip_address': '169.254.0.0/16'},
|
||||
{'mac_address': 'baz', 'ip_address': '2003::f'},
|
||||
]}
|
||||
allowed_pairs_v4 = ovsfw.OFPort._get_allowed_pairs(port, version=4)
|
||||
allowed_pairs_v6 = ovsfw.OFPort._get_allowed_pairs(port, version=6)
|
||||
expected_aap_v4 = {('foo', '10.0.0.1'), ('bar', '192.168.0.1'),
|
||||
('qux', '169.254.0.0/16')}
|
||||
expected_aap_v6 = {('baz', '2003::f')}
|
||||
self.assertEqual(expected_aap_v4, allowed_pairs_v4)
|
||||
self.assertEqual(expected_aap_v6, allowed_pairs_v6)
|
||||
|
||||
def test__get_allowed_pairs_empty(self):
|
||||
port = {}
|
||||
allowed_pairs = ovsfw.OFPort._get_allowed_pairs(port, version=4)
|
||||
self.assertFalse(allowed_pairs)
|
||||
|
||||
def test_update(self):
|
||||
old_port_dict = self.port.neutron_port_dict
|
||||
new_port_dict = old_port_dict.copy()
|
||||
added_ips = [1, 2, 3]
|
||||
new_port_dict.update({
|
||||
'fixed_ips': added_ips,
|
||||
'allowed_address_pairs': [
|
||||
{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '192.168.0.1'},
|
||||
{'mac_address': '00:00:00:00:00:01',
|
||||
'ip_address': '2003::f'}],
|
||||
})
|
||||
self.port.update(new_port_dict)
|
||||
self.assertEqual(new_port_dict, self.port.neutron_port_dict)
|
||||
self.assertIsNot(new_port_dict, self.port.neutron_port_dict)
|
||||
self.assertEqual(added_ips, self.port.fixed_ips)
|
||||
self.assertEqual({('00:00:00:00:00:01', '192.168.0.1')},
|
||||
self.port.allowed_pairs_v4)
|
||||
self.assertIn(('00:00:00:00:00:01', '2003::f'),
|
||||
self.port.allowed_pairs_v6)
|
||||
|
||||
|
||||
class TestFWGPortMap(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFWGPortMap, self).setUp()
|
||||
self.map = ovsfw.FWGPortMap()
|
||||
|
||||
def test_get_or_create_fwg_existing_fwg(self):
|
||||
self.map.fw_groups['id'] = mock.sentinel
|
||||
fwg = self.map.get_or_create_fwg('id')
|
||||
self.assertIs(mock.sentinel, fwg)
|
||||
|
||||
def test_get_or_create_fwg_nonexisting_fwg(self):
|
||||
with mock.patch.object(ovsfw, 'FirewallGroup') as fwg_mock:
|
||||
fwg = self.map.get_or_create_fwg('id')
|
||||
self.assertEqual(fwg_mock.return_value, fwg)
|
||||
|
||||
def _check_port(self, port_id, expected_id):
|
||||
port = self.map.ports[port_id]
|
||||
expected_fwg = self.map.fw_groups[expected_id]
|
||||
self.assertEqual(expected_fwg, port.fw_group)
|
||||
|
||||
def _check_fwg(self, fwg_id, expected_port_ids):
|
||||
fwg = self.map.fw_groups[fwg_id]
|
||||
expected_ports = {self.map.ports[port_id]
|
||||
for port_id in expected_port_ids}
|
||||
self.assertEqual(expected_ports, fwg.ports)
|
||||
|
||||
def _create_ports_and_fwgs(self):
|
||||
fwg_1 = ovsfw.FirewallGroup(1)
|
||||
fwg_2 = ovsfw.FirewallGroup(2)
|
||||
fwg_3 = ovsfw.FirewallGroup(3)
|
||||
port_a = create_ofport({'device': 'a'})
|
||||
port_b = create_ofport({'device': 'b'})
|
||||
port_c = create_ofport({'device': 'c'})
|
||||
self.map.ports = {'a': port_a, 'b': port_b, 'c': port_c}
|
||||
self.map.fw_groups = {1: fwg_1, 2: fwg_2, 3: fwg_3}
|
||||
# XXX FIXME(ivasilevskaya) see note for OFPORT
|
||||
port_a.fw_group = fwg_1
|
||||
port_b.fw_group = fwg_2
|
||||
port_c.fw_group = fwg_2
|
||||
fwg_1.ports = {port_a}
|
||||
fwg_2.ports = {port_b, port_c}
|
||||
|
||||
def test_create_port(self):
|
||||
"""Create a port and assign it to firewall group
|
||||
|
||||
It is implied that 1 port can be assigned to one firewall group only
|
||||
"""
|
||||
port = create_ofport({'device': 'a'})
|
||||
port_dict = {'some-port-attributes-go-here': 42,
|
||||
'firewall_group': 1}
|
||||
self.map.create_port(port, port_dict)
|
||||
self._check_port('a', 1)
|
||||
self._check_fwg(1, ['a'])
|
||||
|
||||
def test_update_port_another_fwg_added(self):
|
||||
"""Update a port with new firewall group id
|
||||
|
||||
It is implied that 1 port can be assigned to one firewall group only
|
||||
"""
|
||||
self._create_ports_and_fwgs()
|
||||
self._check_port('b', 2)
|
||||
port_dict = {'firewall_group': 3}
|
||||
self.map.update_port(self.map.ports['b'], port_dict)
|
||||
self._check_port('a', 1)
|
||||
self._check_port('b', 3)
|
||||
self._check_port('c', 2)
|
||||
self._check_fwg(1, ['a'])
|
||||
self._check_fwg(2, ['c'])
|
||||
self._check_fwg(3, ['b'])
|
||||
|
||||
def test_remove_port(self):
|
||||
self._create_ports_and_fwgs()
|
||||
self.map.remove_port(self.map.ports['c'])
|
||||
self._check_port('b', 2)
|
||||
self._check_fwg(1, ['a'])
|
||||
self._check_fwg(2, ['b'])
|
||||
self.assertNotIn('c', self.map.ports)
|
||||
|
||||
def test_update_rules(self):
|
||||
"""Just make sure it doesn't crash"""
|
||||
self.map.update_rules(42, [], [])
|
||||
|
||||
def test_update_members(self):
|
||||
"""Just make sure it doesn't crash"""
|
||||
self.map.update_members(42, [])
|
||||
|
||||
|
||||
class FakeOVSPort(object):
|
||||
def __init__(self, name, port, mac):
|
||||
self.port_name = name
|
||||
self.ofport = port
|
||||
self.vif_mac = mac
|
||||
|
||||
|
||||
class TestOVSFirewallDriver(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestOVSFirewallDriver, self).setUp()
|
||||
mock_bridge = mock.patch.object(
|
||||
ovs_lib, 'OVSBridge', autospec=True).start()
|
||||
self.firewall = ovsfw.OVSFirewallDriver(mock_bridge)
|
||||
self.mock_bridge = self.firewall.int_br
|
||||
self.mock_bridge.reset_mock()
|
||||
self.fake_ovs_port = FakeOVSPort('port', 1, '00:00:00:00:00:00')
|
||||
self.mock_bridge.br.get_vif_port_by_id.return_value = \
|
||||
self.fake_ovs_port
|
||||
|
||||
def _prepare_firewall_group(self):
|
||||
ingress_rules = [
|
||||
{'position': '1',
|
||||
'protocol': 'tcp',
|
||||
'ip_version': 4,
|
||||
'destination_port': '123',
|
||||
'enabled': True,
|
||||
'action': 'allow',
|
||||
'id': 'fake-fw-rule1'}
|
||||
]
|
||||
egress_rules = [
|
||||
{'position': '2',
|
||||
'protocol': 'udp',
|
||||
'ip_version': 4,
|
||||
'enabled': True,
|
||||
'action': 'allow',
|
||||
'id': 'fake-fw-rule2'},
|
||||
{'position': '3',
|
||||
'protocol': 'tcp',
|
||||
'ip_version': 6,
|
||||
'enabled': True,
|
||||
'action': 'allow',
|
||||
'id': 'fake-fw-rule3'}]
|
||||
self.firewall.update_firewall_group_rules(1, ingress_rules, [])
|
||||
self.firewall.update_firewall_group_rules(2, [], egress_rules)
|
||||
|
||||
@property
|
||||
def port_ofport(self):
|
||||
return self.mock_bridge.br.get_vif_port_by_id.return_value.ofport
|
||||
|
||||
@property
|
||||
def port_mac(self):
|
||||
return self.mock_bridge.br.get_vif_port_by_id.return_value.vif_mac
|
||||
|
||||
def test_initialize_bridge(self):
|
||||
br = self.firewall.initialize_bridge(self.mock_bridge)
|
||||
self.assertEqual(br, self.mock_bridge.deferred.return_value)
|
||||
|
||||
def test__add_flow_dl_type_formatted_to_string(self):
|
||||
dl_type = 0x0800
|
||||
self.firewall._add_flow(dl_type=dl_type)
|
||||
|
||||
def test__add_flow_registers_are_replaced(self):
|
||||
self.firewall._add_flow(in_port=1, reg_port=1, reg_net=2)
|
||||
expected_calls = {'in_port': 1,
|
||||
'reg{:d}'.format(fwaas_ovs_consts.REG_PORT): 1,
|
||||
'reg{:d}'.format(fwaas_ovs_consts.REG_NET): 2}
|
||||
self.mock_bridge.br.add_flow.assert_called_once_with(
|
||||
**expected_calls)
|
||||
|
||||
def test__drop_all_unmatched_flows(self):
|
||||
self.firewall._drop_all_unmatched_flows()
|
||||
expected_calls = [
|
||||
mock.call(actions='drop', priority=0,
|
||||
table=fwaas_ovs_consts.FW_BASE_EGRESS_TABLE),
|
||||
mock.call(actions='drop', priority=0,
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE),
|
||||
mock.call(actions='drop', priority=0,
|
||||
table=fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
mock.call(actions='drop', priority=0,
|
||||
table=fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
|
||||
mock.call(actions='drop', priority=0,
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE)]
|
||||
actual_calls = self.firewall.int_br.br.add_flow.call_args_list
|
||||
self.assertEqual(expected_calls, actual_calls)
|
||||
|
||||
def test_get_or_create_ofport_non_existing(self):
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 123,
|
||||
'lvlan': TESTING_VLAN_TAG,
|
||||
}
|
||||
port = self.firewall.get_or_create_ofport(port_dict)
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 456,
|
||||
'lvlan': TESTING_VLAN_TAG,
|
||||
}
|
||||
port = self.firewall.get_or_create_ofport(port_dict)
|
||||
sg1, sg2 = sorted(
|
||||
self.firewall.fwg_port_map.fw_groups.values(),
|
||||
key=lambda x: x.id)
|
||||
self.assertIn(port, self.firewall.fwg_port_map.ports.values())
|
||||
self.assertEqual(port.fw_group, sg2)
|
||||
self.assertEqual(set(), sg1.ports)
|
||||
self.assertIn(port, sg2.ports)
|
||||
|
||||
def test_get_or_create_ofport_existing(self):
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 123}
|
||||
of_port = create_ofport(port_dict)
|
||||
self.firewall.fwg_port_map.ports[of_port.id] = of_port
|
||||
port = self.firewall.get_or_create_ofport(port_dict)
|
||||
[sg1] = sorted(self.firewall.fwg_port_map.fw_groups.values(),
|
||||
key=lambda x: x.id)
|
||||
self.assertIs(of_port, port)
|
||||
self.assertIn(port, self.firewall.fwg_port_map.ports.values())
|
||||
self.assertEqual(port.fw_group, sg1)
|
||||
self.assertIn(port, sg1.ports)
|
||||
|
||||
def test_get_or_create_ofport_changed(self):
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 123}
|
||||
of_port = create_ofport(port_dict)
|
||||
self.firewall.fwg_port_map.ports[of_port.id] = of_port
|
||||
fake_ovs_port = FakeOVSPort('port', 2, '00:00:00:00:00:00')
|
||||
self.mock_bridge.br.get_vif_port_by_id.return_value = \
|
||||
fake_ovs_port
|
||||
port = self.firewall.get_or_create_ofport(port_dict)
|
||||
self.assertEqual(port.ofport, 2)
|
||||
|
||||
def test_get_or_create_ofport_missing(self):
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 123}
|
||||
self.mock_bridge.br.get_vif_port_by_id.return_value = None
|
||||
with testtools.ExpectedException(exceptions.OVSFWaaSPortNotFound):
|
||||
self.firewall.get_or_create_ofport(port_dict)
|
||||
|
||||
def test_get_or_create_ofport_missing_nocreate(self):
|
||||
port_dict = {
|
||||
'device': 'port-id',
|
||||
'firewall_group': 123}
|
||||
self.mock_bridge.br.get_vif_port_by_id.return_value = None
|
||||
self.assertIsNone(self.firewall.get_ofport(port_dict))
|
||||
self.assertFalse(self.mock_bridge.br.get_vif_port_by_id.called)
|
||||
|
||||
def test_is_port_managed_managed_port(self):
|
||||
port_dict = {'device': 'port-id'}
|
||||
self.firewall.fwg_port_map.ports[port_dict['device']] = object()
|
||||
is_managed = self.firewall.is_port_managed(port_dict)
|
||||
self.assertTrue(is_managed)
|
||||
|
||||
def test_is_port_managed_not_managed_port(self):
|
||||
port_dict = {'device': 'port-id'}
|
||||
is_managed = self.firewall.is_port_managed(port_dict)
|
||||
self.assertFalse(is_managed)
|
||||
|
||||
def test_prepare_port_filter(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'fixed_ips': [{'subnet_id': "some_subnet_id_here",
|
||||
'ip_address': "10.0.0.1"}],
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
exp_egress_classifier = mock.call(
|
||||
actions='set_field:{:d}->reg5,set_field:{:d}->reg6,'
|
||||
'resubmit(,{:d})'.format(
|
||||
self.port_ofport, TESTING_VLAN_TAG,
|
||||
fwaas_ovs_consts.FW_BASE_EGRESS_TABLE),
|
||||
in_port=self.port_ofport,
|
||||
priority=105,
|
||||
table=ovs_consts.TRANSIENT_TABLE)
|
||||
exp_ingress_classifier = mock.call(
|
||||
actions='set_field:{:d}->reg5,set_field:{:d}->reg6,'
|
||||
'strip_vlan,resubmit(,{:d})'.format(
|
||||
self.port_ofport, TESTING_VLAN_TAG,
|
||||
fwaas_ovs_consts.FW_BASE_INGRESS_TABLE),
|
||||
dl_dst=self.port_mac,
|
||||
dl_vlan='0x%x' % TESTING_VLAN_TAG,
|
||||
priority=95,
|
||||
table=ovs_consts.TRANSIENT_TABLE)
|
||||
filter_rule = mock.call(
|
||||
actions='ct(commit,zone=NXM_NX_REG6[0..15]),'
|
||||
'output:{:d}'.format(self.port_ofport),
|
||||
dl_type="0x{:04x}".format(n_const.ETHERTYPE_IP),
|
||||
nw_proto=constants.PROTO_NUM_TCP,
|
||||
priority=70,
|
||||
reg5=self.port_ofport,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
|
||||
table=fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
tcp_dst='0x007b')
|
||||
calls = self.mock_bridge.br.add_flow.call_args_list
|
||||
for call in exp_ingress_classifier, exp_egress_classifier, filter_rule:
|
||||
self.assertIn(call, calls)
|
||||
|
||||
def test_prepare_port_filter_port_security_disabled(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'port_security_enabled': False}
|
||||
self._prepare_firewall_group()
|
||||
with mock.patch.object(
|
||||
self.firewall, 'initialize_port_flows') as m_init_flows:
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
self.assertFalse(m_init_flows.called)
|
||||
|
||||
def test_prepare_port_filter_initialized_port(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
self.assertFalse(self.mock_bridge.br.delete_flows.called)
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
self.assertTrue(self.mock_bridge.br.delete_flows.called)
|
||||
|
||||
def test_update_port_filter(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
port_dict['firewall_group'] = 2
|
||||
self.mock_bridge.reset_mock()
|
||||
|
||||
self.firewall.update_port_filter(port_dict)
|
||||
self.assertTrue(self.mock_bridge.br.delete_flows.called)
|
||||
filter_rules = [
|
||||
mock.call(
|
||||
actions='resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
dl_type="0x{:04x}".format(n_const.ETHERTYPE_IP),
|
||||
nw_proto=constants.PROTO_NUM_UDP,
|
||||
priority=71,
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_NEW_NOT_ESTABLISHED,
|
||||
reg5=self.port_ofport,
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE),
|
||||
# XXX FIXME NOTE(ivasilevskaya) this test originally tested that
|
||||
# flows for SG with remote_group=this group were generated with
|
||||
# proper conjunction action. If the original idea that conj_manager
|
||||
# isn't needed for firewall groups proves to be wrong this needs to
|
||||
# be revizited and properly fixed/covered with tests
|
||||
mock.call(
|
||||
actions='resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
ct_state=fwaas_ovs_consts.OF_STATE_ESTABLISHED_NOT_REPLY,
|
||||
dl_type=mock.ANY,
|
||||
nw_proto=6,
|
||||
priority=70, reg5=self.port_ofport,
|
||||
table=fwaas_ovs_consts.FW_RULES_EGRESS_TABLE)]
|
||||
self.mock_bridge.br.add_flow.assert_has_calls(filter_rules,
|
||||
any_order=True)
|
||||
|
||||
def test_update_port_filter_create_new_port_if_not_present(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1}
|
||||
self._prepare_firewall_group()
|
||||
with mock.patch.object(
|
||||
self.firewall, 'prepare_port_filter') as prepare_mock:
|
||||
self.firewall.update_port_filter(port_dict)
|
||||
self.assertTrue(prepare_mock.called)
|
||||
|
||||
def test_update_port_filter_port_security_disabled(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
port_dict['port_security_enabled'] = False
|
||||
self.firewall.update_port_filter(port_dict)
|
||||
self.assertTrue(self.mock_bridge.br.delete_flows.called)
|
||||
|
||||
def test_remove_port_filter(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.prepare_port_filter(port_dict)
|
||||
self.firewall.remove_port_filter(port_dict)
|
||||
self.assertTrue(self.mock_bridge.br.delete_flows.called)
|
||||
self.assertIn(1, self.firewall.fwg_to_delete)
|
||||
|
||||
def test_remove_port_filter_port_security_disabled(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1}
|
||||
self.firewall.remove_port_filter(port_dict)
|
||||
self.assertFalse(self.mock_bridge.br.delete_flows.called)
|
||||
|
||||
def test_update_firewall_group_rules(self):
|
||||
"""Just make sure it doesn't crash"""
|
||||
new_rules_ingress = [
|
||||
{'ip_version': 4,
|
||||
'action': 'allow',
|
||||
'protocol': constants.PROTO_NAME_ICMP},
|
||||
{'ip_version': 4,
|
||||
'direction': 'deny'}]
|
||||
self.firewall.update_firewall_group_rules(1, new_rules_ingress, [])
|
||||
|
||||
def test__cleanup_stale_sg(self):
|
||||
self._prepare_firewall_group()
|
||||
self.firewall.fwg_to_delete = {1}
|
||||
with mock.patch.object(self.firewall.fwg_port_map,
|
||||
'delete_fwg') as delete_fwg_mock:
|
||||
self.firewall._cleanup_stale_fwg()
|
||||
delete_fwg_mock.assert_called_once_with(1)
|
||||
|
||||
def test_get_ovs_port(self):
|
||||
ovs_port = self.firewall.get_ovs_port('port_id')
|
||||
self.assertEqual(self.fake_ovs_port, ovs_port)
|
||||
|
||||
def test_get_ovs_port_non_existent(self):
|
||||
self.mock_bridge.br.get_vif_port_by_id.return_value = None
|
||||
with testtools.ExpectedException(exceptions.OVSFWaaSPortNotFound):
|
||||
self.firewall.get_ovs_port('port_id')
|
||||
|
||||
def test__initialize_egress_no_port_security_sends_to_egress(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': TESTING_VLAN_TAG}
|
||||
self.firewall._initialize_egress_no_port_security(port_dict)
|
||||
expected_call = mock.call(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
priority=100,
|
||||
in_port=self.fake_ovs_port.ofport,
|
||||
actions='set_field:%d->reg%d,'
|
||||
'set_field:%d->reg%d,'
|
||||
'resubmit(,%d)' % (
|
||||
self.fake_ovs_port.ofport,
|
||||
fwaas_ovs_consts.REG_PORT,
|
||||
TESTING_VLAN_TAG,
|
||||
fwaas_ovs_consts.REG_NET,
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE)
|
||||
)
|
||||
calls = self.mock_bridge.br.add_flow.call_args_list
|
||||
self.assertIn(expected_call, calls)
|
||||
|
||||
def test__initialize_egress_no_port_security_no_tag(self):
|
||||
port_dict = {'device': 'port-id',
|
||||
'firewall_group': 1,
|
||||
'lvlan': None}
|
||||
self.firewall._initialize_egress_no_port_security(port_dict)
|
||||
self.assertFalse(self.mock_bridge.br.add_flow.called)
|
||||
|
||||
def test__remove_egress_no_port_security_deletes_flow(self):
|
||||
self.mock_bridge.br.db_get_val.return_value = {'tag': TESTING_VLAN_TAG}
|
||||
self.firewall.fwg_port_map.unfiltered['port_id'] = 1
|
||||
self.firewall._remove_egress_no_port_security('port_id')
|
||||
expected_call = mock.call(
|
||||
table=ovs_consts.TRANSIENT_TABLE,
|
||||
in_port=self.fake_ovs_port.ofport,
|
||||
)
|
||||
calls = self.mock_bridge.br.delete_flows.call_args_list
|
||||
self.assertIn(expected_call, calls)
|
||||
|
||||
def test__remove_egress_no_port_security_no_tag(self):
|
||||
self.mock_bridge.br.db_get_val.return_value = {}
|
||||
self.firewall._remove_egress_no_port_security('port_id')
|
||||
self.assertFalse(self.mock_bridge.br.delete_flows.called)
|
|
@ -0,0 +1,336 @@
|
|||
# Copyright 2015 Red Hat, Inc.
|
||||
#
|
||||
# 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 neutron_lib import constants
|
||||
|
||||
from neutron.agent import firewall
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.tests import base
|
||||
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import constants as fwaas_ovs_consts
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import firewall as ovsfw
|
||||
from neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall \
|
||||
import rules
|
||||
|
||||
TESTING_VLAN_TAG = 1
|
||||
|
||||
|
||||
class TestIsValidPrefix(base.BaseTestCase):
|
||||
def test_valid_prefix_ipv4(self):
|
||||
is_valid = rules.is_valid_prefix('10.0.0.0/0')
|
||||
self.assertTrue(is_valid)
|
||||
|
||||
def test_invalid_prefix_ipv4(self):
|
||||
is_valid = rules.is_valid_prefix('0.0.0.0/0')
|
||||
self.assertFalse(is_valid)
|
||||
|
||||
def test_valid_prefix_ipv6(self):
|
||||
is_valid = rules.is_valid_prefix('ffff::0/0')
|
||||
self.assertTrue(is_valid)
|
||||
|
||||
def test_invalid_prefix_ipv6(self):
|
||||
is_valid = rules.is_valid_prefix('0000:0::0/0')
|
||||
self.assertFalse(is_valid)
|
||||
is_valid = rules.is_valid_prefix('::0/0')
|
||||
self.assertFalse(is_valid)
|
||||
is_valid = rules.is_valid_prefix('::/0')
|
||||
self.assertFalse(is_valid)
|
||||
|
||||
|
||||
class TestCreateFlowsFromRuleAndPort(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestCreateFlowsFromRuleAndPort, self).setUp()
|
||||
ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00')
|
||||
ovs_port.ofport = 1
|
||||
port_dict = {'device': 'port_id'}
|
||||
self.port = ovsfw.OFPort(
|
||||
port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG)
|
||||
|
||||
self.create_flows_mock = mock.patch.object(
|
||||
rules, 'create_protocol_flows').start()
|
||||
|
||||
@property
|
||||
def passed_flow_template(self):
|
||||
return self.create_flows_mock.call_args[0][1]
|
||||
|
||||
def _test_create_flows_from_rule_and_port_helper(
|
||||
self, rule, expected_template):
|
||||
rules.create_flows_from_rule_and_port(rule, self.port)
|
||||
|
||||
self.assertEqual(expected_template, self.passed_flow_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_no_ip_ipv4(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv4,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IP,
|
||||
'reg_port': self.port.ofport,
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_src_and_dst_ipv4(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv4,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
'source_ip_prefix': '192.168.0.0/24',
|
||||
'dest_ip_prefix': '10.0.0.1/32',
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IP,
|
||||
'reg_port': self.port.ofport,
|
||||
'nw_src': '192.168.0.0/24',
|
||||
'nw_dst': '10.0.0.1/32',
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_src_and_dst_with_zero_ipv4(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv4,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
'source_ip_prefix': '192.168.0.0/24',
|
||||
'dest_ip_prefix': '0.0.0.0/0',
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IP,
|
||||
'reg_port': self.port.ofport,
|
||||
'nw_src': '192.168.0.0/24',
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_no_ip_ipv6(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv6,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IPV6,
|
||||
'reg_port': self.port.ofport,
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_src_and_dst_ipv6(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv6,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
'source_ip_prefix': '2001:db8:bbbb::1/64',
|
||||
'dest_ip_prefix': '2001:db8:aaaa::1/64',
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IPV6,
|
||||
'reg_port': self.port.ofport,
|
||||
'ipv6_src': '2001:db8:bbbb::1/64',
|
||||
'ipv6_dst': '2001:db8:aaaa::1/64',
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
def test_create_flows_from_rule_and_port_src_and_dst_with_zero_ipv6(self):
|
||||
rule = {
|
||||
'ethertype': constants.IPv6,
|
||||
'direction': firewall.INGRESS_DIRECTION,
|
||||
'source_ip_prefix': '2001:db8:bbbb::1/64',
|
||||
'dest_ip_prefix': '::/0',
|
||||
}
|
||||
expected_template = {
|
||||
'priority': 70,
|
||||
'dl_type': n_const.ETHERTYPE_IPV6,
|
||||
'reg_port': self.port.ofport,
|
||||
'ipv6_src': '2001:db8:bbbb::1/64',
|
||||
}
|
||||
self._test_create_flows_from_rule_and_port_helper(rule,
|
||||
expected_template)
|
||||
|
||||
|
||||
class TestCreateProtocolFlows(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestCreateProtocolFlows, self).setUp()
|
||||
ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00')
|
||||
ovs_port.ofport = 1
|
||||
port_dict = {'device': 'port_id'}
|
||||
self.port = ovsfw.OFPort(
|
||||
port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG)
|
||||
|
||||
def _test_create_protocol_flows_helper(self, direction, rule,
|
||||
expected_flows):
|
||||
flow_template = {'some_settings': 'foo'}
|
||||
for flow in expected_flows:
|
||||
flow.update(flow_template)
|
||||
flows = rules.create_protocol_flows(
|
||||
direction, flow_template, self.port, rule)
|
||||
self.assertEqual(expected_flows, flows)
|
||||
|
||||
def test_create_protocol_flows_ingress(self):
|
||||
rule = {'protocol': constants.PROTO_NUM_TCP}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_INGRESS_TABLE,
|
||||
'actions': 'output:1',
|
||||
'nw_proto': constants.PROTO_NUM_TCP,
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.INGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_egress(self):
|
||||
rule = {'protocol': constants.PROTO_NUM_TCP}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
'nw_proto': constants.PROTO_NUM_TCP,
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_no_protocol(self):
|
||||
rule = {}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_icmp6(self):
|
||||
rule = {'ethertype': constants.IPv6,
|
||||
'protocol': constants.PROTO_NUM_IPV6_ICMP}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
'nw_proto': constants.PROTO_NUM_IPV6_ICMP,
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_port_range(self):
|
||||
rule = {'ethertype': constants.IPv4,
|
||||
'protocol': constants.PROTO_NUM_TCP,
|
||||
'port_range_min': 22,
|
||||
'port_range_max': 23}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
'nw_proto': constants.PROTO_NUM_TCP,
|
||||
'tcp_dst': '0x0016/0xfffe'
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_icmp(self):
|
||||
rule = {'ethertype': constants.IPv4,
|
||||
'protocol': constants.PROTO_NUM_ICMP,
|
||||
'port_range_min': 0}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
'nw_proto': constants.PROTO_NUM_ICMP,
|
||||
'icmp_type': 0
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
def test_create_protocol_flows_ipv6_icmp(self):
|
||||
rule = {'ethertype': constants.IPv6,
|
||||
'protocol': constants.PROTO_NUM_IPV6_ICMP,
|
||||
'port_range_min': 5,
|
||||
'port_range_max': 0}
|
||||
expected_flows = [{
|
||||
'table': fwaas_ovs_consts.FW_RULES_EGRESS_TABLE,
|
||||
'actions': 'resubmit(,{:d})'.format(
|
||||
fwaas_ovs_consts.FW_ACCEPT_OR_INGRESS_TABLE),
|
||||
'nw_proto': constants.PROTO_NUM_IPV6_ICMP,
|
||||
'icmp_type': 5,
|
||||
'icmp_code': 0,
|
||||
}]
|
||||
self._test_create_protocol_flows_helper(
|
||||
firewall.EGRESS_DIRECTION, rule, expected_flows)
|
||||
|
||||
|
||||
class TestCreatePortRangeFlows(base.BaseTestCase):
|
||||
def _test_create_port_range_flows_helper(self, expected_flows, rule):
|
||||
flow_template = {'some_settings': 'foo'}
|
||||
for flow in expected_flows:
|
||||
flow.update(flow_template)
|
||||
port_range_flows = rules.create_port_range_flows(flow_template, rule)
|
||||
self.assertEqual(expected_flows, port_range_flows)
|
||||
|
||||
def test_create_port_range_flows_with_source_and_destination(self):
|
||||
rule = {
|
||||
'protocol': constants.PROTO_NUM_TCP,
|
||||
'source_port_range_min': 123,
|
||||
'source_port_range_max': 124,
|
||||
'port_range_min': 10,
|
||||
'port_range_max': 11,
|
||||
}
|
||||
expected_flows = [
|
||||
{'tcp_src': '0x007b', 'tcp_dst': '0x000a/0xfffe'},
|
||||
{'tcp_src': '0x007c', 'tcp_dst': '0x000a/0xfffe'},
|
||||
]
|
||||
self._test_create_port_range_flows_helper(expected_flows, rule)
|
||||
|
||||
def test_create_port_range_flows_with_source(self):
|
||||
rule = {
|
||||
'protocol': constants.PROTO_NUM_TCP,
|
||||
'source_port_range_min': 123,
|
||||
'source_port_range_max': 124,
|
||||
}
|
||||
expected_flows = [
|
||||
{'tcp_src': '0x007b'},
|
||||
{'tcp_src': '0x007c'},
|
||||
]
|
||||
self._test_create_port_range_flows_helper(expected_flows, rule)
|
||||
|
||||
def test_create_port_range_flows_with_destination(self):
|
||||
rule = {
|
||||
'protocol': constants.PROTO_NUM_TCP,
|
||||
'port_range_min': 10,
|
||||
'port_range_max': 11,
|
||||
}
|
||||
expected_flows = [
|
||||
{'tcp_dst': '0x000a/0xfffe'},
|
||||
]
|
||||
self._test_create_port_range_flows_helper(expected_flows, rule)
|
||||
|
||||
def test_create_port_range_flows_without_port_range(self):
|
||||
rule = {
|
||||
'protocol': constants.PROTO_NUM_TCP,
|
||||
}
|
||||
expected_flows = []
|
||||
self._test_create_port_range_flows_helper(expected_flows, rule)
|
||||
|
||||
def test_create_port_range_with_icmp_protocol(self):
|
||||
# NOTE: such call is prevented by create_protocols_flows
|
||||
rule = {
|
||||
'protocol': constants.PROTO_NUM_ICMP,
|
||||
'port_range_min': 10,
|
||||
'port_range_max': 11,
|
||||
}
|
||||
expected_flows = []
|
||||
self._test_create_port_range_flows_helper(expected_flows, rule)
|
|
@ -53,6 +53,7 @@ neutron.agent.l2.extensions =
|
|||
fwaas_v2 = neutron_fwaas.services.firewall.agents.l2.fwaas_v2:FWaaSV2AgentExtension
|
||||
neutron.agent.l2.firewall_drivers =
|
||||
noop = neutron_fwaas.services.firewall.drivers.linux.l2.noop.noop_driver:NoopFirewallL2Driver
|
||||
ovs = neutron_fwaas.services.firewall.drivers.linux.l2.openvswitch_firewall.firewall:OVSFirewallDriver
|
||||
neutron.agent.l3.extensions =
|
||||
fwaas = neutron_fwaas.services.firewall.agents.l3reference.firewall_l3_agent:L3WithFWaaS
|
||||
fwaas_v2 = neutron_fwaas.services.firewall.agents.l3reference.firewall_l3_agent_v2:L3WithFWaaS
|
||||
|
|
Loading…
Reference in New Issue