Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2017 OpenStack Foundation
#
# 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.
#

"""add NAT support to IPSec VPN

Revision ID: 44e812868e6f
Revises: 1ec373736f8b
Create Date: 2017-10-31 16:06:24.427885

"""

# revision identifiers, used by Alembic.
revision = '44e812868e6f'
down_revision = '1ec373736f8b'

from alembic import op
import sqlalchemy as sa



def upgrade():
op.add_column(
'ipsec_site_connections',
sa.Column('local_cidr', sa.String(length=32), nullable=True)
)


def downgrade():
op.drop_column('ipsec_site_connections', 'local_cidr')
2 changes: 1 addition & 1 deletion neutron/db/migration/alembic_migrations/versions/HEAD
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1ec373736f8b
44e812868e6f
18 changes: 9 additions & 9 deletions neutron/db/vpn/vpn_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.

import netaddr
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import exc
Expand Down Expand Up @@ -110,6 +109,7 @@ class IPsecSiteConnection(model_base.BASEV2,
description = sa.Column(sa.String(255))
peer_address = sa.Column(sa.String(255), nullable=False)
peer_id = sa.Column(sa.String(255), nullable=False)
local_cidr = sa.Column(sa.String(32))
route_mode = sa.Column(sa.String(8), nullable=False)
mtu = sa.Column(sa.Integer, nullable=False)
initiator = sa.Column(sa.Enum("bi-directional", "response-only",
Expand Down Expand Up @@ -248,6 +248,7 @@ def _make_ipsec_site_connection_dict(self, ipsec_site_conn, fields=None):
'description': ipsec_site_conn['description'],
'peer_address': ipsec_site_conn['peer_address'],
'peer_id': ipsec_site_conn['peer_id'],
'local_cidr': ipsec_site_conn['local_cidr'],
'route_mode': ipsec_site_conn['route_mode'],
'mtu': ipsec_site_conn['mtu'],
'auth_mode': ipsec_site_conn['auth_mode'],
Expand All @@ -269,11 +270,9 @@ def _make_ipsec_site_connection_dict(self, ipsec_site_conn, fields=None):

return self._fields(res, fields)

def _get_subnet_ip_version(self, context, vpnservice_id):
def _get_subnet_cidr(self, context, vpnservice_id):
vpn_service_db = self._get_vpnservice(context, vpnservice_id)
subnet = vpn_service_db.subnet['cidr']
ip_version = netaddr.IPNetwork(subnet).version
return ip_version
return vpn_service_db.subnet['cidr']

def create_ipsec_site_connection(self, context, ipsec_site_connection,
validator=None):
Expand All @@ -293,17 +292,18 @@ def create_ipsec_site_connection(self, context, ipsec_site_connection,
IPsecPolicy,
ipsec_sitecon['ipsecpolicy_id'])
vpnservice_id = ipsec_sitecon['vpnservice_id']
ip_version = self._get_subnet_ip_version(context, vpnservice_id)
subnet_cidr = self._get_subnet_cidr(context, vpnservice_id)
validator.validate_ipsec_site_connection(context,
ipsec_sitecon,
ip_version)
subnet_cidr)
ipsec_site_conn_db = IPsecSiteConnection(
id=uuidutils.generate_uuid(),
tenant_id=tenant_id,
name=ipsec_sitecon['name'],
description=ipsec_sitecon['description'],
peer_address=ipsec_sitecon['peer_address'],
peer_id=ipsec_sitecon['peer_id'],
local_cidr=ipsec_sitecon['local_cidr'],
route_mode='static',
mtu=ipsec_sitecon['mtu'],
auth_mode='psk',
Expand Down Expand Up @@ -339,13 +339,13 @@ def update_ipsec_site_connection(
IPsecSiteConnection,
ipsec_site_conn_id)
vpnservice_id = ipsec_site_conn_db['vpnservice_id']
ip_version = self._get_subnet_ip_version(context, vpnservice_id)
subnet_cidr = self._get_subnet_cidr(context, vpnservice_id)
validator.assign_sensible_ipsec_sitecon_defaults(
ipsec_sitecon, ipsec_site_conn_db)
validator.validate_ipsec_site_connection(
context,
ipsec_sitecon,
ip_version)
subnet_cidr)
self.assert_update_allowed(ipsec_site_conn_db)

if "peer_cidrs" in ipsec_sitecon:
Expand Down
16 changes: 14 additions & 2 deletions neutron/db/vpn/vpn_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.

import netaddr

from neutron.db import l3_db
from neutron.extensions import vpnaas
from neutron import manager
Expand Down Expand Up @@ -52,6 +54,14 @@ def _check_mtu(self, context, mtu, ip_version):
raise vpnaas.IPsecSiteConnectionMtuError(mtu=mtu,
version=ip_version)

def _check_local_cidr(self, ipsec_sitecon, subnet):
local_cidr = ipsec_sitecon.get('local_cidr')
if local_cidr:
if netaddr.IPNetwork(local_cidr).prefixlen != subnet.prefixlen:
raise vpnaas.IPsecSiteConnectionLocalCIDRError(
local_cidr=local_cidr,
subnet_cidr='%s' % subnet.cidr)

def assign_sensible_ipsec_sitecon_defaults(self, ipsec_sitecon,
prev_conn=None):
"""Provide defaults for optional items, if missing.
Expand All @@ -73,12 +83,14 @@ def assign_sensible_ipsec_sitecon_defaults(self, ipsec_sitecon,
prev_conn['dpd_timeout'])

def validate_ipsec_site_connection(self, context, ipsec_sitecon,
ip_version):
subnet_cidr):
"""Reference implementation of validation for IPSec connection."""
subnet = netaddr.IPNetwork(subnet_cidr)
self._check_dpd(ipsec_sitecon)
self._check_local_cidr(ipsec_sitecon, subnet)
mtu = ipsec_sitecon.get('mtu')
if mtu:
self._check_mtu(context, mtu, ip_version)
self._check_mtu(context, mtu, subnet.version)

def _check_router(self, context, router_id):
router = self.l3_plugin.get_router(context, router_id)
Expand Down
8 changes: 8 additions & 0 deletions neutron/extensions/vpnaas.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class IPsecSiteConnectionMtuError(qexception.InvalidInput):
"for ipv%(version)s")


class IPsecSiteConnectionLocalCIDRError(qexception.InvalidInput):
message = _("ipsec_site_connection prefixlens of local CIDR %(local_cidr)s "
"and subnet CIDR %(subnet_cidr)s are not equal")


class IKEPolicyNotFound(qexception.NotFound):
message = _("IKEPolicy %(ikepolicy_id)s could not be found")

Expand Down Expand Up @@ -179,6 +184,9 @@ class PPTPUsernameAlreadyExists(qexception.BadRequest):
'convert_to': attr.convert_to_list,
'validate': {'type:subnet_list': None},
'is_visible': True},
'local_cidr': {'allow_post': True, 'allow_put': True,
'validate': {'type:subnet_or_none': None},
'is_visible': True, 'default': None},
'route_mode': {'allow_post': False, 'allow_put': False,
'default': 'static',
'is_visible': True},
Expand Down
12 changes: 9 additions & 3 deletions neutron/services/vpn/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def get_namespace(self, router_id):
return
return router_info.ns_name

def add_nat_rule(self, router_id, chain, rule, top=False):
def add_nat_rule(self, router_id, chain, rule, top=False, tag=None):
"""Add nat rule in namespace.

:param router_id: router_id
Expand All @@ -81,9 +81,9 @@ def add_nat_rule(self, router_id, chain, rule, top=False):
if not router_info:
return
router_info.iptables_manager.ipv4['nat'].add_rule(
chain, rule, top=top)
chain, rule, top=top, tag=tag)

def remove_nat_rule(self, router_id, chain, rule, top=False):
def remove_nat_rule(self, router_id, chain, rule, top=False, _tag=None):
"""Remove nat rule in namespace.

:param router_id: router_id
Expand All @@ -98,6 +98,12 @@ def remove_nat_rule(self, router_id, chain, rule, top=False):
router_info.iptables_manager.ipv4['nat'].remove_rule(
chain, rule, top=top)

def remove_nat_rules_by_tag(self, router_id, tag):
router_info = self.router_info.get(router_id)
if not router_info:
return
router_info.iptables_manager.ipv4['nat'].clear_rules_by_tag(tag)

def iptables_apply(self, router_id):
"""Apply IPtables.

Expand Down
40 changes: 32 additions & 8 deletions neutron/services/vpn/device_drivers/ipsec.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
}

