Skip to content

Commit 0e2687c

Browse files
authored
Merge pull request #187 from stackhpc/backport_ovn_snat
2 parents 6a46bc0 + dcef25c commit 0e2687c

File tree

8 files changed

+247
-110
lines changed

8 files changed

+247
-110
lines changed

neutron/common/ovn/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,6 @@
463463
portbindings.VNIC_BAREMETAL,
464464
portbindings.VNIC_VIRTIO_FORWARDER,
465465
]
466+
467+
# OVN default SNAT CIDR
468+
OVN_DEFAULT_SNAT_CIDR = '0.0.0.0/0'

neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@
221221
default=0,
222222
help=_('The number of seconds to keep MAC_Binding entries in '
223223
'the OVN DB. 0 to disable aging.')),
224+
cfg.BoolOpt('ovn_router_indirect_snat',
225+
default=False,
226+
help=_('Whether to configure SNAT for all nested subnets '
227+
'connected to the router through any other routers, '
228+
'similar to the default ML2/OVS behavior. Defaults to '
229+
'"False".')),
224230
]
225231

226232
nb_global_opts = [
@@ -376,3 +382,7 @@ def get_fdb_removal_limit():
376382
def get_ovn_mac_binding_age_threshold():
377383
# This value is always stored as a string in the OVN DB
378384
return str(cfg.CONF.ovn.mac_binding_age_threshold)
385+
386+
387+
def is_ovn_router_indirect_snat_enabled():
388+
return cfg.CONF.ovn.ovn_router_indirect_snat

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 90 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import collections
1717
import copy
1818
import datetime
19+
import functools
1920

2021
import netaddr
2122
from neutron_lib.api.definitions import l3
@@ -51,6 +52,8 @@
5152
from neutron.common import utils as common_utils
5253
from neutron.conf.agent import ovs_conf
5354
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
55+
from neutron.conf.plugins.ml2.drivers.ovn.ovn_conf \
56+
import is_ovn_router_indirect_snat_enabled as is_nested_snat
5457
from neutron.db import ovn_revision_numbers_db as db_rev
5558
from neutron.db import segments_db
5659
from neutron.objects import router
@@ -64,6 +67,10 @@
6467
LOG = log.getLogger(__name__)
6568

6669

70+
def _has_separate_snat_per_subnet(router):
71+
return utils.is_snat_enabled(router) and not is_nested_snat()
72+
73+
6774
OvnPortInfo = collections.namedtuple(
6875
"OvnPortInfo",
6976
[
@@ -1219,23 +1226,23 @@ def _get_gw_info(self, context, port_dict):
12191226
else const.IPv6_ANY))
12201227
return gateways_info
12211228

1222-
def _delete_router_ext_gw(self, router, networks, txn):
1229+
def _delete_router_ext_gw(self, router_id, txn):
12231230
context = n_context.get_admin_context()
1224-
if not networks:
1225-
networks = []
1226-
router_id = router['id']
1231+
cidrs = self._get_snat_cidrs_for_external_router(context, router_id)
12271232
gw_lrouter_name = utils.ovn_name(router_id)
12281233
deleted_ports = []
12291234
for gw_port in self._get_router_gw_ports(context, router_id):
12301235
for gw_info in self._get_gw_info(context, gw_port):
1231-
if gw_info.ip_version == const.IP_VERSION_4:
1232-
for network in networks:
1233-
txn.add(self._nb_idl.delete_nat_rule_in_lrouter(
1234-
gw_lrouter_name, type='snat', logical_ip=network,
1235-
external_ip=gw_info.router_ip))
12361236
txn.add(self._nb_idl.delete_static_route(
12371237
gw_lrouter_name, ip_prefix=gw_info.ip_prefix,
12381238
nexthop=gw_info.gateway_ip))
1239+
if gw_info.ip_version != const.IP_VERSION_4:
1240+
continue
1241+
for cidr in cidrs:
1242+
txn.add(self._nb_idl.delete_nat_rule_in_lrouter(
1243+
gw_lrouter_name, type='snat',
1244+
external_ip=gw_info.router_ip,
1245+
logical_ip=cidr))
12391246
txn.add(self._nb_idl.delete_lrouter_port(
12401247
utils.ovn_lrouter_port_name(gw_port['id']),
12411248
gw_lrouter_name))
@@ -1281,7 +1288,7 @@ def _get_nets_and_ipv6_ra_confs_for_router_port(self, context, port):
12811288

12821289
return list(networks), ipv6_ra_configs
12831290

1284-
def _add_router_ext_gw(self, context, router, networks, txn):
1291+
def _add_router_ext_gw(self, context, router, txn):
12851292
lrouter_name = utils.ovn_name(router['id'])
12861293
router_default_route_ecmp_enabled = router.get(
12871294
'enable_default_route_ecmp', False)
@@ -1319,9 +1326,9 @@ def _add_router_ext_gw(self, context, router, networks, txn):
13191326
maintain_bfd=router_default_route_bfd_enabled,
13201327
**columns))
13211328

