Skip to content

Commit

Permalink
[feature] Allowed to re-use VNI for all VXLAN over Wireguard tunnels #…
Browse files Browse the repository at this point in the history
…784

Closes #784
  • Loading branch information
pandafy committed Jul 18, 2023
1 parent f396d07 commit 5aa797b
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 10 deletions.
27 changes: 21 additions & 6 deletions openwisp_controller/config/base/vpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,14 @@ def _get_vxlan_peers(self):
Returns list of vxlan peers, the result is cached.
"""
peers = []
vxlan_interface = self.config.get('vxlan', [{}])[0].get('name')
for vpnclient in self._get_peer_queryset():
if vpnclient.ip:
peers.append({'vni': vpnclient.vni, 'remote': vpnclient.ip.ip_address})
if not vpnclient.ip:
continue
peer = {'vni': vpnclient.vni, 'remote': vpnclient.ip.ip_address}
if vxlan_interface:
peer['interface'] = vxlan_interface
peers.append(peer)
return peers


Expand Down Expand Up @@ -625,10 +630,7 @@ class AbstractVpnClient(models.Model):

class Meta:
abstract = True
unique_together = (
('config', 'vpn'),
('vpn', 'vni'),
)
unique_together = (('config', 'vpn'),)
verbose_name = _('VPN client')
verbose_name_plural = _('VPN clients')

Expand All @@ -643,6 +645,16 @@ def register_auto_ip_stopper(cls, func):
if func not in cls._auto_ip_stopper_funcs:
cls._auto_ip_stopper_funcs.append(func)

def _get_unique_checks(self, exclude=None, include_meta_constraints=False):
unique_checks, date_checks = super()._get_unique_checks(
exclude, include_meta_constraints
)
if self.vpn.config.get('vxlan', [{}])[0].get('vni', 0) == 0:
# If VNI is specified in VXLAN tunnel configuration,
# then each VXLAN tunnel should have different VNI.
unique_checks.append((self.__class__, ('vpn', 'vni')))
return unique_checks, date_checks

def save(self, *args, **kwargs):
"""
automatically provisions tunnel keys
Expand Down Expand Up @@ -760,6 +772,9 @@ def _auto_vxlan(self):
"""
if not self.vpn._is_backend_type('vxlan') or self.vni:
return
if self.vpn.config.get('vxlan', [{}])[0].get('vni', 0) != 0:
self.vni = self.vpn.config['vxlan'][0]['vni']
return
last_tunnel = (
self._meta.model.objects.filter(vpn=self.vpn).order_by('vni').last()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.3 on 2023-07-18 16:59

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("config", "0049_devicegroup_context"),
]

operations = [
migrations.AlterUniqueTogether(
name="vpnclient",
unique_together={("config", "vpn")},
),
]
47 changes: 45 additions & 2 deletions openwisp_controller/config/tests/test_vpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,14 +851,14 @@ def test_vxlan_config_creation(self):
client.refresh_from_db()
self.assertEqual(client.vni, None)

def test_vxlan_vni_conflict(self):
def test_duplicate_vxlan_tunnels_same_vni(self):
tunnel, subnet = self._create_vxlan_tunnel()
d1 = self._create_device()
c1 = self._create_config(device=d1)
client = VpnClient(vpn=tunnel, config=c1, vni=1)
client.full_clean()
client.save()
with self.subTest('vni with same ID should fail'):
with self.subTest('Test server configuration does not define VNI'):
d2 = self._create_device(name='d2', mac_address='16:DB:7F:E8:50:01')
c2 = self._create_config(device=d2)
client = VpnClient(vpn=tunnel, config=c2, vni=1)
Expand All @@ -871,6 +871,14 @@ def test_vxlan_vni_conflict(self):
['VPN client with this Vpn and Vni already exists.'],
)

with self.subTest('Test server configuration defines VNI'):
tunnel.config['vxlan'] = [{'interface': 'vxlan1', 'vni': 1}]
tunnel.full_clean()
tunnel.save()
client = VpnClient(vpn=tunnel, config=c2, vni=1)
client.full_clean()
client.save()

def test_vxlan_schema(self):
with self.assertRaises(ValidationError) as context_manager:
self._create_vxlan_tunnel(config={'wireguard': []})
Expand Down Expand Up @@ -953,3 +961,38 @@ def test_auto_peer_configuration(self):
config = vpn.get_config()
self.assertEqual(config['wireguard'][0]['name'], 'wg2')
self.assertEqual(config['wireguard'][0]['port'], 51821)

def test_unicast_vxlan_tunnels(self):
tunnel, _ = self._create_vxlan_tunnel(
config={
'wireguard': [{'name': 'wg0', 'port': 51820}],
'vxlan': [{'name': 'vxlan1', 'vni': 1}],
}
)
device1 = self._create_device_config(
device_opts={'organization': tunnel.organization}
)
device2 = self._create_device_config(
device_opts={
'organization': tunnel.organization,
'name': 'device2',
'mac_address': '16:DB:7F:E8:50:01',
}
)
vpn_template = self._create_template(
name='vxlan-wireguard',
type='vpn',
vpn=tunnel,
organization=tunnel.organization,
auto_cert=True,
)
device1.config.templates.add(vpn_template)
device2.config.templates.add(vpn_template)
self.assertEqual(VpnClient.objects.count(), 2)
self.assertListEqual(
tunnel._get_vxlan_peers(),
[
{'interface': 'vxlan1', 'remote': '10.0.0.2', 'vni': 1},
{'interface': 'vxlan1', 'remote': '10.0.0.3', 'vni': 1},
],
)
16 changes: 15 additions & 1 deletion openwisp_controller/vpn_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from netjsonconfig import OpenVpn as BaseOpenVpn
from netjsonconfig import VxlanWireguard as BaseVxlanWireguard
from netjsonconfig import Wireguard as BaseWireguard
from netjsonconfig.backends.vxlan.schema import base_vxlan_properties

# adapt OpenVPN schema in order to limit it to 1 item only
limited_schema = deepcopy(BaseOpenVpn.schema)
Expand Down Expand Up @@ -63,9 +64,22 @@ class Wireguard(BaseWireguard):
schema = limited_wireguard_schema


limited_vxlan_wireguard_schema = deepcopy(limited_wireguard_schema)
limited_vxlan_properties = deepcopy(base_vxlan_properties)
limited_vxlan_properties['vxlan']['items']['properties']['vni']['description'] = (
(
'VXLAN Network Identifier, if set to "0", each tunnel will have'
' different VNI. If a non-zero VNI is specified, then it will be'
' used for all VXLAN tunnels.'
),
)
limited_vxlan_properties['vxlan'].update({'maxItems': 1, 'minItems': 1})
limited_vxlan_wireguard_schema['properties'].update(limited_vxlan_properties)


class VxlanWireguard(BaseVxlanWireguard):
"""
VXLAN over WireGuard
"""

schema = limited_wireguard_schema
schema = limited_vxlan_wireguard_schema
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
django-sortedm2m~=3.1.1
django-reversion~=5.0.4
django-taggit~=4.0.0
netjsonconfig @ https://github.com/openwisp/netjsonconfig/tarball/master
netjsonconfig @ https://github.com/openwisp/netjsonconfig/tarball/vxlan-unicast
django-x509 @ https://github.com/openwisp/django-x509/tarball/master
django-loci @ https://github.com/openwisp/django-loci/tarball/master
django-flat-json-widget @ https://github.com/openwisp/django-flat-json-widget/tarball/master
Expand Down

0 comments on commit 5aa797b

Please sign in to comment.