IPSEC_CONNS = 'ipsec_site_connections'
NETMAP_DNAT_RULE = ('-i qg-+ -s %s -d %s -m policy --dir in --pol ipsec '
'-j NETMAP --to %s')
NETMAP_SNAT_RULE = '-o qg-+ -s %s -d %s -j NETMAP --to %s'


def _get_template(template_file):
Expand Down Expand Up @@ -522,17 +525,38 @@ def _update_nat(self, vpnservice, func):
:param vpnservice: vpnservices
:param func: self.add_nat_rule or self.remove_nat_rule
"""
local_cidr = vpnservice['subnet']['cidr']
local_subnet_cidr = vpnservice['subnet']['cidr']
router_id = vpnservice['router_id']
rule_tag = vpnservice['id']
self.agent.remove_nat_rules_by_tag(router_id, rule_tag)
for ipsec_site_connection in vpnservice['ipsec_site_connections']:
local_cidr = ipsec_site_connection['local_cidr']
for peer_cidr in ipsec_site_connection['peer_cidrs']:
func(
router_id,
'POSTROUTING',
'-s %s -d %s -m policy '
'--dir out --pol ipsec '
'-j ACCEPT ' % (local_cidr, peer_cidr),
top=True)
if local_cidr:
func(
router_id,
'POSTROUTING',
'-s %s -d %s -m policy '
'--dir out --pol ipsec '
'-j ACCEPT ' % (local_cidr, peer_cidr),
top=True, tag=rule_tag)
func(router_id, 'PREROUTING',
NETMAP_DNAT_RULE % (
peer_cidr, local_cidr, local_subnet_cidr),
top=True, tag=rule_tag)
func(router_id, 'POSTROUTING',
NETMAP_SNAT_RULE % (
local_subnet_cidr, peer_cidr, local_cidr),
top=True, tag=rule_tag)
else:
func(
router_id,
'POSTROUTING',
'-s %s -d %s -m policy '
'--dir out --pol ipsec '
'-j ACCEPT ' % (local_subnet_cidr, peer_cidr),
top=True)

self.agent.iptables_apply(router_id)

def vpnservice_updated(self, context, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ conn {{ipsec_site_connection.id}}
auto={{ipsec_site_connection.initiator}}
# NOTE:REQUIRED
# [subnet]
{% if ipsec_site_connection.local_cidr -%}
leftsubnet={{ipsec_site_connection.local_cidr}}
{% else -%}
leftsubnet={{vpnservice.subnet.cidr}}
{% endif -%}
# leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only)
# [updown]
# What "updown" script to run to adjust routing and/or firewalling when
Expand Down