Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/source/admin/config-ovsfwdriver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ not true and there may be slight differences between those drivers.
| (please check [3]_ for details) | | rule. |
+----------------------------------------+-----------------------+-----------------------+


Permitted ethertypes
~~~~~~~~~~~~~~~~~~~~

The OVS Firewall blocks traffic that does not have either the IPv4 or IPv6
ethertypes at present. This is a behavior change compared to the
"iptables_hybrid" firewall, which only operates on IP packets and thus does
not address other ethertypes. With the configuration option
``permitted_ethertypes`` it is possible to define a set of allowed ethertypes.
Any traffic with these allowed ethertypes with destination to a local port or
generated from a local port and MAC address, will be allowed.

References
~~~~~~~~~~

Expand Down
19 changes: 19 additions & 0 deletions neutron/agent/linux/openvswitch_firewall/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,25 @@ def _initialize_ingress(self, port):
actions='output:{:d}'.format(port.ofport)
)

# Allow custom ethertypes
for permitted_ethertype in self.permitted_ethertypes:
if permitted_ethertype[:2] == '0x':
try:
hex_ethertype = hex(int(permitted_ethertype, base=16))
self._add_flow(
table=ovs_consts.BASE_INGRESS_TABLE,
priority=100,
dl_type=hex_ethertype,
reg_port=port.ofport,
actions='output:{:d}'.format(port.ofport)
)
continue
except ValueError:
pass
LOG.warning('Custom ethertype %(permitted_ethertype)s is not '
'a hexadecimal number.',
{'permitted_ethertype': permitted_ethertype})

self._initialize_ingress_ipv6_icmp(port)

# DHCP offers
Expand Down
2 changes: 0 additions & 2 deletions neutron/common/ovn/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,6 @@
LSP_OPTIONS_MCAST_FLOOD = 'mcast_flood'

LRP_OPTIONS_RESIDE_REDIR_CH = 'reside-on-redirect-chassis'
LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type'
BRIDGE_REDIRECT_TYPE = "bridged"

# Port Binding types
PB_TYPE_VIRTUAL = 'virtual'
Expand Down
2 changes: 1 addition & 1 deletion neutron/db/db_base_plugin_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def create_network_db(self, context, network):
args = {'tenant_id': project_id,
'id': n.get('id') or uuidutils.generate_uuid(),
'name': n['name'],
'mtu': n.get('mtu', constants.DEFAULT_NETWORK_MTU),
'mtu': n.get('mtu', 0),
'admin_state_up': n['admin_state_up'],
'status': n.get('status', constants.NET_STATUS_ACTIVE),
'description': n.get('description')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,10 @@ def check_vlan_distributed_ports(self):
# Get router ports belonging to VLAN networks
vlan_nets = self._ovn_client._plugin.get_networks(
context, {pnet.NETWORK_TYPE: [n_const.TYPE_VLAN]})
vlan_net_ids = [vn['id'] for vn in vlan_nets]
# FIXME(ltomasbo): Once Bugzilla 2162756 is fixed the
# is_provider_network check should be removed
vlan_net_ids = [vn['id'] for vn in vlan_nets
if not utils.is_provider_network(vn)]
router_ports = self._ovn_client._plugin.get_ports(
context, {'network_id': vlan_net_ids,
'device_owner': n_const.ROUTER_PORT_OWNERS})
Expand Down
43 changes: 22 additions & 21 deletions neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,29 +1554,21 @@ def _gen_router_port_options(self, port, network=None):
if network is None:
network = self._plugin.get_network(admin_context,
port['network_id'])

# For VLAN type networks we need to set the
# "reside-on-redirect-chassis" option so the routing for this
# logical router port is centralized in the chassis hosting the
# distributed gateway port.
# https://github.com/openvswitch/ovs/commit/85706c34d53d4810f54bec1de662392a3c06a996
# FIXME(ltomasbo): Once Bugzilla 2162756 is fixed the
# is_provider_network check should be removed
if network.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN:
options[ovn_const.LRP_OPTIONS_RESIDE_REDIR_CH] = (
'false' if ovn_conf.is_ovn_distributed_floating_ip()
'false' if (ovn_conf.is_ovn_distributed_floating_ip() and
not utils.is_provider_network(network))
else 'true')

is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
'device_owner')

# NOTE(ltomasbo): For VLAN type networks connected through the gateway
# port there is a need to set the redirect-type option to bridge to
# ensure traffic is not centralized through the controller.
# For geneve based tenant networks it won't have any effect as it only
# applies to network with a localnet associated to it
if is_gw_port and ovn_conf.is_ovn_distributed_floating_ip():
options[ovn_const.LRP_OPTIONS_REDIRECT_TYPE] = (
ovn_const.BRIDGE_REDIRECT_TYPE)

