NSX-V3: Enhance VPNaaS related validations

a No-SNAT router subnets cannot overlap with VPN subnets becasue of the rotuer
advertisment.
This patch adds validations when changing the rotuer GW or addign an interface.

Also change the local endpoint port creation so this port will have a distingished
name and device id/owner and won't be queried by mistake.

Change-Id: I41faf97bae67ca85b38da3ade47894865eac8d51
This commit is contained in:
Adit Sarfaty 2018-03-21 11:47:34 +02:00
parent 6ea7d64683
commit 3fd27427ae
3 changed files with 78 additions and 13 deletions

View File

@ -3741,7 +3741,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
raise n_exc.InvalidInput(error_message=msg)
# VPNaaS need to be notified on router GW changes (there is
# currentlyno matching upstream registration for this)
# currently no matching upstream registration for this)
vpn_plugin = directory.get_plugin(plugin_const.VPN)
if vpn_plugin:
vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]

View File

@ -38,7 +38,7 @@ from vmware_nsxlib.v3 import vpn_ipsec
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
VPN_PORT_OWNER = constants.DEVICE_OWNER_NEUTRON_PREFIX + 'vpnservice'
VPN_PORT_OWNER = 'vpnservice'
class RouterWithSNAT(nexception.BadRequest):
@ -46,6 +46,16 @@ class RouterWithSNAT(nexception.BadRequest):
"SNAT")
class RouterWithOverlapNoSnat(nexception.BadRequest):
message = _("Router %(router_id)s has a subnet overlapping with a VPN "
"local subnet, and cannot disable SNAT")
class RouterOverlapping(nexception.BadRequest):
message = _("Router %(router_id)s interface is overlapping with a VPN "
"local subnet and cannot be added")
class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
def __init__(self, service_plugin):
@ -63,6 +73,10 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
events.AFTER_DELETE)
registry.subscribe(
self._verify_overlap_subnet, resources.ROUTER_INTERFACE,
events.BEFORE_CREATE)
@property
def l3_plugin(self):
return self._core_plugin
@ -364,7 +378,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
def _find_vpn_service_port(self, context, router_id):
"""Look for the neutron port created for the vpnservice of a router"""
filters = {'device_id': [router_id],
filters = {'device_id': ['router-' + router_id],
'device_owner': [VPN_PORT_OWNER]}
ports = self.l3_plugin.get_ports(context, filters=filters)
if ports:
@ -383,18 +397,68 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
if port:
self.l3_plugin.delete_port(ctx, port['id'])
def _check_subnets_overlap_with_all_conns(self, context, subnets):
# find all vpn services with connections
filters = {'status': [constants.ACTIVE]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context, filters=filters)
for conn in connections:
srv_id = conn.get('vpnservice_id')
srv = self.vpn_plugin._get_vpnservice(context, srv_id)
srv_subnet = self.l3_plugin.get_subnet(
context, srv['subnet_id'])
if netaddr.IPSet(subnets) & netaddr.IPSet([srv_subnet['cidr']]):
return False
return True
def _verify_overlap_subnet(self, resource, event, trigger, **kwargs):
"""Upon router interface creation validation overlapping with vpn"""
router_db = kwargs.get('router_db')
port = kwargs.get('port')
if not port or not router_db:
LOG.warning("NSX V3 VPNaaS ROUTER_INTERFACE BEFORE_CRAETE "
"callback didn't get all the relevant information")
return
if router_db.enable_snat:
# checking only no-snat routers
return
admin_con = n_context.get_admin_context()
subnet_id = port['fixed_ips'][0].get('subnet_id')
if subnet_id:
subnet = self._core_plugin.get_subnet(admin_con, subnet_id)
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, [subnet['cidr']]):
raise RouterOverlapping(router_id=kwargs.get('router_id'))
def validate_router_gw_info(self, context, router_id, gw_info):
"""Upon router gw update - verify no-snat"""
# ckeck if this router has a vpn service
# check if this router has a vpn service
admin_con = context.elevated()
filters = {'router_id': [router_id],
'status': [constants.ACTIVE]}
services = self.vpn_plugin.get_vpnservices(
context.elevated(), filters=filters)
services = self.vpn_plugin.get_vpnservices(admin_con, filters=filters)
if services:
# do not allow enable-snat
if (gw_info and
gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
raise RouterWithSNAT(router_id=router_id)
else:
# if this is a non-vpn router. if snat was disabled, should check
# there is no overlapping with vpn connections
if (gw_info and
not gw_info.get('enable_snat',
cfg.CONF.enable_snat_by_default)):
# get router subnets
subnets = self._core_plugin._find_router_subnets_cidrs(
context, router_id)
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, subnets):
raise RouterWithOverlapNoSnat(router_id=router_id)
def _get_session_rules(self, context, connection, vpnservice):
# TODO(asarfaty): support vpn-endpoint-groups too
@ -600,7 +664,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
def _create_vpn_service(self, tier0_uuid):
try:
service = self._nsx_vpn.service.create(
'Neutron VPN service for router ' + tier0_uuid,
'Neutron VPN service for T0 router ' + tier0_uuid,
tier0_uuid,
enabled=True,
ike_log_level=ipsec_utils.DEFAULT_LOG_LEVEL,
@ -658,13 +722,15 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
port = self._find_vpn_service_port(context, router_id)
if not port:
# create a new port, on the external network of the router
# Note(asarfaty): using a unique device owner and device id to
# make sure tis port will be ignored in certain queries
ext_net = vpnservice.router.gw_port['network_id']
port_data = {
'port': {
'network_id': ext_net,
'name': None,
'name': 'VPN local address port',
'admin_state_up': True,
'device_id': vpnservice.router['id'],
'device_id': 'router-' + vpnservice.router['id'],
'device_owner': VPN_PORT_OWNER,
'fixed_ips': constants.ATTR_NOT_SPECIFIED,
'mac_address': constants.ATTR_NOT_SPECIFIED,

View File

@ -14,6 +14,7 @@
# under the License.
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
from neutron_lib import constants
@ -235,7 +236,8 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
if (rtr['id'] != this_router and
rtr.get('external_gateway_info') and
not rtr['external_gateway_info'].get(
'enable_snat', True))]
'enable_snat',
cfg.CONF.enable_snat_by_default))]
for rtr in nosnat_routers:
if rtr['id'] == this_router:
continue
@ -271,9 +273,6 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
'conn': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
# TODO(asarfaty): also add this validation when adding an interface
# or no-snat to a router through the nsx-v3 plugin
def validate_ipsec_site_connection(self, context, ipsec_site_conn):
"""Called upon create/update of a connection"""