NSX|V3: Do not add SNAT rules if same address scope
In case the router GW and interface belong to the same address scope there is not need to add SNAT rules for this interface, as there are no overlapping addresses. For this end, we need to add specific SNAT rule for each of the router relevant subnets, instead of just 1 SNAT all rule. Those NAT rules will be deleted when the GW is removed, changed to no-SNAT or when an interface is removed. Depends-on: Id43a2ced7d6526f538f485f345c20ba44673c7b2 Change-Id: Ie528503ec77b397a19fdcf90cfced738fdfc7a22
This commit is contained in:
parent
2033fe37c1
commit
3887d898b4
@ -20,6 +20,7 @@ from neutron.db import address_scope_db
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import address_scope as ext_address_scope
|
||||
from neutron_lib.api.definitions import network as net_def
|
||||
from neutron_lib.api.definitions import port as port_def
|
||||
@ -126,3 +127,31 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
port_filters = {'device_id': [router_id],
|
||||
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
|
||||
return self.get_ports(context, filters=port_filters)
|
||||
|
||||
def _find_router_subnets_cidrs(self, context, router_id):
|
||||
"""Retrieve cidrs of subnets attached to the specified router."""
|
||||
subnets = self._find_router_subnets_and_cidrs(context, router_id)
|
||||
return [subnet['cidr'] for subnet in subnets]
|
||||
|
||||
def _get_port_by_device_id(self, context, device_id, device_owner):
|
||||
"""Retrieve ports associated with a specific device id.
|
||||
|
||||
Used for retrieving all neutron ports attached to a given router.
|
||||
"""
|
||||
port_qry = context.session.query(models_v2.Port)
|
||||
return port_qry.filter_by(
|
||||
device_id=device_id,
|
||||
device_owner=device_owner,).all()
|
||||
|
||||
def _find_router_subnets_and_cidrs(self, context, router_id):
|
||||
"""Retrieve subnets attached to the specified router."""
|
||||
ports = self._get_port_by_device_id(context, router_id,
|
||||
l3_db.DEVICE_OWNER_ROUTER_INTF)
|
||||
# No need to check for overlapping CIDRs
|
||||
subnets = []
|
||||
for port in ports:
|
||||
for ip in port.get('fixed_ips', []):
|
||||
subnet_qry = context.session.query(models_v2.Subnet)
|
||||
subnet = subnet_qry.filter_by(id=ip.subnet_id).one()
|
||||
subnets.append({'id': subnet.id, 'cidr': subnet.cidr})
|
||||
return subnets
|
||||
|
@ -3243,34 +3243,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
address_groups.append(address_group)
|
||||
return address_groups
|
||||
|
||||
def _get_port_by_device_id(self, context, device_id, device_owner):
|
||||
"""Retrieve ports associated with a specific device id.
|
||||
|
||||
Used for retrieving all neutron ports attached to a given router.
|
||||
"""
|
||||
port_qry = context.session.query(models_v2.Port)
|
||||
return port_qry.filter_by(
|
||||
device_id=device_id,
|
||||
device_owner=device_owner,).all()
|
||||
|
||||
def _find_router_subnets_cidrs(self, context, router_id):
|
||||
"""Retrieve cidrs of subnets attached to the specified router."""
|
||||
subnets = self._find_router_subnets_and_cidrs(context, router_id)
|
||||
return [subnet['cidr'] for subnet in subnets]
|
||||
|
||||
def _find_router_subnets_and_cidrs(self, context, router_id):
|
||||
"""Retrieve subnets attached to the specified router."""
|
||||
ports = self._get_port_by_device_id(context, router_id,
|
||||
l3_db.DEVICE_OWNER_ROUTER_INTF)
|
||||
# No need to check for overlapping CIDRs
|
||||
subnets = []
|
||||
for port in ports:
|
||||
for ip in port.get('fixed_ips', []):
|
||||
subnet_qry = context.session.query(models_v2.Subnet)
|
||||
subnet = subnet_qry.filter_by(id=ip.subnet_id).one()
|
||||
subnets.append({'id': subnet.id, 'cidr': subnet.cidr})
|
||||
return subnets
|
||||
|
||||
def _get_nat_rules(self, context, router):
|
||||
fip_qry = context.session.query(l3_db_models.FloatingIP)
|
||||
fip_db = fip_qry.filter_by(router_id=router['id']).all()
|
||||
|
@ -2737,7 +2737,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
# TODO(berlin): revocate bgp announce on org tier0 router
|
||||
pass
|
||||
if remove_snat_rules:
|
||||
self._routerlib.delete_gw_snat_rule(nsx_router_id, orgaddr)
|
||||
self._routerlib.delete_gw_snat_rules(nsx_router_id, orgaddr)
|
||||
if remove_router_link_port:
|
||||
self._routerlib.remove_router_link_port(
|
||||
nsx_router_id, org_tier0_uuid)
|
||||
@ -2752,8 +2752,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
self._routerlib.add_router_link_port(nsx_router_id, new_tier0_uuid,
|
||||
tags=tags)
|
||||
if add_snat_rules:
|
||||
self._routerlib.add_gw_snat_rule(nsx_router_id, newaddr,
|
||||
bypass_firewall=False)
|
||||
# Add SNAT rules for all the subnets which are in different scope
|
||||
# than the gw
|
||||
gw_address_scope = self._get_network_address_scope(
|
||||
context, router.gw_port.network_id)
|
||||
subnets = self._find_router_subnets_and_cidrs(context.elevated(),
|
||||
router_id)
|
||||
for subnet in subnets:
|
||||
self._add_subnet_snat_rule(context, router_id, nsx_router_id,
|
||||
subnet, gw_address_scope, newaddr)
|
||||
if bgp_announce:
|
||||
# TODO(berlin): bgp announce on new tier0 router
|
||||
pass
|
||||
@ -2762,6 +2769,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
advertise_route_nat_flag,
|
||||
advertise_route_connected_flag)
|
||||
|
||||
def _add_subnet_snat_rule(self, context, router_id, nsx_router_id, subnet,
|
||||
gw_address_scope, gw_ip):
|
||||
# if the subnets address scope is the same as the gateways:
|
||||
# no need for SNAT
|
||||
if gw_address_scope:
|
||||
subnet_address_scope = self._get_subnet_address_scope(
|
||||
context, subnet['id'])
|
||||
if (gw_address_scope == subnet_address_scope):
|
||||
LOG.info("No need for SNAT rule for router %(router)s "
|
||||
"and subnet %(subnet)s because they use the "
|
||||
"same address scope %(addr_scope)s.",
|
||||
{'router': router_id,
|
||||
'subnet': subnet['id'],
|
||||
'addr_scope': gw_address_scope})
|
||||
return
|
||||
|
||||
self._routerlib.add_gw_snat_rule(nsx_router_id, gw_ip,
|
||||
source_net=subnet['cidr'],
|
||||
bypass_firewall=False)
|
||||
|
||||
def _process_extra_attr_router_create(self, context, router_db, r):
|
||||
for extra_attr in l3_attrs_db.get_attr_info().keys():
|
||||
if extra_attr in r:
|
||||
@ -3070,11 +3097,12 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
nsx_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
|
||||
context.session, port['id'])
|
||||
router_db = self._get_router(context, router_id)
|
||||
gw_network_id = (router_db.gw_port.network_id if router_db.gw_port
|
||||
else None)
|
||||
|
||||
# If it is a no-snat router, interface address scope must be the
|
||||
# same as the gateways
|
||||
if not router_db.enable_snat:
|
||||
gw_network_id = router_db.gw_port.network_id
|
||||
if not router_db.enable_snat and gw_network_id:
|
||||
self._validate_address_scope_for_router_interface(
|
||||
context, router_id, gw_network_id, subnet['id'])
|
||||
|
||||
@ -3106,6 +3134,15 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
# its DHCP port), by creating it if needed.
|
||||
nsx_rpc.handle_router_metadata_access(self, context, router_id,
|
||||
interface=info)
|
||||
|
||||
# add the SNAT rule for this interface
|
||||
if (router_db.enable_snat and gw_network_id and
|
||||
router_db.gw_port.get('fixed_ips')):
|
||||
gw_ip = router_db.gw_port['fixed_ips'][0]['ip_address']
|
||||
gw_address_scope = self._get_network_address_scope(
|
||||
context, gw_network_id)
|
||||
self._add_subnet_snat_rule(context, router_id, nsx_router_id,
|
||||
subnet, gw_address_scope, gw_ip)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error("Neutron failed to add_router_interface on "
|
||||
@ -3176,6 +3213,14 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
else:
|
||||
self.nsxlib.logical_router_port.delete_by_lswitch_id(
|
||||
nsx_net_id)
|
||||
# try to delete the SNAT rule of this subnet
|
||||
if (router_db.gw_port and router_db.enable_snat and
|
||||
router_db.gw_port.get('fixed_ips')):
|
||||
gw_ip = router_db.gw_port['fixed_ips'][0]['ip_address']
|
||||
self._routerlib.delete_gw_snat_rule_by_source(
|
||||
nsx_router_id, gw_ip, subnet['cidr'],
|
||||
skip_not_found=True)
|
||||
|
||||
except nsx_lib_exc.ResourceNotFound:
|
||||
LOG.error("router port on router %(router_id)s for net "
|
||||
"%(net_id)s not found at the backend",
|
||||
|
@ -891,6 +891,92 @@ class TestL3NatTestCase(L3NatTest,
|
||||
int_subnet['subnet']['id'],
|
||||
None)
|
||||
|
||||
def test_router_address_scope_snat_rules(self):
|
||||
"""Test that if the router interface had the same address scope
|
||||
as the gateway - snat rule is not added.
|
||||
"""
|
||||
# create an external network on one address scope
|
||||
with self.address_scope(name='as1') as addr_scope, \
|
||||
self.network() as ext_net:
|
||||
self._set_net_external(ext_net['network']['id'])
|
||||
as_id = addr_scope['address_scope']['id']
|
||||
subnet = netaddr.IPNetwork('10.10.10.0/21')
|
||||
subnetpool = self._test_create_subnetpool(
|
||||
[subnet.cidr], name='sp1',
|
||||
min_prefixlen='24', address_scope_id=as_id)
|
||||
subnetpool_id = subnetpool['subnetpool']['id']
|
||||
data = {'subnet': {
|
||||
'network_id': ext_net['network']['id'],
|
||||
'subnetpool_id': subnetpool_id,
|
||||
'ip_version': 4,
|
||||
'enable_dhcp': False,
|
||||
'tenant_id': ext_net['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
ext_subnet = self.deserialize(self.fmt, req.get_response(self.api))
|
||||
|
||||
# create a regular network on the same address scope
|
||||
with self.network() as net:
|
||||
data = {'subnet': {
|
||||
'network_id': net['network']['id'],
|
||||
'subnetpool_id': subnetpool_id,
|
||||
'ip_version': 4,
|
||||
'tenant_id': net['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
int_subnet = self.deserialize(
|
||||
self.fmt, req.get_response(self.api))
|
||||
|
||||
# create a router with this gateway
|
||||
with self.router() as r:
|
||||
self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
ext_subnet['subnet']['network_id'])
|
||||
|
||||
with mock.patch("vmware_nsxlib.v3.router.RouterLib."
|
||||
"add_gw_snat_rule") as add_nat:
|
||||
# Add the interface
|
||||
self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
int_subnet['subnet']['id'],
|
||||
None)
|
||||
# make sure snat rules are not added
|
||||
add_nat.assert_not_called()
|
||||
|
||||
# create a regular network on a different address scope
|
||||
with self.address_scope(name='as2') as addr_scope2, \
|
||||
self.network() as net:
|
||||
as_id2 = addr_scope2['address_scope']['id']
|
||||
subnet2 = netaddr.IPNetwork('20.10.10.0/24')
|
||||
subnetpool2 = self._test_create_subnetpool(
|
||||
[subnet2.cidr], name='sp2',
|
||||
min_prefixlen='24', address_scope_id=as_id2)
|
||||
subnetpool_id2 = subnetpool2['subnetpool']['id']
|
||||
data = {'subnet': {
|
||||
'network_id': net['network']['id'],
|
||||
'subnetpool_id': subnetpool_id2,
|
||||
'ip_version': 4,
|
||||
'tenant_id': net['network']['tenant_id']}}
|
||||
req = self.new_create_request('subnets', data)
|
||||
int_subnet = self.deserialize(
|
||||
self.fmt, req.get_response(self.api))
|
||||
|
||||
# create a router with this gateway
|
||||
with self.router() as r:
|
||||
self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
ext_subnet['subnet']['network_id'])
|
||||
|
||||
with mock.patch("vmware_nsxlib.v3.router.RouterLib."
|
||||
"add_gw_snat_rule") as add_nat:
|
||||
# Add the interface
|
||||
self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
int_subnet['subnet']['id'],
|
||||
None)
|
||||
# make sure snat rules are added
|
||||
add_nat.assert_called_once()
|
||||
|
||||
|
||||
class ExtGwModeTestCase(test_ext_gw_mode.ExtGwModeIntTestCase,
|
||||
L3NatTest):
|
||||
|
Loading…
x
Reference in New Issue
Block a user