if is_gw_port and ovn_conf.is_ovn_emit_need_to_frag_enabled():
try:
router_ports = self._get_router_ports(admin_context,
Expand Down Expand Up @@ -2377,17 +2369,26 @@ def _find_metadata_port_ip(self, context, subnet):
return fixed_ip['ip_address']

def create_metadata_port(self, context, network):
if ovn_conf.is_ovn_metadata_enabled():
metadata_port = self._find_metadata_port(context, network['id'])
if not metadata_port:
# Create a neutron port for DHCP/metadata services
port = {'port':
{'network_id': network['id'],
if not ovn_conf.is_ovn_metadata_enabled():
return

if self._find_metadata_port(context, network['id']):
return

# Create a neutron port for DHCP/metadata services
filters = {'network_id': [network['id']]}
subnets = self._plugin.get_subnets(context, filters=filters)
fixed_ips = [{'subnet_id': s['id']}
for s in subnets if s['enable_dhcp']]
port = {'port': {'network_id': network['id'],
'tenant_id': network['project_id'],
'device_owner': const.DEVICE_OWNER_DISTRIBUTED,
'device_id': 'ovnmeta-%s' % network['id']}}
# TODO(boden): rehome create_port into neutron-lib
p_utils.create_port(self._plugin, context, port)
'device_id': 'ovnmeta-%s' % network['id'],
'fixed_ips': fixed_ips,
}
}
# TODO(boden): rehome create_port into neutron-lib
p_utils.create_port(self._plugin, context, port)

def update_metadata_port(self, context, network_id, subnet=None):
"""Update metadata port.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2023 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.

from neutron_lib import constants

from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
from neutron.tests.functional import base


class TestOVNClient(base.TestOVNFunctionalBase):

def test_create_metadata_port(self):
def check_metadata_port(enable_dhcp):
ports = self.plugin.get_ports(
self.context, filters={'network_id': [network['id']]})
self.assertEqual(1, len(ports))
if enable_dhcp:
self.assertEqual(1, len(ports[0]['fixed_ips']))
else:
self.assertEqual(0, len(ports[0]['fixed_ips']))
return ports

ovn_config.cfg.CONF.set_override('ovn_metadata_enabled', True,
group='ovn')
ovn_client = self.mech_driver._ovn_client
for enable_dhcp in (True, False):
network_args = {'tenant_id': 'project_1',
'name': 'test_net_1',
'admin_state_up': True,
'shared': False,
'status': constants.NET_STATUS_ACTIVE}
network = self.plugin.create_network(self.context,
{'network': network_args})
subnet_args = {'tenant_id': 'project_1',
'name': 'test_snet_1',
'network_id': network['id'],
'ip_version': constants.IP_VERSION_4,
'cidr': '10.210.10.0/28',
'enable_dhcp': enable_dhcp,
'gateway_ip': constants.ATTR_NOT_SPECIFIED,
'allocation_pools': constants.ATTR_NOT_SPECIFIED,
'dns_nameservers': constants.ATTR_NOT_SPECIFIED,
'host_routes': constants.ATTR_NOT_SPECIFIED}
self.plugin.create_subnet(self.context, {'subnet': subnet_args})

# The metadata port has been created during the network creation.
ports = check_metadata_port(enable_dhcp)

# Force the deletion and creation the metadata port.
self.plugin.delete_port(self.context, ports[0]['id'])
ovn_client.create_metadata_port(self.context, network)
check_metadata_port(enable_dhcp)

# Call again the "create_metadata_port" method as is idempotent
# because it checks first if the metadata port exists.
ovn_client.create_metadata_port(self.context, network)
check_metadata_port(enable_dhcp)
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from neutron.agent.linux.openvswitch_firewall import exceptions
from neutron.agent.linux.openvswitch_firewall import firewall as ovsfw
from neutron.conf.agent import securitygroups_rpc
from neutron.conf.plugins.ml2.drivers import ovs_conf
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
Expand Down Expand Up @@ -514,6 +515,7 @@ def __init__(self, name, port, mac):
class TestOVSFirewallDriver(base.BaseTestCase):
def setUp(self):
super(TestOVSFirewallDriver, self).setUp()
ovs_conf.register_ovs_agent_opts(cfg=cfg.CONF)
mock_bridge = mock.patch.object(
ovs_lib, 'OVSBridge', autospec=True).start()
securitygroups_rpc.register_securitygroups_opts()
Expand Down Expand Up @@ -840,6 +842,26 @@ def test_initialize_port_flows_vlan_dvr_conntrack_direct_vlan(self):
return_value={"vlan1": "br-vlan1"}):
self.firewall.initialize_port_flows(port)

def test_initialize_port_flows_permitted_ethertypes(self):
self.firewall.permitted_ethertypes = ['0x1234', '0x5678']
port_dict = {'device': 'port-id',
'security_groups': [1]}
of_port = create_ofport(port_dict,
network_type=constants.TYPE_VLAN,
physical_network='vlan1')
self.firewall.sg_port_map.ports[of_port.id] = of_port
port = self.firewall.get_or_create_ofport(port_dict)
with mock.patch.object(self.firewall, '_add_flow') as mock_add_flow:
self.firewall.initialize_port_flows(port)

calls = [mock.call(table=ovs_consts.BASE_INGRESS_TABLE,
priority=100, dl_type='0x1234',
reg_port=1, actions='output:1'),
mock.call(table=ovs_consts.BASE_INGRESS_TABLE,
priority=100, dl_type='0x5678',
reg_port=1, actions='output:1')]
mock_add_flow.assert_has_calls(calls, any_order=True)

def test_delete_all_port_flows(self):
port_dict = {
'device': 'port-id',
Expand Down
9 changes: 0 additions & 9 deletions releasenotes/notes/bug-2003455-dff0d0f00b5a18e2.yaml

This file was deleted.