summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.tmpl4
-rw-r--r--data/templates/dhcp-server/dhcpdv6.conf.tmpl4
-rw-r--r--data/templates/monitoring/telegraf.tmpl6
-rw-r--r--interface-definitions/dhcp-server.xml.in12
-rw-r--r--python/vyos/configdict.py6
-rw-r--r--python/vyos/configdiff.py84
-rw-r--r--python/vyos/configtree.py96
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py38
-rwxr-xr-xsrc/conf_mode/dhcp_server.py4
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py4
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py22
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py6
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py52
13 files changed, 302 insertions, 36 deletions
diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl
index dbd864b5e..0f0c622d4 100644
--- a/data/templates/dhcp-server/dhcpd.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpd.conf.tmpl
@@ -62,7 +62,7 @@ subnet {{ address | network_from_ipv4 }} netmask {{ address | netmask_from_ipv4
# Shared network configration(s)
{% if shared_network_name is defined and shared_network_name is not none %}
{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %}
-shared-network {{ network | replace('_','-') }} {
+shared-network {{ network }} {
{% if network_config.authoritative is defined %}
authoritative;
{% endif %}
@@ -212,7 +212,7 @@ shared-network {{ network | replace('_','-') }} {
{% endfor %}
{% endif %}
on commit {
- set shared-networkname = "{{ network | replace('_','-') }}";
+ set shared-networkname = "{{ network }}";
{% if hostfile_update is defined %}
set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
index 45d629928..d5f277463 100644
--- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
@@ -15,7 +15,7 @@ option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }};
# Shared network configration(s)
{% if shared_network_name is defined and shared_network_name is not none %}
{% for network, network_config in shared_network_name.items() if network_config.disable is not defined %}
-shared-network {{ network | replace('_','-') }} {
+shared-network {{ network }} {
{% if network_config.common_options is defined and network_config.common_options is not none %}
{% if network_config.common_options.info_refresh_time is defined and network_config.common_options.info_refresh_time is not none %}
option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
@@ -117,7 +117,7 @@ shared-network {{ network | replace('_','-') }} {
{% endfor %}
{% endif %}
on commit {
- set shared-networkname = "{{ network | replace('_','-') }}";
+ set shared-networkname = "{{ network }}";
}
}
{% endfor %}
diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl
index d3145a500..cf33eec4e 100644
--- a/data/templates/monitoring/telegraf.tmpl
+++ b/data/templates/monitoring/telegraf.tmpl
@@ -1,12 +1,12 @@
# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py
[agent]
- interval = "10s"
+ interval = "15s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
- collection_jitter = "0s"
- flush_interval = "10s"
+ collection_jitter = "5s"
+ flush_interval = "15s"
flush_jitter = "0s"
precision = ""
debug = false
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index d1ed579e9..8c10ccf99 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -369,6 +369,18 @@
<leafNode name="tftp-server-name">
<properties>
<help>TFTP server name</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>TFTP server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>TFTP server FQDN</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="time-offset">
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 1f245f3d2..be10cbdfc 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -104,6 +104,12 @@ def list_diff(first, second):
second = set(second)
return [item for item in first if item not in second]
+def is_node_changed(conf, path):
+ from vyos.configdiff import get_config_diff
+ D = get_config_diff(conf, key_mangling=('-', '_'))
+ D.set_level(conf.get_level())
+ return D.is_node_changed(path)
+
def leaf_node_changed(conf, path):
"""
Check if a leaf node was altered. If it has been altered - values has been
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 0e41fbe27..81932e6d0 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -16,6 +16,7 @@
from enum import IntFlag, auto
from vyos.config import Config
+from vyos.configtree import DiffTree
from vyos.configdict import dict_merge
from vyos.util import get_sub_dict, mangle_dict_keys
from vyos.xml import defaults
@@ -36,6 +37,8 @@ class Diff(IntFlag):
ADD = auto()
STABLE = auto()
+ALL = Diff.MERGE | Diff.DELETE | Diff.ADD | Diff.STABLE
+
requires_effective = [enum_to_key(Diff.DELETE)]
target_defaults = [enum_to_key(Diff.MERGE)]
@@ -73,19 +76,24 @@ def get_config_diff(config, key_mangling=None):
isinstance(key_mangling[1], str)):
raise ValueError("key_mangling must be a tuple of two strings")
- return ConfigDiff(config, key_mangling)
+ diff_t = DiffTree(config._running_config, config._session_config)
+
+ return ConfigDiff(config, key_mangling, diff_tree=diff_t)
class ConfigDiff(object):
"""
The class of config changes as represented by comparison between the
session config dict and the effective config dict.
"""
- def __init__(self, config, key_mangling=None):
+ def __init__(self, config, key_mangling=None, diff_tree=None):
self._level = config.get_level()
self._session_config_dict = config.get_cached_root_dict(effective=False)
self._effective_config_dict = config.get_cached_root_dict(effective=True)
self._key_mangling = key_mangling
+ self._diff_tree = diff_tree
+ self._diff_dict = diff_tree.dict if diff_tree else {}
+
# mirrored from Config; allow path arguments relative to level
def _make_path(self, path):
if isinstance(path, str):
@@ -134,7 +142,17 @@ class ConfigDiff(object):
self._key_mangling[1])
return config_dict
- def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
+ def is_node_changed(self, path=[]):
+ if self._diff_tree is None:
+ raise NotImplementedError("diff_tree class not available")
+
+ if (self._diff_tree.add.exists(self._make_path(path)) or
+ self._diff_tree.sub.exists(self._make_path(path))):
+ return True
+ return False
+
+ def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False,
+ recursive=False):
"""
Args:
path (str|list): config path
@@ -144,6 +162,8 @@ class ConfigDiff(object):
value
no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default
values to ret['merge']
+ recursive: if true, use config_tree diff algorithm provided by
+ diff_tree class
Returns: dict of lists, representing differences between session
and effective config, under path
@@ -154,6 +174,34 @@ class ConfigDiff(object):
"""
session_dict = get_sub_dict(self._session_config_dict,
self._make_path(path), get_first_key=True)
+
+ if recursive:
+ if self._diff_tree is None:
+ raise NotImplementedError("diff_tree class not available")
+ else:
+ add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ ret = {}
+ ret[enum_to_key(Diff.MERGE)] = session_dict
+ ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path),
+ get_first_key=True)
+ ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path),
+ get_first_key=True)
+ ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path),
+ get_first_key=True)
+ for e in Diff:
+ k = enum_to_key(e)
+ if not (e & expand_nodes):
+ ret[k] = list(ret[k])
+ else:
+ if self._key_mangling:
+ ret[k] = self._mangle_dict_keys(ret[k])
+ if k in target_defaults and not no_defaults:
+ default_values = defaults(self._make_path(path))
+ ret[k] = dict_merge(default_values, ret[k])
+ return ret
+
effective_dict = get_sub_dict(self._effective_config_dict,
self._make_path(path), get_first_key=True)
@@ -179,7 +227,8 @@ class ConfigDiff(object):
return ret
- def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
+ def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False,
+ recursive=False):
"""
Args:
path (str|list): config path
@@ -189,6 +238,8 @@ class ConfigDiff(object):
value
no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default
values to ret['merge']
+ recursive: if true, use config_tree diff algorithm provided by
+ diff_tree class
Returns: dict of lists, representing differences between session
and effective config, at path
@@ -198,6 +249,31 @@ class ConfigDiff(object):
dict['stable'] = config values in both session and effective
"""
session_dict = get_sub_dict(self._session_config_dict, self._make_path(path))
+
+ if recursive:
+ if self._diff_tree is None:
+ raise NotImplementedError("diff_tree class not available")
+ else:
+ add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ ret = {}
+ ret[enum_to_key(Diff.MERGE)] = session_dict
+ ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path))
+ ret[enum_to_key(Diff.ADD)] = get_sub_dict(add, self._make_path(path))
+ ret[enum_to_key(Diff.STABLE)] = get_sub_dict(inter, self._make_path(path))
+ for e in Diff:
+ k = enum_to_key(e)
+ if not (e & expand_nodes):
+ ret[k] = list(ret[k])
+ else:
+ if self._key_mangling:
+ ret[k] = self._mangle_dict_keys(ret[k])
+ if k in target_defaults and not no_defaults:
+ default_values = defaults(self._make_path(path))
+ ret[k] = dict_merge(default_values, ret[k])
+ return ret
+
effective_dict = get_sub_dict(self._effective_config_dict, self._make_path(path))
ret = _key_sets_from_dicts(session_dict, effective_dict)
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index d8ffaca99..e9cdb69e4 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -17,6 +17,7 @@ import json
from ctypes import cdll, c_char_p, c_void_p, c_int
+LIBPATH = '/usr/lib/libvyosconfig.so.0'
def escape_backslash(string: str) -> str:
"""Escape single backslashes in string that are not in escape sequence"""
@@ -42,7 +43,9 @@ class ConfigTreeError(Exception):
class ConfigTree(object):
- def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'):
+ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
+ if config_string is None and address is None:
+ raise TypeError("ConfigTree() requires one of 'config_string' or 'address'")
self.__config = None
self.__lib = cdll.LoadLibrary(libpath)
@@ -60,7 +63,7 @@ class ConfigTree(object):
self.__to_string.restype = c_char_p
self.__to_commands = self.__lib.to_commands
- self.__to_commands.argtypes = [c_void_p]
+ self.__to_commands.argtypes = [c_void_p, c_char_p]
self.__to_commands.restype = c_char_p
self.__to_json = self.__lib.to_json
@@ -123,18 +126,26 @@ class ConfigTree(object):
self.__set_tag.argtypes = [c_void_p, c_char_p]
self.__set_tag.restype = c_int
+ self.__get_subtree = self.__lib.get_subtree
+ self.__get_subtree.argtypes = [c_void_p, c_char_p]
+ self.__get_subtree.restype = c_void_p
+
self.__destroy = self.__lib.destroy
self.__destroy.argtypes = [c_void_p]
- config_section, version_section = extract_version(config_string)
- config_section = escape_backslash(config_section)
- config = self.__from_string(config_section.encode())
- if config is None:
- msg = self.__get_error().decode()
- raise ValueError("Failed to parse config: {0}".format(msg))
+ if address is None:
+ config_section, version_section = extract_version(config_string)
+ config_section = escape_backslash(config_section)
+ config = self.__from_string(config_section.encode())
+ if config is None:
+ msg = self.__get_error().decode()
+ raise ValueError("Failed to parse config: {0}".format(msg))
+ else:
+ self.__config = config
+ self.__version = version_section
else:
- self.__config = config
- self.__version = version_section
+ self.__config = address
+ self.__version = ''
def __del__(self):
if self.__config is not None:
@@ -143,13 +154,16 @@ class ConfigTree(object):
def __str__(self):
return self.to_string()
+ def _get_config(self):
+ return self.__config
+
def to_string(self):
config_string = self.__to_string(self.__config).decode()
config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
- def to_commands(self):
- return self.__to_commands(self.__config).decode()
+ def to_commands(self, op="set"):
+ return self.__to_commands(self.__config, op.encode()).decode()
def to_json(self):
return self.__to_json(self.__config).decode()
@@ -281,3 +295,61 @@ class ConfigTree(object):
else:
raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
+ def get_subtree(self, path, with_node=False):
+ check_path(path)
+ path_str = " ".join(map(str, path)).encode()
+
+ res = self.__get_subtree(self.__config, path_str, with_node)
+ subt = ConfigTree(address=res)
+ return subt
+
+class DiffTree:
+ def __init__(self, left, right, path=[], libpath=LIBPATH):
+ if left is None:
+ left = ConfigTree(config_string='\n')
+ if right is None:
+ right = ConfigTree(config_string='\n')
+ if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
+ raise TypeError("Arguments must be instances of ConfigTree")
+ if path:
+ if not left.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree")
+ if not right.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree")
+
+ self.left = left
+ self.right = right
+
+ self.__lib = cdll.LoadLibrary(libpath)
+
+ self.__diff_tree = self.__lib.diff_tree
+ self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p]
+ self.__diff_tree.restype = c_void_p
+
+ self.__trim_tree = self.__lib.trim_tree
+ self.__trim_tree.argtypes = [c_void_p, c_void_p]
+ self.__trim_tree.restype = c_void_p
+
+ check_path(path)
+ path_str = " ".join(map(str, path)).encode()
+
+ res = self.__diff_tree(path_str, left._get_config(), right._get_config())
+
+ # full diff config_tree and python dict representation
+ self.full = ConfigTree(address=res)
+ self.dict = json.loads(self.full.to_json())
+
+ # config_tree sub-trees
+ self.add = self.full.get_subtree(['add'])
+ self.sub = self.full.get_subtree(['sub'])
+ self.inter = self.full.get_subtree(['inter'])
+
+ # trim sub(-tract) tree to get delete tree for commands
+ ref = self.right.get_subtree(path, with_node=True) if path else self.right
+ res = self.__trim_tree(self.sub._get_config(), ref._get_config())
+ self.delete = ConfigTree(address=res)
+
+ def to_commands(self):
+ add = self.add.to_commands()
+ delete = self.delete.to_commands(op="delete")
+ return delete + "\n" + add
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 24df0af4d..55218ecac 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -607,6 +607,44 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
interface = f'vtun{ii}'
self.assertNotIn(interface, interfaces())
+ def test_openvpn_options(self):
+ # Ensure OpenVPN process restart on openvpn-option CLI node change
+
+ interface = 'vtun5001'
+ path = base_path + [interface]
+
+ self.cli_set(path + ['mode', 'site-to-site'])
+ self.cli_set(path + ['local-address', '10.0.0.2'])
+ self.cli_set(path + ['remote-address', '192.168.0.3'])
+ self.cli_set(path + ['shared-secret-key-file', s2s_key])
+
+ self.cli_commit()
+
+ # Now verify the OpenVPN "raw" option passing. Once an openvpn-option is
+ # added, modified or deleted from the CLI, OpenVPN daemon must be restarted
+ cur_pid = process_named_running('openvpn')
+ self.cli_set(path + ['openvpn-option', '--persist-tun'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_set(path + ['openvpn-option', '--persist-key'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_delete(path + ['openvpn-option'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
if __name__ == '__main__':
# Our SSL certificates need a subject ...
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index a8cef5ebf..d27f8d995 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -109,7 +109,7 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
# T2665: defaults include lease time per TAG node which need to be added to
# individual subnet definitions
default_values = defaults(base + ['shared-network-name', 'subnet'])
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index e6a2e4486..be1e6db1e 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -41,7 +41,7 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
return dhcpv6
def verify(dhcpv6):
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index 979a5612e..f49d5b304 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -21,6 +21,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
@@ -41,6 +42,14 @@ def get_config(config=None):
conf = Config()
base = ['interfaces', 'geneve']
geneve = get_interface_dict(conf, base)
+
+ # GENEVE interfaces are picky and require recreation if certain parameters
+ # change. But a GENEVE interface should - of course - not be re-created if
+ # it's description or IP address is adjusted. Feels somehow logic doesn't it?
+ for cli_option in ['remote', 'vni']:
+ if leaf_node_changed(conf, cli_option):
+ geneve.update({'rebuild_required': {}})
+
return geneve
def verify(geneve):
@@ -65,11 +74,12 @@ def generate(geneve):
def apply(geneve):
# Check if GENEVE interface already exists
- if geneve['ifname'] in interfaces():
- g = GeneveIf(geneve['ifname'])
- # GENEVE is super picky and the tunnel always needs to be recreated,
- # thus we can simply always delete it first.
- g.remove()
+ if 'rebuild_required' in geneve or 'delete' in geneve:
+ if geneve['ifname'] in interfaces():
+ g = GeneveIf(geneve['ifname'])
+ # GENEVE is super picky and the tunnel always needs to be recreated,
+ # thus we can simply always delete it first.
+ g.remove()
if 'deleted' not in geneve:
# This is a special type of interface which needs additional parameters
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 38ed127ff..f7edddcbf 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -29,7 +29,7 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_diffie_hellman_length
@@ -83,8 +83,8 @@ def get_config(config=None):
openvpn = get_interface_dict(conf, base)
if 'deleted' not in openvpn:
- tmp = leaf_node_changed(conf, ['openvpn-option'])
- if tmp: openvpn['restart_required'] = ''
+ if is_node_changed(conf, ['openvpn-option']):
+ openvpn.update({'restart_required': {}})
openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn)
return openvpn
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
new file mode 100755
index 000000000..0c9e83112
--- /dev/null
+++ b/src/op_mode/vpn_ipsec.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+
+from vyos.util import call
+
+
+def debug_peer(peer, tunnel):
+ if not peer or peer == "all":
+ debug_commands = [
+ "sudo ipsec statusall",
+ "sudo swanctl -L",
+ "sudo swanctl -l",
+ "sudo swanctl -P",
+ "sudo ip x sa show",
+ "sudo ip x policy show",
+ "sudo ip tunnel show",
+ "sudo ip address",
+ "sudo ip rule show",
+ "sudo ip route | head -100",
+ "sudo ip route show table 220"
+ ]
+ for debug_cmd in debug_commands:
+ print(f'\n### {debug_cmd} ###')
+ call(debug_cmd)
+ return
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Control action', required=True)
+ parser.add_argument('--name', help='Name for peer reset', required=False)
+ parser.add_argument('--tunnel', help='Specific tunnel of peer', required=False)
+
+ args = parser.parse_args()
+
+ if args.action == "vpn-debug":
+ debug_peer(args.name, args.tunnel)