1322-
# 3. Add snat rules for tenant networks in lrouter if snat is enabled
1323-
if utils.is_snat_enabled(router) and networks:
1324-
self.update_nat_rules(router, networks, enable_snat=True, txn=txn)
1329+
# 3. Add necessary snat rule(s) in lrouter if snat is enabled
1330+
if utils.is_snat_enabled(router):
1331+
self.update_nat_rules(router['id'], enable_snat=True, txn=txn)
13251332
return added_ports
13261333

13271334
def _check_external_ips_changed(self, ovn_snats,
@@ -1423,17 +1430,20 @@ def _get_v4_network_for_router_port(self, context, port):
14231430
cidr = subnet['cidr']
14241431
return cidr
14251432

1426-
def _get_v4_network_of_all_router_ports(self, context, router_id,
1427-
ports=None):
1433+
def _get_v4_network_of_all_router_ports(self, context, router_id):
14281434
networks = []
1429-
ports = ports or self._get_router_ports(context, router_id)
1430-
for port in ports:
1435+
for port in self._get_router_ports(context, router_id):
14311436
network = self._get_v4_network_for_router_port(context, port)
14321437
if network:
14331438
networks.append(network)
1434-
14351439
return networks
14361440

1441+
def _get_snat_cidrs_for_external_router(self, context, router_id):
1442+
if is_nested_snat():
1443+
return [ovn_const.OVN_DEFAULT_SNAT_CIDR]
1444+
# nat rule per attached subnet per external ip
1445+
return self._get_v4_network_of_all_router_ports(context, router_id)
1446+
14371447
def _gen_router_ext_ids(self, router):
14381448
return {
14391449
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
@@ -1462,12 +1472,9 @@ def create_router(self, context, router, add_external_gateway=True):
14621472
# by the ovn_db_sync.py script, remove it after the database
14631473
# synchronization work
14641474
if add_external_gateway:
1465-
networks = self._get_v4_network_of_all_router_ports(
1466-
context, router['id'])
1467-
if (router.get(l3_ext_gw_multihoming.EXTERNAL_GATEWAYS) and
1468-
networks is not None):
1475+
if router.get(l3_ext_gw_multihoming.EXTERNAL_GATEWAYS):
14691476
added_gw_ports = self._add_router_ext_gw(
1470-
context, router, networks, txn)
1477+
context, router, txn)
14711478

14721479
self._qos_driver.create_router(txn, router)
14731480

@@ -1495,7 +1502,6 @@ def update_router(self, context, new_router, router_object=None):
14951502
l3_ext_gw_multihoming.EXTERNAL_GATEWAYS)
14961503

