summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/accel-ppp/ipoe.config.j23
-rw-r--r--data/templates/accel-ppp/l2tp.config.j23
-rw-r--r--data/templates/accel-ppp/pptp.config.j23
-rw-r--r--data/templates/accel-ppp/sstp.config.j23
-rw-r--r--data/templates/ipsec/swanctl/peer.j210
-rw-r--r--data/templates/ipsec/swanctl/remote_access.j218
-rw-r--r--debian/control2
-rw-r--r--interface-definitions/interfaces_vxlan.xml.in28
-rw-r--r--interface-definitions/vpn_ipsec.xml.in6
-rw-r--r--python/vyos/ifconfig/interface.py58
-rw-r--r--python/vyos/ifconfig/l2tpv3.py12
-rw-r--r--python/vyos/ifconfig/vxlan.py20
-rw-r--r--python/vyos/opmode.py2
-rw-r--r--python/vyos/utils/network.py28
-rw-r--r--python/vyos/utils/process.py2
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py28
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_l2tpv3.py5
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py47
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py17
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py14
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py130
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py6
-rwxr-xr-xsrc/conf_mode/interfaces_geneve.py2
-rwxr-xr-xsrc/conf_mode/interfaces_l2tpv3.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vxlan.py31
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py75
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py16
-rwxr-xr-xsrc/services/vyos-configd5
30 files changed, 478 insertions, 102 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index d87b90473..9729b295e 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -16,6 +16,9 @@ net-snmp
{% if limits is vyos_defined %}
connlimit
{% endif %}
+{% if extended_scripts is vyos_defined %}
+pppd_compat
+{% endif %}
[core]
thread-count={{ thread_count }}
diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2
index db4db66a7..099bc59da 100644
--- a/data/templates/accel-ppp/l2tp.config.j2
+++ b/data/templates/accel-ppp/l2tp.config.j2
@@ -16,6 +16,9 @@ net-snmp
{% if limits is vyos_defined %}
connlimit
{% endif %}
+{% if extended_scripts is vyos_defined %}
+pppd_compat
+{% endif %}
[core]
thread-count={{ thread_count }}
diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2
index 44f35998b..52ef3cb0e 100644
--- a/data/templates/accel-ppp/pptp.config.j2
+++ b/data/templates/accel-ppp/pptp.config.j2
@@ -16,6 +16,9 @@ net-snmp
{% if limits is vyos_defined %}
connlimit
{% endif %}
+{% if extended_scripts is vyos_defined %}
+pppd_compat
+{% endif %}
[core]
thread-count={{ thread_count }}
diff --git a/data/templates/accel-ppp/sstp.config.j2 b/data/templates/accel-ppp/sstp.config.j2
index 38da829f3..45d0658af 100644
--- a/data/templates/accel-ppp/sstp.config.j2
+++ b/data/templates/accel-ppp/sstp.config.j2
@@ -16,6 +16,9 @@ net-snmp
{% if limits is vyos_defined %}
connlimit
{% endif %}
+{% if extended_scripts is vyos_defined %}
+pppd_compat
+{% endif %}
[core]
thread-count={{ thread_count }}
diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2
index 58f0199fa..3a9af2c94 100644
--- a/data/templates/ipsec/swanctl/peer.j2
+++ b/data/templates/ipsec/swanctl/peer.j2
@@ -63,6 +63,11 @@
life_packets = {{ vti_esp.life_packets }}
{% endif %}
life_time = {{ vti_esp.lifetime }}s
+{% if vti_esp.disable_rekey is vyos_defined %}
+ rekey_bytes = 0
+ rekey_packets = 0
+ rekey_time = 0s
+{% endif %}
local_ts = 0.0.0.0/0,::/0
remote_ts = 0.0.0.0/0,::/0
updown = "/etc/ipsec.d/vti-up-down {{ peer_conf.vti.bind }}"
@@ -108,6 +113,11 @@
life_packets = {{ tunnel_esp.life_packets }}
{% endif %}
life_time = {{ tunnel_esp.lifetime }}s
+{% if tunnel_esp.disable_rekey is vyos_defined %}
+ rekey_bytes = 0
+ rekey_packets = 0
+ rekey_time = 0s
+{% endif %}
{% if tunnel_esp.mode is not defined or tunnel_esp.mode == 'tunnel' %}
{% if tunnel_conf.local.prefix is vyos_defined %}
{% set local_prefix = tunnel_conf.local.prefix if 'any' not in tunnel_conf.local.prefix else ['0.0.0.0/0', '::/0'] %}
diff --git a/data/templates/ipsec/swanctl/remote_access.j2 b/data/templates/ipsec/swanctl/remote_access.j2
index 6bced88c7..e384ae972 100644
--- a/data/templates/ipsec/swanctl/remote_access.j2
+++ b/data/templates/ipsec/swanctl/remote_access.j2
@@ -8,6 +8,10 @@
proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}
send_certreq = no
+{% if ike.dead_peer_detection is vyos_defined %}
+ dpd_timeout = {{ ike.dead_peer_detection.timeout }}
+ dpd_delay = {{ ike.dead_peer_detection.interval }}
+{% endif %}
rekey_time = {{ ike.lifetime }}s
keyingtries = 0
{% if rw_conf.unique is vyos_defined %}
@@ -44,8 +48,18 @@
children {
ikev2-vpn {
esp_proposals = {{ esp | get_esp_ike_cipher(ike) | join(',') }}
- rekey_time = {{ esp.lifetime }}s
- rand_time = 540s
+{% if esp.life_bytes is vyos_defined %}
+ life_bytes = {{ esp.life_bytes }}
+{% endif %}
+{% if esp.life_packets is vyos_defined %}
+ life_packets = {{ esp.life_packets }}
+{% endif %}
+ life_time = {{ esp.lifetime }}s
+{% if esp.disable_rekey is vyos_defined %}
+ rekey_bytes = 0
+ rekey_packets = 0
+ rekey_time = 0s
+{% endif %}
dpd_action = clear
inactivity = {{ rw_conf.timeout }}
{% if rw_conf.replay_window is vyos_defined %}
diff --git a/debian/control b/debian/control
index 3501af1bd..2244e7ad7 100644
--- a/debian/control
+++ b/debian/control
@@ -127,7 +127,7 @@ Depends:
pciutils,
pdns-recursor,
pmacct (>= 1.6.0),
- podman,
+ podman (>=4.9.5),
pppoe,
procps,
python3,
diff --git a/interface-definitions/interfaces_vxlan.xml.in b/interface-definitions/interfaces_vxlan.xml.in
index 504c08e7e..937acb123 100644
--- a/interface-definitions/interfaces_vxlan.xml.in
+++ b/interface-definitions/interfaces_vxlan.xml.in
@@ -117,15 +117,35 @@
<format>u32:0-4094</format>
<description>Virtual Local Area Network (VLAN) ID</description>
</valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>VLAN IDs range (use '-' as delimiter)</description>
+ </valueHelp>
<constraint>
- <validator name="numeric" argument="--range 0-4094"/>
+ <validator name="numeric" argument="--allow-range --range 0-4094"/>
</constraint>
- <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage>
+ <constraintErrorMessage>Not a valid VLAN ID or range, VLAN ID must be between 0 and 4094</constraintErrorMessage>
</properties>
<children>
- #include <include/vni.xml.i>
+ <leafNode name="vni">
+ <properties>
+ <help>Virtual Network Identifier</help>
+ <valueHelp>
+ <format>u32:0-16777214</format>
+ <description>VXLAN virtual network identifier</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;start-end&gt;</format>
+ <description>VXLAN virtual network IDs range (use '-' as delimiter)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--allow-range --range 0-16777214"/>
+ </constraint>
+ <constraintErrorMessage>Not a valid VXLAN virtual network ID or range</constraintErrorMessage>
+ </properties>
+ </leafNode>
</children>
- </tagNode>
+ </tagNode>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 7f425d982..4a7fde75b 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -99,6 +99,12 @@
</constraint>
</properties>
</leafNode>
+ <leafNode name="disable-rekey">
+ <properties>
+ <help>Do not locally initiate a re-key of the SA, remote peer must re-key before expiration</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="mode">
<properties>
<help>ESP mode</help>
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index fd4f5b269..f29615a44 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -371,6 +371,9 @@ class Interface(Control):
# can not delete ALL interfaces, see below
self.flush_addrs()
+ # remove interface from conntrack VRF interface map
+ self._del_interface_from_ct_iface_map()
+
# ---------------------------------------------------------------------
# Any class can define an eternal regex in its definition
# interface matching the regex will not be deleted
@@ -388,33 +391,20 @@ class Interface(Control):
cmd = 'ip link del dev {ifname}'.format(**self.config)
return self._cmd(cmd)
- def _set_vrf_ct_zone(self, vrf, old_vrf_tableid=None):
- """
- Add/Remove rules in nftables to associate traffic in VRF to an
- individual conntack zone
- """
+ def _nft_check_and_run(self, nft_command):
+ # Check if deleting is possible first to avoid raising errors
+ _, err = self._popen(f'nft --check {nft_command}')
+ if not err:
+ # Remove map element
+ self._cmd(f'nft {nft_command}')
- def nft_check_and_run(nft_command):
- # Check if deleting is possible first to avoid raising errors
- _, err = self._popen(f'nft --check {nft_command}')
- if not err:
- # Remove map element
- self._cmd(f'nft {nft_command}')
+ def _del_interface_from_ct_iface_map(self):
+ nft_command = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
+ self._nft_check_and_run(nft_command)
- if vrf:
- # Get routing table ID for VRF
- vrf_table_id = get_vrf_tableid(vrf)
- # Add map element with interface and zone ID
- if vrf_table_id:
- # delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF
- if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id):
- nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
- nft_check_and_run(nft_del_element)
-
- self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}')
- else:
- nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
- nft_check_and_run(nft_del_element)
+ def _add_interface_to_ct_iface_map(self, vrf_table_id: int):
+ nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
+ self._nft_check_and_run(nft_command)
def get_min_mtu(self):
"""
@@ -564,6 +554,10 @@ class Interface(Control):
>>> Interface('eth0').set_vrf()
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return False
+
tmp = self.get_interface('vrf')
if tmp == vrf:
return None
@@ -571,7 +565,19 @@ class Interface(Control):
# Get current VRF table ID
old_vrf_tableid = get_vrf_tableid(self.ifname)
self.set_interface('vrf', vrf)
- self._set_vrf_ct_zone(vrf, old_vrf_tableid)
+
+ if vrf:
+ # Get routing table ID number for VRF
+ vrf_table_id = get_vrf_tableid(vrf)
+ # Add map element with interface and zone ID
+ if vrf_table_id:
+ # delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF
+ if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id):
+ self._del_interface_from_ct_iface_map()
+ self._add_interface_to_ct_iface_map(vrf_table_id)
+ else:
+ self._del_interface_from_ct_iface_map()
+
return True
def set_arp_cache_tmo(self, tmo):
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index 85a89ef8b..c1f2803ee 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -90,9 +90,17 @@ class L2TPv3If(Interface):
"""
if self.exists(self.ifname):
- # interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
+ # remove all assigned IP addresses from interface - this is a bit redundant
+ # as the kernel will remove all addresses on interface deletion
+ self.flush_addrs()
+
+ # remove interface from conntrack VRF interface map, here explicitly and do not
+ # rely on the base class implementation as the interface will
+ # vanish as soon as the l2tp session is deleted
+ self._del_interface_from_ct_iface_map()
+
if {'tunnel_id', 'session_id'} <= set(self.config):
cmd = 'ip l2tp del session tunnel_id {tunnel_id}'
cmd += ' session_id {session_id}'
@@ -101,3 +109,5 @@ class L2TPv3If(Interface):
if 'tunnel_id' in self.config:
cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}'
self._cmd(cmd.format(**self.config))
+
+ # No need to call the baseclass as the interface is now already gone
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 918aea202..1023c58d1 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -134,6 +134,19 @@ class VXLANIf(Interface):
Controls whether vlan to tunnel mapping is enabled on the port.
By default this flag is off.
"""
+ def range_to_dict(vlan_to_vni):
+ """ Converts dict of ranges to dict """
+ result_dict = {}
+ for vlan, vlan_conf in vlan_to_vni.items():
+ vni = vlan_conf['vni']
+ vlan_range, vni_range = vlan.split('-'), vni.split('-')
+ if len(vlan_range) > 1:
+ vlan_range = range(int(vlan_range[0]), int(vlan_range[1]) + 1)
+ vni_range = range(int(vni_range[0]), int(vni_range[1]) + 1)
+ dict_to_add = {str(k): {'vni': str(v)} for k, v in zip(vlan_range, vni_range)}
+ result_dict.update(dict_to_add)
+ return result_dict
+
if not isinstance(state, bool):
raise ValueError('Value out of range')
@@ -142,7 +155,7 @@ class VXLANIf(Interface):
if dict_search('parameters.vni_filter', self.config) != None:
cur_vni_filter = get_vxlan_vni_filter(self.ifname)
- for vlan, vlan_config in self.config['vlan_to_vni_removed'].items():
+ for vlan, vlan_config in range_to_dict(self.config['vlan_to_vni_removed']).items():
# If VNI filtering is enabled, remove matching VNI filter
if cur_vni_filter != None:
vni = vlan_config['vni']
@@ -159,10 +172,11 @@ class VXLANIf(Interface):
if 'vlan_to_vni' in self.config:
# Determine current OS Kernel configured VLANs
+ vlan_vni_mapping = range_to_dict(self.config['vlan_to_vni'])
os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname)
- add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids)
+ add_vlan = list_diff(list(vlan_vni_mapping.keys()), os_configured_vlan_ids)
- for vlan, vlan_config in self.config['vlan_to_vni'].items():
+ for vlan, vlan_config in vlan_vni_mapping.items():
# VLAN mapping already exists - skip
if vlan not in add_vlan:
continue
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 8dab9a4ca..b52da9d4e 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -81,7 +81,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release)", name):
return True
else:
return False
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 8befe370f..55798651f 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -530,3 +530,31 @@ def get_vxlan_vni_filter(interface: str) -> list:
os_configured_vnis.append(str(vniStart))
return os_configured_vnis
+
+def get_nft_vrf_zone_mapping() -> dict:
+ """
+ Retrieve current nftables conntrack mapping list from Kernel
+
+ returns: [{'interface': 'red', 'vrf_tableid': 1000},
+ {'interface': 'eth2', 'vrf_tableid': 1000},
+ {'interface': 'blue', 'vrf_tableid': 2000}]
+ """
+ from json import loads
+ from jmespath import search
+ from vyos.utils.process import cmd
+ output = []
+ tmp = loads(cmd('sudo nft -j list table inet vrf_zones'))
+ # {'nftables': [{'metainfo': {'json_schema_version': 1,
+ # 'release_name': 'Old Doc Yak #3',
+ # 'version': '1.0.9'}},
+ # {'table': {'family': 'inet', 'handle': 6, 'name': 'vrf_zones'}},
+ # {'map': {'elem': [['eth0', 666],
+ # ['dum0', 666],
+ # ['wg500', 666],
+ # ['bond10.666', 666]],
+ vrf_list = search('nftables[].map.elem | [0]', tmp)
+ if not vrf_list:
+ return output
+ for (vrf_name, vrf_id) in vrf_list:
+ output.append({'interface' : vrf_name, 'vrf_tableid' : vrf_id})
+ return output
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index bd0644bc0..a2398edf7 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -225,7 +225,7 @@ def process_named_running(name: str, cmdline: str=None, timeout: int=0):
if not tmp:
if time.time() > time_expire:
break
- time.sleep(0.100) # wait 250ms
+ time.sleep(0.100) # wait 100ms
continue
return tmp
else:
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 4072fd5c2..e7e29387f 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -15,7 +15,6 @@
from netifaces import AF_INET
from netifaces import AF_INET6
from netifaces import ifaddresses
-from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -25,13 +24,15 @@ from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.file import read_file
from vyos.utils.dict import dict_search
+from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import get_vrf_tableid
-from vyos.utils.process import cmd
+from vyos.utils.network import interface_exists
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
+from vyos.utils.network import get_nft_vrf_zone_mapping
from vyos.xml_ref import cli_defined
dhclient_base_dir = directories['isc_dhclient_dir']
@@ -117,8 +118,11 @@ class BasicInterfaceTest:
self.cli_commit()
# Verify that no previously interface remained on the system
+ ct_map = get_nft_vrf_zone_mapping()
for intf in self._interfaces:
- self.assertNotIn(intf, interfaces())
+ self.assertFalse(interface_exists(intf))
+ for map_entry in ct_map:
+ self.assertNotEqual(intf, map_entry['interface'])
# No daemon started during tests should remain running
for daemon in ['dhcp6c', 'dhclient']:
@@ -303,6 +307,24 @@ class BasicInterfaceTest:
self.cli_delete(['vrf', 'name', vrf1_name])
self.cli_delete(['vrf', 'name', vrf2_name])
+ def test_add_to_invalid_vrf(self):
+ if not self._test_vrf:
+ self.skipTest('not supported')
+
+ # move interface into first VRF
+ for interface in self._interfaces:
+ for option in self._options.get(interface, []):
+ self.cli_set(self._base_path + [interface] + option.split())
+ self.cli_set(self._base_path + [interface, 'vrf', 'invalid'])
+
+ # check validate() - can not use a non-existing VRF
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'vrf', 'invalid'])
+ self.cli_set(self._base_path + [interface, 'description', 'test_add_to_invalid_vrf'])
+
def test_span_mirror(self):
if not self._mirror_interfaces:
self.skipTest('not supported')
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index efaa74fe0..112c58c09 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -75,6 +75,8 @@ class VyOSUnitTestSHIM:
self._session.delete(config)
def cli_commit(self):
+ if self.debug:
+ print('commit')
self._session.commit()
# during a commit there is a process opening commit_lock, and run() returns 0
while run(f'sudo lsof -nP {commit_lock}') == 0:
diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
index af3d49f75..abc55e6d2 100755
--- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py
+++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
@@ -20,7 +20,7 @@ import unittest
from base_interfaces_test import BasicInterfaceTest
from vyos.utils.process import cmd
-
+from vyos.utils.kernel import unload_kmod
class L2TPv3InterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -62,7 +62,6 @@ if __name__ == '__main__':
# reloaded on demand - not needed but test more and more features
for module in ['l2tp_ip6', 'l2tp_ip', 'l2tp_eth', 'l2tp_eth',
'l2tp_netlink', 'l2tp_core']:
- if os.path.exists(f'/sys/module/{module}'):
- cmd(f'sudo rmmod {module}')
+ unload_kmod(module)
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index 18676491b..b2076b43b 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -27,6 +27,13 @@ from vyos.utils.network import get_vxlan_vni_filter
from vyos.template import is_ipv6
from base_interfaces_test import BasicInterfaceTest
+def convert_to_list(ranges_to_convert):
+ result_list = []
+ for r in ranges_to_convert:
+ ranges = r.split('-')
+ result_list.extend([str(i) for i in range(int(ranges[0]), int(ranges[1]) + 1)])
+ return result_list
+
class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -153,6 +160,11 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
'31': '10031',
}
+ vlan_to_vni_ranges = {
+ '40-43': '10040-10043',
+ '45-47': '10045-10047'
+ }
+
self.cli_set(self._base_path + [interface, 'parameters', 'external'])
self.cli_set(self._base_path + [interface, 'source-address', source_address])
@@ -185,6 +197,26 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_vxlan_vlan_tunnels('vxlan0')
self.assertEqual(tmp, list(vlan_to_vni))
+ # add ranged VLAN - VNI mapping
+ for vlan, vni in vlan_to_vni_ranges.items():
+ self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni])
+ self.cli_commit()
+
+ tmp = get_vxlan_vlan_tunnels('vxlan0')
+ vlans_list = convert_to_list(vlan_to_vni_ranges.keys())
+ self.assertEqual(tmp, list(vlan_to_vni) + vlans_list)
+
+ # check validate() - cannot map VNI range to a single VLAN id
+ self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100', 'vni', '100-102'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(self._base_path + [interface, 'vlan-to-vni', '100'])
+
+ # check validate() - cannot map VLAN to VNI with different ranges
+ self.cli_set(self._base_path + [interface, 'vlan-to-vni', '100-102', 'vni', '100-105'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
self.cli_delete(['interfaces', 'bridge', bridge])
def test_vxlan_neighbor_suppress(self):
@@ -287,6 +319,12 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
'60': '10060',
'69': '10069',
}
+
+ vlan_to_vni_ranges = {
+ '70-73': '10070-10073',
+ '75-77': '10075-10077'
+ }
+
for vlan, vni in vlan_to_vni.items():
self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni])
# we need a bridge ...
@@ -313,6 +351,15 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_vxlan_vni_filter(interface)
self.assertListEqual(list(vlan_to_vni.values()), tmp)
+ # add ranged VLAN - VNI mapping
+ for vlan, vni in vlan_to_vni_ranges.items():
+ self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni])
+ self.cli_commit()
+
+ tmp = get_vxlan_vni_filter(interface)
+ vnis_list = convert_to_list(vlan_to_vni_ranges.values())
+ self.assertListEqual(list(vlan_to_vni.values()) + vnis_list, tmp)
+
self.cli_delete(['interfaces', 'bridge', bridge])
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 7bfe0d221..ba2f60a89 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -34,7 +34,6 @@ def get_config_value(interface, key):
tmp = re.findall(f'{key}=+(.*)', tmp)
return tmp[0]
-wifi_cc_path = ['system', 'wireless', 'country-code']
country = 'se'
class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
@@ -42,19 +41,23 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
cls._base_path = ['interfaces', 'wireless']
cls._options = {
'wlan0': ['physical-device phy0',
+ f'country-code {country}',
'ssid VyOS-WIFI-0',
'type station',
'address 192.0.2.1/30'],
'wlan1': ['physical-device phy0',
+ f'country-code {country}',
'ssid VyOS-WIFI-1',
'type access-point',
'address 192.0.2.5/30',
'channel 0'],
'wlan10': ['physical-device phy1',
+ f'country-code {country}',
'ssid VyOS-WIFI-2',
'type station',
'address 192.0.2.9/30'],
'wlan11': ['physical-device phy1',
+ f'country-code {country}',
'ssid VyOS-WIFI-3',
'type access-point',
'address 192.0.2.13/30',
@@ -68,9 +71,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
cls._test_ipv6 = False
cls._test_vlan = False
- cls.cli_set(cls, wifi_cc_path + [country])
-
-
def test_wireless_add_single_ip_address(self):
# derived method to check if member interfaces are enslaved properly
super().test_add_single_ip_address()
@@ -91,6 +91,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
ssid = 'ssid'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
# auto-powersave is special
@@ -169,6 +170,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
antennas = '3'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'channel', '36'])
@@ -238,6 +240,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
antennas = '3'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'channel', '36'])
@@ -311,6 +314,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
center_channel_freq_1 = '15'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'channel', channel])
self.cli_set(self._base_path + [interface, 'mode', 'ax'])
@@ -394,13 +398,12 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
# SSID and country-code are already configured in self.setUpClass()
# Therefore, we must delete those here to check if commit will fail without it.
- self.cli_delete(wifi_cc_path)
self.cli_delete(self._base_path + [interface, 'ssid'])
# Country-Code must be set
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(wifi_cc_path + [country])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
# SSID must be set
with self.assertRaises(ConfigSessionError):
@@ -457,6 +460,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(bridge_path + ['member', 'interface', interface])
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'channel', '1'])
@@ -495,6 +499,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04']
self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', country])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept'])
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 585c1dc89..905eaf2e9 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -16,7 +16,6 @@
import unittest
-from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
@@ -27,6 +26,7 @@ PROCESS_NAME = 'ospfd'
base_path = ['protocols', 'ospf']
route_map = 'foo-bar-baz10'
+dummy_if = 'dum3562'
class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
@classmethod
@@ -38,6 +38,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
+ cls.cli_set(cls, ['interfaces', 'dummy', dummy_if])
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -46,6 +47,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['policy', 'route-map', route_map])
+ cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if])
super(TestProtocolsOSPF, cls).tearDownClass()
def tearDown(self):
@@ -441,14 +443,13 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
global_block_high = "399"
local_block_low = "400"
local_block_high = "499"
- interface = 'lo'
maximum_stack_size = '5'
prefix_one = '192.168.0.1/32'
prefix_two = '192.168.0.2/32'
prefix_one_value = '1'
prefix_two_value = '2'
- self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['interface', dummy_if])
self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size])
self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low])
self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high])
@@ -472,17 +473,14 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
def test_ospf_15_ldp_sync(self):
holddown = "500"
- interface = 'lo'
interfaces = Section.interfaces('ethernet')
- self.cli_set(base_path + ['interface', interface])
+ self.cli_set(base_path + ['interface', dummy_if])
self.cli_set(base_path + ['ldp-sync', 'holddown', holddown])
# Commit main OSPF changes
self.cli_commit()
- sleep(10)
-
# Verify main OSPF changes
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
@@ -514,7 +512,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
- self.assertIn(f' no ip ospf mpls ldp-sync', config)
+ self.assertNotIn(f' ip ospf mpls ldp-sync', config)
def test_ospf_16_graceful_restart(self):
period = '300'
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 27356d70e..2dc66485b 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -252,6 +252,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
for line in swanctl_conf_lines:
self.assertIn(line, swanctl_conf)
+ # if dpd is not specified it should not be enabled (see T6599)
+ swanctl_unexpected_lines = [
+ f'dpd_timeout'
+ f'dpd_delay'
+ ]
+
+ for unexpected_line in swanctl_unexpected_lines:
+ self.assertNotIn(unexpected_line, swanctl_conf)
+
swanctl_secrets_lines = [
f'id-{regex_uuid4} = "{local_id}"',
f'id-{regex_uuid4} = "{remote_id}"',
@@ -639,8 +648,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
f'auth = eap-mschapv2',
f'eap_id = %any',
f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
- f'rekey_time = {eap_lifetime}s',
- f'rand_time = 540s',
+ f'life_time = {eap_lifetime}s',
f'dpd_action = clear',
f'replay_window = 32',
f'inactivity = 28800',
@@ -761,8 +769,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
f'auth = eap-tls',
f'eap_id = %any',
f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
- f'rekey_time = {eap_lifetime}s',
- f'rand_time = 540s',
+ f'life_time = {eap_lifetime}s',
f'dpd_action = clear',
f'inactivity = 28800',
f'local_ts = 0.0.0.0/0,::/0',
@@ -876,8 +883,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
f'certs = peer1.pem',
f'cacerts = MyVyOS-CA.pem,MyVyOS-IntCA.pem',
f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
- f'rekey_time = {eap_lifetime}s',
- f'rand_time = 540s',
+ f'life_time = {eap_lifetime}s',
f'dpd_action = clear',
f'inactivity = 28800',
f'local_ts = 0.0.0.0/0,::/0',
@@ -968,5 +974,117 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.tearDownPKI()
+ def test_remote_access_no_rekey(self):
+ # In some RA secnarios, disabling server-initiated rekey of IKE and CHILD SA is desired
+ self.setupPKI()
+
+ ike_group = 'IKE-RW'
+ esp_group = 'ESP-RW'
+
+ conn_name = 'vyos-rw'
+ local_address = '192.0.2.1'
+ ip_pool_name = 'ra-rw-ipv4'
+ ike_lifetime = '7200'
+ eap_lifetime = '3600'
+ local_id = 'ipsec.vyos.net'
+
+ name_servers = ['172.16.254.100', '172.16.254.101']
+ prefix = '172.16.250.0/28'
+
+ # IKE
+ self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', '0'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
+ self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256'])
+
+ # ESP
+ self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime])
+ self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'disable-rekey'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128'])
+ self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256'])
+
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id])
+ # Use client-mode x509 instead of default EAP-MSCHAPv2
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'x509'])
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509'])
+
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name])
+ # verify() - CA cert required for x509 auth
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name])
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', int_ca_name])
+
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group])
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group])
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address])
+ self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name])
+
+ for ns in name_servers:
+ self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns])
+ self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix])
+
+ self.cli_commit()
+
+ # verify applied configuration
+ swanctl_conf = read_file(swanctl_file)
+ swanctl_lines = [
+ f'{conn_name}',
+ f'remote_addrs = %any',
+ f'local_addrs = {local_address}',
+ f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048',
+ f'version = 2',
+ f'send_certreq = no',
+ f'rekey_time = 0s',
+ f'keyingtries = 0',
+ f'pools = {ip_pool_name}',
+ f'id = "{local_id}"',
+ f'auth = pubkey',
+ f'certs = peer1.pem',
+ f'cacerts = MyVyOS-CA.pem,MyVyOS-IntCA.pem',
+ f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256',
+ f'life_time = {eap_lifetime}s',
+ f'rekey_time = 0s',
+ f'dpd_action = clear',
+ f'inactivity = 28800',
+ f'local_ts = 0.0.0.0/0,::/0',
+ ]
+ for line in swanctl_lines:
+ self.assertIn(line, swanctl_conf)
+
+ swanctl_pool_lines = [
+ f'{ip_pool_name}',
+ f'addrs = {prefix}',
+ f'dns = {",".join(name_servers)}',
+ ]
+ for line in swanctl_pool_lines:
+ self.assertIn(line, swanctl_conf)
+
+ # Check Root CA, Intermediate CA and Peer cert/key pair is present
+ self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}.pem')))
+ self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}.pem')))
+ self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem')))
+
+ self.tearDownPKI()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index 4666e98e7..700e4cec7 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -19,8 +19,9 @@ import os
import platform
import unittest
-kernel = platform.release()
+from vyos.utils.kernel import check_kmod
+kernel = platform.release()
class TestKernelModules(unittest.TestCase):
""" VyOS makes use of a lot of Kernel drivers, modules and features. The
required modules which are essential for VyOS should be tested that they are
@@ -35,9 +36,8 @@ class TestKernelModules(unittest.TestCase):
super(TestKernelModules, cls).setUpClass()
CONFIG = '/proc/config.gz'
-
if not os.path.isfile(CONFIG):
- call('sudo modprobe configs')
+ check_kmod('configs')
with gzip.open(CONFIG, 'rt') as f:
cls._config_data = f.read()
diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py
index 769139e0f..007708d4a 100755
--- a/src/conf_mode/interfaces_geneve.py
+++ b/src/conf_mode/interfaces_geneve.py
@@ -24,6 +24,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import GeneveIf
from vyos.utils.network import interface_exists
from vyos import ConfigError
@@ -59,6 +60,7 @@ def verify(geneve):
verify_mtu_ipv6(geneve)
verify_address(geneve)
+ verify_vrf(geneve)
verify_bond_bridge_member(geneve)
verify_mirror_redirect(geneve)
diff --git a/src/conf_mode/interfaces_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py
index e25793543..b9f827bee 100755
--- a/src/conf_mode/interfaces_l2tpv3.py
+++ b/src/conf_mode/interfaces_l2tpv3.py
@@ -24,6 +24,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import L2TPv3If
from vyos.utils.kernel import check_kmod
from vyos.utils.network import is_addr_assigned
@@ -76,6 +77,7 @@ def verify(l2tpv3):
verify_mtu_ipv6(l2tpv3)
verify_address(l2tpv3)
+ verify_vrf(l2tpv3)
verify_bond_bridge_member(l2tpv3)
verify_mirror_redirect(l2tpv3)
return None
diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py
index e6a833df7..20629c6c1 100755
--- a/src/conf_mode/interfaces_vti.py
+++ b/src/conf_mode/interfaces_vti.py
@@ -19,6 +19,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_vrf
from vyos.ifconfig import VTIIf
from vyos import ConfigError
from vyos import airbag
@@ -38,6 +39,7 @@ def get_config(config=None):
return vti
def verify(vti):
+ verify_vrf(vti)
verify_mirror_redirect(vti)
return None
diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py
index 39365968a..68646e8ff 100755
--- a/src/conf_mode/interfaces_vxlan.py
+++ b/src/conf_mode/interfaces_vxlan.py
@@ -28,6 +28,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
from vyos.ifconfig import VXLANIf
from vyos.template import is_ipv6
@@ -178,13 +179,36 @@ def verify(vxlan):
'is member of a bridge interface!')
vnis_used = []
+ vlans_used = []
for vif, vif_config in vxlan['vlan_to_vni'].items():
if 'vni' not in vif_config:
raise ConfigError(f'Must define VNI for VLAN "{vif}"!')
vni = vif_config['vni']
- if vni in vnis_used:
- raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!')
- vnis_used.append(vni)
+
+ err_msg = f'VLAN range "{vif}" does not match VNI range "{vni}"!'
+ vif_range, vni_range = list(map(int, vif.split('-'))), list(map(int, vni.split('-')))
+
+ if len(vif_range) != len(vni_range):
+ raise ConfigError(err_msg)
+
+ if len(vif_range) > 1:
+ if vni_range[0] > vni_range[-1] or vif_range[0] > vif_range[-1]:
+ raise ConfigError('The upper bound of the range must be greater than the lower bound!')
+ vni_range = range(vni_range[0], vni_range[1] + 1)
+ vif_range = range(vif_range[0], vif_range[1] + 1)
+
+ if len(vif_range) != len(vni_range):
+ raise ConfigError(err_msg)
+
+ for vni_id in vni_range:
+ if vni_id in vnis_used:
+ raise ConfigError(f'VNI "{vni_id}" is already assigned to a different VLAN!')
+ vnis_used.append(vni_id)
+
+ for vif_id in vif_range:
+ if vif_id in vlans_used:
+ raise ConfigError(f'VLAN "{vif_id}" is already in use!')
+ vlans_used.append(vif_id)
if dict_search('parameters.neighbor_suppress', vxlan) != None:
if 'is_bridge_member' not in vxlan:
@@ -193,6 +217,7 @@ def verify(vxlan):
verify_mtu_ipv6(vxlan)
verify_address(vxlan)
+ verify_vrf(vxlan)
verify_bond_bridge_member(vxlan)
verify_mirror_redirect(vxlan)
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index 3f01612a2..ff38c979c 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from re import findall
from netaddr import EUI, mac_unix_expanded
+from time import sleep
from vyos.config import Config
from vyos.configdict import get_interface_dict
@@ -34,6 +35,9 @@ from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.kernel import check_kmod
from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import is_systemd_service_running
+from vyos.utils.network import interface_exists
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -87,6 +91,11 @@ def get_config(config=None):
if wifi.from_defaults(['security', 'wpa']): # if not set by user
del wifi['security']['wpa']
+ # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
+ if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []):
+ wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
+ del wifi['capabilities']['ht']['40mhz_incapable']
+
if dict_search('security.wpa', wifi) != None:
wpa_cipher = wifi['security']['wpa'].get('cipher')
wpa_mode = wifi['security']['wpa'].get('mode')
@@ -114,7 +123,7 @@ def get_config(config=None):
tmp = find_other_stations(conf, base, wifi['ifname'])
if tmp: wifi['station_interfaces'] = tmp
- # used in hostapt.conf.j2
+ # used in hostapd.conf.j2
wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi)
wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi)
@@ -218,11 +227,6 @@ def verify(wifi):
def generate(wifi):
interface = wifi['ifname']
- # always stop hostapd service first before reconfiguring it
- call(f'systemctl stop hostapd@{interface}.service')
- # always stop wpa_supplicant service first before reconfiguring it
- call(f'systemctl stop wpa_supplicant@{interface}.service')
-
# Delete config files if interface is removed
if 'deleted' in wifi:
if os.path.isfile(hostapd_conf.format(**wifi)):
@@ -258,11 +262,6 @@ def generate(wifi):
mac.dialect = mac_unix_expanded
wifi['mac'] = str(mac)
- # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
- if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []):
- wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
- del wifi['capabilities']['ht']['40mhz_incapable']
-
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi)
@@ -276,23 +275,45 @@ def generate(wifi):
def apply(wifi):
interface = wifi['ifname']
+ # From systemd source code:
+ # If there's a stop job queued before we enter the DEAD state, we shouldn't act on Restart=,
+ # in order to not undo what has already been enqueued. */
+ #
+ # It was found that calling restart on hostapd will (4 out of 10 cases) deactivate
+ # the service instead of restarting it, when it was not yet properly stopped
+ # systemd[1]: hostapd@wlan1.service: Deactivated successfully.
+ # Thus kill all WIFI service and start them again after it's ensured nothing lives
+ call(f'systemctl stop hostapd@{interface}.service')
+ call(f'systemctl stop wpa_supplicant@{interface}.service')
+
if 'deleted' in wifi:
- WiFiIf(interface).remove()
- else:
- # Finally create the new interface
- w = WiFiIf(**wifi)
- w.update(wifi)
-
- # Enable/Disable interface - interface is always placed in
- # administrative down state in WiFiIf class
- if 'disable' not in wifi:
- # Physical interface is now configured. Proceed by starting hostapd or
- # wpa_supplicant daemon. When type is monitor we can just skip this.
- if wifi['type'] == 'access-point':
- call(f'systemctl start hostapd@{interface}.service')
-
- elif wifi['type'] == 'station':
- call(f'systemctl start wpa_supplicant@{interface}.service')
+ WiFiIf(**wifi).remove()
+ return None
+
+ while (is_systemd_service_running(f'hostapd@{interface}.service') or \
+ is_systemd_service_active(f'hostapd@{interface}.service')):
+ sleep(0.250) # wait 250ms
+
+ # Finally create the new interface
+ w = WiFiIf(**wifi)
+ w.update(wifi)
+
+ # Enable/Disable interface - interface is always placed in
+ # administrative down state in WiFiIf class
+ if 'disable' not in wifi:
+ # Wait until interface was properly added to the Kernel
+ ii = 0
+ while not (interface_exists(interface) and ii < 20):
+ sleep(0.250) # wait 250ms
+ ii += 1
+
+ # Physical interface is now configured. Proceed by starting hostapd or
+ # wpa_supplicant daemon. When type is monitor we can just skip this.
+ if wifi['type'] == 'access-point':
+ call(f'systemctl start hostapd@{interface}.service')
+
+ elif wifi['type'] == 'station':
+ call(f'systemctl start wpa_supplicant@{interface}.service')
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index dc78c755e..cf82b767f 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -24,6 +24,7 @@ from time import sleep
from vyos.base import Warning
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
from vyos.configdict import leaf_node_changed
@@ -86,9 +87,22 @@ def get_config(config=None):
ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
get_first_key=True,
- with_recursive_defaults=True,
with_pki=True)
+ # We have to cleanup the default dict, as default values could
+ # enable features which are not explicitly enabled on the
+ # CLI. E.g. dead-peer-detection defaults should not be injected
+ # unless the feature is explicitly opted in to by setting the
+ # top-level node
+ default_values = conf.get_config_defaults(**ipsec.kwargs, recursive=True)
+
+ if 'ike_group' in ipsec:
+ for name, ike in ipsec['ike_group'].items():
+ if 'dead_peer_detection' not in ike:
+ del default_values['ike_group'][name]['dead_peer_detection']
+
+ ipsec = config_dict_merge(default_values, ipsec)
+
ipsec['dhcp_interfaces'] = set()
ipsec['dhcp_no_address'] = {}
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index a7306afd1..49eb6799c 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -143,9 +143,8 @@ def run_script(script_name, config, args) -> int:
script.generate(c)
script.apply(c)
except ConfigError as e:
- s = f'{script_name}: {repr(e)}'
- logger.error(s)
- explicit_print(session_out, session_mode, s)
+ logger.error(e)
+ explicit_print(session_out, session_mode, str(e))
return R_ERROR_COMMIT
except Exception as e:
logger.critical(e)