summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/add-rebase-label.yml28
-rw-r--r--data/templates/ipsec/swanctl/peer.j210
-rw-r--r--data/templates/ipsec/swanctl/remote_access.j218
-rw-r--r--interface-definitions/vpn_ipsec.xml.in6
-rw-r--r--python/vyos/ifconfig/interface.py28
-rw-r--r--python/vyos/utils/network.py23
-rw-r--r--python/vyos/utils/process.py2
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py46
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py52
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py14
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py5
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py130
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py7
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py6
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py75
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py16
-rwxr-xr-xsrc/conf_mode/vrf.py6
18 files changed, 368 insertions, 106 deletions
diff --git a/.github/workflows/add-rebase-label.yml b/.github/workflows/add-rebase-label.yml
index 4cf3545ba..9041303af 100644
--- a/.github/workflows/add-rebase-label.yml
+++ b/.github/workflows/add-rebase-label.yml
@@ -1,14 +1,14 @@
-name: Add rebase label
-
-on:
- pull_request_target:
- types: [synchronize, opened, reopened, labeled, unlabeled]
-
-permissions:
- pull-requests: write
- contents: read
-
-jobs:
- add-rebase-label:
- uses: vyos/.github/.github/workflows/add-rebase-label.yml@current
- secrets: inherit
+name: Add rebase label
+
+on:
+ pull_request_target:
+ types: [synchronize, opened, reopened, labeled, unlabeled]
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ add-rebase-label:
+ uses: vyos/.github/.github/workflows/add-rebase-label.yml@current
+ secrets: inherit
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/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 117479ade..748830004 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -37,6 +37,7 @@ from vyos.utils.network import mac2eui64
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_namespace
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import run
@@ -402,7 +403,7 @@ class Interface(Control):
if netns: cmd = f'ip netns exec {netns} {cmd}'
return self._cmd(cmd)
- def _set_vrf_ct_zone(self, vrf):
+ 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
@@ -411,20 +412,27 @@ class Interface(Control):
if 'netns' in self.config:
return None
+ 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}')
+
if vrf:
# Get routing table ID for VRF
- vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get(
- 'info_data', {}).get('table')
+ 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}" }}'
- # Check if deleting is possible first to avoid raising errors
- _, err = self._popen(f'nft --check {nft_del_element}')
- if not err:
- # Remove map element
- self._cmd(f'nft {nft_del_element}')
+ nft_check_and_run(nft_del_element)
def get_min_mtu(self):
"""
@@ -601,8 +609,10 @@ class Interface(Control):
if tmp == vrf:
return False
+ # Get current VRF table ID
+ old_vrf_tableid = get_vrf_tableid(self.ifname)
self.set_interface('vrf', vrf)
- self._set_vrf_ct_zone(vrf)
+ self._set_vrf_ct_zone(vrf, old_vrf_tableid)
return True
def set_arp_cache_tmo(self, tmo):
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 829124b57..8406a5638 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -83,6 +83,19 @@ def get_interface_vrf(interface):
return tmp['master']
return 'default'
+def get_vrf_tableid(interface: str):
+ """ Return VRF table ID for given interface name or None """
+ from vyos.utils.dict import dict_search
+ table = None
+ tmp = get_interface_config(interface)
+ # Check if we are "the" VRF interface
+ if dict_search('linkinfo.info_kind', tmp) == 'vrf':
+ table = tmp['linkinfo']['info_data']['table']
+ # or an interface bound to a VRF
+ elif dict_search('linkinfo.info_slave_kind', tmp) == 'vrf':
+ table = tmp['linkinfo']['info_slave_data']['table']
+ return table
+
def get_interface_config(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
@@ -537,21 +550,21 @@ def ipv6_prefix_length(low, high):
return None
xor = bytearray(a ^ b for a, b in zip(lo, hi))
-
+
plen = 0
while plen < 128 and xor[plen // 8] == 0:
plen += 8
-
+
if plen == 128:
return plen
-
+
for i in range((plen // 8) + 1, 16):
if xor[i] != 0:
return None
-
+
for i in range(8):
msk = ~xor[plen // 8] & 0xff
-
+
if msk == bytemasks[i]:
return plen + i + 1
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index 60ef87a51..ce880f4a4 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 9be2c2f1a..4072fd5c2 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -28,6 +28,7 @@ from vyos.utils.dict import dict_search
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 is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
@@ -257,6 +258,51 @@ class BasicInterfaceTest:
self.cli_delete(['vrf', 'name', vrf_name])
+ def test_move_interface_between_vrf_instances(self):
+ if not self._test_vrf:
+ self.skipTest('not supported')
+
+ vrf1_name = 'smoketest_mgmt1'
+ vrf1_table = '5424'
+ vrf2_name = 'smoketest_mgmt2'
+ vrf2_table = '7412'
+
+ self.cli_set(['vrf', 'name', vrf1_name, 'table', vrf1_table])
+ self.cli_set(['vrf', 'name', vrf2_name, 'table', vrf2_table])
+
+ # 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', vrf1_name])
+
+ self.cli_commit()
+
+ # check that interface belongs to proper VRF
+ for interface in self._interfaces:
+ tmp = get_interface_vrf(interface)
+ self.assertEqual(tmp, vrf1_name)
+
+ tmp = get_interface_config(vrf1_name)
+ self.assertEqual(int(vrf1_table), get_vrf_tableid(interface))
+
+ # move interface into second VRF
+ for interface in self._interfaces:
+ self.cli_set(self._base_path + [interface, 'vrf', vrf2_name])
+
+ self.cli_commit()
+
+ # check that interface belongs to proper VRF
+ for interface in self._interfaces:
+ tmp = get_interface_vrf(interface)
+ self.assertEqual(tmp, vrf2_name)
+
+ tmp = get_interface_config(vrf2_name)
+ self.assertEqual(int(vrf2_table), get_vrf_tableid(interface))
+
+ self.cli_delete(['vrf', 'name', vrf1_name])
+ self.cli_delete(['vrf', 'name', vrf2_name])
+
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 4bcc50453..5acfe20fd 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -80,6 +80,8 @@ class VyOSUnitTestSHIM:
self._session.discard()
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_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 58aef0001..7bfe0d221 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -22,9 +22,11 @@ from base_interfaces_test import BasicInterfaceTest
from glob import glob
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import process_named_running
-from vyos.utils.kernel import check_kmod
from vyos.utils.file import read_file
+from vyos.utils.kernel import check_kmod
+from vyos.utils.network import interface_exists
+from vyos.utils.process import process_named_running
+from vyos.utils.process import call
from vyos.xml_ref import default_value
def get_config_value(interface, key):
@@ -33,7 +35,7 @@ def get_config_value(interface, key):
return tmp[0]
wifi_cc_path = ['system', 'wireless', 'country-code']
-
+country = 'se'
class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -66,7 +68,8 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
cls._test_ipv6 = False
cls._test_vlan = False
- cls.cli_set(cls, wifi_cc_path + ['se'])
+ 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
@@ -84,7 +87,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
def test_wireless_hostapd_config(self):
# Only set the hostapd (access-point) options
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'ssid'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
@@ -161,7 +164,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
def test_wireless_hostapd_vht_mu_beamformer_config(self):
# Multi-User-Beamformer
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'vht_mu-beamformer'
antennas = '3'
@@ -230,7 +233,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
def test_wireless_hostapd_vht_su_beamformer_config(self):
# Single-User-Beamformer
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'vht_su-beamformer'
antennas = '3'
@@ -299,16 +302,14 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
def test_wireless_hostapd_he_config(self):
# Only set the hostapd (access-point) options - HE mode for 802.11ax at 6GHz
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'ssid'
channel = '1'
sae_pw = 'VyOSVyOSVyOS'
- country = 'de'
bss_color = '37'
channel_set_width = '134'
center_channel_freq_1 = '15'
- self.cli_set(wifi_cc_path + [country])
self.cli_set(self._base_path + [interface, 'ssid', ssid])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'channel', channel])
@@ -353,10 +354,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_config_value(interface, 'he_oper_centr_freq_seg0_idx')
self.assertEqual(center_channel_freq_1, tmp)
- # Country code
- tmp = get_config_value(interface, 'country_code')
- self.assertEqual(country.upper(), tmp)
-
# BSS coloring
tmp = get_config_value(interface, 'he_bss_color')
self.assertEqual(bss_color, tmp)
@@ -386,15 +383,12 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
def test_wireless_hostapd_wpa_config(self):
# Only set the hostapd (access-point) options
- interface = 'wlan1'
- phy = 'phy0'
+ interface = self._interfaces[1] # wlan1
ssid = 'VyOS-SMOKETEST'
channel = '1'
wpa_key = 'VyOSVyOSVyOS'
mode = 'n'
- country = 'de'
- self.cli_set(self._base_path + [interface, 'physical-device', phy])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'mode', mode])
@@ -454,7 +448,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.assertTrue(process_named_running('hostapd'))
def test_wireless_access_point_bridge(self):
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'VyOS-Test'
bridge = 'br42477'
@@ -491,7 +485,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_delete(bridge_path)
def test_wireless_security_station_address(self):
- interface = 'wlan1'
+ interface = self._interfaces[1] # wlan1
ssid = 'VyOS-ACL'
hostapd_accept_station_conf = f'/run/hostapd/{interface}_station_accept.conf'
@@ -511,6 +505,12 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
+ self.assertTrue(interface_exists(interface))
+ self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_accept.conf'))
+ self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_deny.conf'))
+
+ self.assertTrue(process_named_running('hostapd'))
+
# in accept mode all addresses are allowed unless specified in the deny list
tmp = get_config_value(interface, 'macaddr_acl')
self.assertEqual(tmp, '0')
@@ -526,6 +526,11 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
# Switch mode accept -> deny
self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'deny'])
self.cli_commit()
+
+ self.assertTrue(interface_exists(interface))
+ self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_accept.conf'))
+ self.assertTrue(os.path.isfile(f'/run/hostapd/{interface}_station_deny.conf'))
+
# In deny mode all addresses are denied unless specified in the allow list
tmp = get_config_value(interface, 'macaddr_acl')
self.assertEqual(tmp, '1')
@@ -535,4 +540,9 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
if __name__ == '__main__':
check_kmod('mac80211_hwsim')
- unittest.main(verbosity=2, failfast=True)
+ # loading the module created two WIFI Interfaces in the background (wlan0 and wlan1)
+ # remove them to have a clean test start
+ for interface in ['wlan0', 'wlan1']:
+ if interface_exists(interface):
+ call(f'sudo iw dev {interface} del')
+ unittest.main(verbosity=2)
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_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index c5cf2aab6..f676e2a52 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -21,6 +21,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv6
from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vrf_tableid
base_path = ['protocols', 'static']
vrf_path = ['protocols', 'vrf']
@@ -421,7 +422,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
tmp = get_interface_config(vrf)
# Compare VRF table ID
- self.assertEqual(tmp['linkinfo']['info_data']['table'], int(vrf_config['table']))
+ self.assertEqual(get_vrf_tableid(vrf), int(vrf_config['table']))
self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf')
# Verify FRR bgpd configuration
@@ -478,4 +479,4 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.assertIn(tmp, frrconfig)
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2, failfast=True)
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/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 243397dc2..176882ca5 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -25,6 +25,7 @@ from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.utils.file import read_file
from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import interface_exists
from vyos.utils.system import sysctl_read
@@ -111,8 +112,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
- tmp = get_interface_config(vrf)
- self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
+ self.assertEqual(int(table), get_vrf_tableid(vrf))
# Increment table ID for the next run
table = str(int(table) + 1)
@@ -266,8 +266,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for address in addresses:
self.assertTrue(is_intf_addr_assigned(interface, address))
# Verify VRF table ID
- tmp = get_interface_config(vrf)
- self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
+ self.assertEqual(int(table), get_vrf_tableid(vrf))
# Verify interface is assigned to VRF
tmp = get_interface_config(interface)
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_wireless.py b/src/conf_mode/interfaces_wireless.py
index 5fd7ab6e9..f35a250cb 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()
@@ -93,6 +97,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')
@@ -120,7 +129,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)
@@ -232,11 +241,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)):
@@ -272,11 +276,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)
@@ -290,23 +289,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/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 8d8c234c0..184725573 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -26,7 +26,7 @@ from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
-from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import get_vrf_members
from vyos.utils.network import interface_exists
from vyos.utils.process import call
@@ -160,8 +160,8 @@ def verify(vrf):
# routing table id can't be changed - OS restriction
if interface_exists(name):
- tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
- if tmp and tmp != vrf_config['table']:
+ tmp = get_vrf_tableid(name)
+ if tmp and tmp != int(vrf_config['table']):
raise ConfigError(f'VRF "{name}" table id modification not possible!')
# VRF routing table ID must be unique on the system