14971504
ovn_snats = utils.get_lrouter_snats(ovn_router)
1498-
networks = self._get_v4_network_of_all_router_ports(context, router_id)
14991505
try:
15001506
check_rev_cmd = self._nb_idl.check_revision_number(
15011507
router_name, new_router, ovn_const.TYPE_ROUTERS)
@@ -1504,13 +1510,13 @@ def update_router(self, context, new_router, router_object=None):
15041510
if gateway_new and not gateway_old:
15051511
# Route gateway is set
15061512
added_gw_ports = self._add_router_ext_gw(
1507-
context, new_router, networks, txn)
1513+
context, new_router, txn)
15081514
elif gateway_old and not gateway_new:
15091515
# router gateway is removed
15101516
txn.add(self._nb_idl.delete_lrouter_ext_gw(router_name))
15111517
if router_object:
15121518
deleted_gw_port_ids = self._delete_router_ext_gw(
1513-
router_object, networks, txn)
1519+
router_object['id'], txn)
15141520
elif gateway_new and gateway_old:
15151521
# Check if external gateway has changed, if yes, delete
15161522
# the old gateway and add the new gateway
@@ -1528,16 +1534,16 @@ def update_router(self, context, new_router, router_object=None):
15281534
router_name))
15291535
if router_object:
15301536
deleted_gw_port_ids = self._delete_router_ext_gw(
1531-
router_object, networks, txn)
1537+
router_object['id'], txn)
15321538
added_gw_ports = self._add_router_ext_gw(
1533-
context, new_router, networks, txn)
1539+
context, new_router, txn)
15341540
else:
15351541
# Check if snat has been enabled/disabled and update
15361542
new_snat_state = utils.is_snat_enabled(new_router)
1537-
if bool(ovn_snats) != new_snat_state and networks:
1543+
if bool(ovn_snats) != new_snat_state:
15381544
self.update_nat_rules(
1539-
new_router, networks,
1540-
enable_snat=new_snat_state, txn=txn)
1545+
new_router['id'], enable_snat=new_snat_state,
1546+
txn=txn)
15411547

15421548
update = {'external_ids': self._gen_router_ext_ids(new_router)}
15431549
update['enabled'] = new_router.get('admin_state_up') or False
@@ -1785,26 +1791,26 @@ def create_router_port(self, context, router_id, router_interface):
17851791

17861792
gw_ports = self._get_router_gw_ports(context, router_id)
17871793
if gw_ports:
1788-
cidr = None
1789-
for fixed_ip in port['fixed_ips']:
1790-
subnet = self._plugin.get_subnet(context,
1791-
fixed_ip['subnet_id'])
1792-
if multi_prefix:
1793-
if 'subnet_id' in router_interface:
1794-
if subnet['id'] != router_interface['subnet_id']:
1795-
continue
1796-
if subnet['ip_version'] == const.IP_VERSION_4:
1797-
cidr = subnet['cidr']
1798-
17991794
if ovn_conf.is_ovn_emit_need_to_frag_enabled():
18001795
for gw_port in gw_ports:
18011796
provider_net = self._plugin.get_network(
18021797
context, gw_port['network_id'])
18031798
self.set_gateway_mtu(context, provider_net)
18041799

1805-
if utils.is_snat_enabled(router) and cidr:
1806-
self.update_nat_rules(router, networks=[cidr],
1807-
enable_snat=True, txn=txn)
1800+
if _has_separate_snat_per_subnet(router):
1801+
for fixed_ip in port['fixed_ips']:
1802+
subnet = self._plugin.get_subnet(
1803+
context, fixed_ip['subnet_id'])
1804+
if (multi_prefix and
1805+
'subnet_id' in router_interface and
1806+
subnet['id'] != router_interface['subnet_id']):
1807+
continue
1808+
if subnet['ip_version'] == const.IP_VERSION_4:
1809+
self.update_nat_rules(
1810+
router['id'], cidrs=[subnet['cidr']],
1811+
enable_snat=True, txn=txn)
1812+
break # TODO(ihar): handle multiple ipv4 ips?
1813+
18081814
if ovn_conf.is_ovn_distributed_floating_ip():
18091815
router_gw_ports = self._get_router_gw_ports(context,
18101816
router_id)
@@ -1937,19 +1943,17 @@ def delete_router_port(self, context, port_id, subnet_ids=None):
19371943
context, gw_port['network_id'])
19381944
self.set_gateway_mtu(context, provider_net, txn=txn)
19391945

1940-
cidr = None
1941-
for sid in subnet_ids:
1942-
try:
1943-
subnet = self._plugin.get_subnet(context, sid)
1944-
except n_exc.SubnetNotFound:
1945-
continue
1946-
if subnet['ip_version'] == const.IP_VERSION_4:
1947-
cidr = subnet['cidr']
1948-
break
1949-
1950-
if utils.is_snat_enabled(router) and cidr:
1951-
self.update_nat_rules(
1952-
router, networks=[cidr], enable_snat=False, txn=txn)
1946+
if _has_separate_snat_per_subnet(router):
1947+
for sid in subnet_ids:
1948+
try:
1949+
subnet = self._plugin.get_subnet(context, sid)
1950+
except n_exc.SubnetNotFound:
1951+
continue
1952+
if subnet['ip_version'] == const.IP_VERSION_4:
1953+
self.update_nat_rules(
1954+
router['id'], cidrs=[subnet['cidr']],
1955+
enable_snat=False, txn=txn)
1956+
break # TODO(ihar): handle multiple ipv4 ips?
19531957

19541958
if ovn_conf.is_ovn_distributed_floating_ip():
19551959
router_gw_ports = self._get_router_gw_ports(context, router_id)
@@ -1968,20 +1972,35 @@ def delete_router_port(self, context, port_id, subnet_ids=None):
19681972
db_rev.bump_revision(
19691973
context, port, ovn_const.TYPE_ROUTER_PORTS)
19701974

1971-
def update_nat_rules(self, router, networks, enable_snat, txn=None):
1972-
"""Update the NAT rules in a logical router."""
1975+
def _iter_ipv4_gw_addrs(self, context, router_id):
1976+
yield from (
1977+
gw_info.router_ip
1978+
for gw_port in self._get_router_gw_ports(context, router_id)
1979+
for gw_info in self._get_gw_info(context, gw_port)
1980+
if gw_info.ip_version != const.IP_VERSION_6
1981+
)
1982+
1983+
def update_nat_rules(self, router_id, enable_snat, cidrs=None, txn=None):
1984+
if enable_snat:
1985+
idl_func = self._nb_idl.add_nat_rule_in_lrouter
1986+
else:
1987+
idl_func = self._nb_idl.delete_nat_rule_in_lrouter
1988+
func = functools.partial(
1989+
idl_func, utils.ovn_name(router_id), type='snat')
1990+
19731991
context = n_context.get_admin_context()
1974-
func = (self._nb_idl.add_nat_rule_in_lrouter if enable_snat else
1975-
self._nb_idl.delete_nat_rule_in_lrouter)
1976-
gw_lrouter_name = utils.ovn_name(router['id'])
1977-
# Update NAT rules only for IPv4 subnets
1978-
commands = [func(gw_lrouter_name, type='snat', logical_ip=network,
1979-
external_ip=gw_info.router_ip)
1980-
for gw_port in self._get_router_gw_ports(context,
1981-
router['id'])
1982-
for gw_info in self._get_gw_info(context, gw_port)
1983-
if gw_info.ip_version != const.IP_VERSION_6
1984-
for network in networks]
1992+
cidrs = (
1993+
cidrs or
1994+
self._get_snat_cidrs_for_external_router(context, router_id)
1995+
)
1996+
commands = [
1997+
func(logical_ip=cidr, external_ip=router_ip)
1998+
for router_ip in self._iter_ipv4_gw_addrs(context, router_id)
1999+
for cidr in cidrs
2000+
]
2001+
if not commands:
2002+
return
2003+
19852004
self._transaction(commands, txn=txn)
19862005

19872006
def create_provnet_port(self, network_id, segment, txn=None):

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -540,12 +540,12 @@ def sync_routers_and_rports(self, ctx):
540540
if gw_info.ip_version == constants.IP_VERSION_6:
541541
continue
542542
if gw_info.router_ip and utils.is_snat_enabled(router):
543-
networks = self._ovn_client.\
544-
_get_v4_network_of_all_router_ports(
545-
ctx, router['id'])
546-
for network in networks:
543+
cidrs = self._ovn_client.\
544+
_get_snat_cidrs_for_external_router(ctx,
545+
router['id'])
546+
for cidr in cidrs:
547547
db_extends[router['id']]['snats'].append({
548-
'logical_ip': network,
548+
'logical_ip': cidr,
549549
'external_ip': gw_info.router_ip,
550550
'type': 'snat'})
551551

0 commit comments

Comments
 (0)